寫在最前
此系列文章是作者在最近參加了一些筆試面試之后進(jìn)行的一波小總結(jié)汁蝶,本文為第一篇渐扮,將持續(xù)定期更新。
無論你認(rèn)為自己的編程技術(shù)在同齡人中多么出類拔萃掖棉,自己寫過多少高技術(shù)含量paper墓律,跟過多少學(xué)校的項(xiàng)目。一個你可能從未仔細(xì)探索的小問題很可能讓你的能力不受信任幔亥,相信不少人和我一樣筆試時后悔沒提前鞏固一下基礎(chǔ)知識耻讽。說多都是淚...
文中的部分例子和定義來自作者十星推薦,每個學(xué)java人都看過的《Thinking in Java》帕棉,推薦大家看英文原版针肥,會很大程度加深對Java的理解饼记。另外,問題提綱的總結(jié)來自作者最近研讀的機(jī)械工業(yè)出版社的《Java程序員面試筆試寶典》慰枕。文章主干具则,分析解釋部分全部由作者手打,標(biāo)明網(wǎng)上資料忘記出處的具帮,希望細(xì)心人指點(diǎn)博肋。
1.static的作用
static,顧名思義蜂厅,靜態(tài)匪凡。當(dāng)類中的成員變量或方法聲明為static時,無需為此類創(chuàng)建參照掘猿,便可對其進(jìn)行操作病游。即,不依賴該類的實(shí)例术奖,同時也被該類的實(shí)例所共享礁遵。下面通過兩種static的使用情況進(jìn)行分析:
- static變量
同為成員變量,與實(shí)例變量每創(chuàng)建一次便為其分配一次內(nèi)存不同采记,JVM只為static變量分配一次內(nèi)存佣耐,并在加載類的過程中完成該操作。可用類名直接訪問此變量或通過實(shí)例調(diào)用唧龄,前者是被推薦的兼砖,因?yàn)檫@樣不僅強(qiáng)調(diào)了該變量的static屬性,而且也在某種程度上使編譯器更容易去進(jìn)行優(yōu)化既棺。所以在對象之間有共享值或?yàn)榱朔奖阍L問某種變量時一般需要使用static變量讽挟。
- static方法
對于static方法,也同時可以通過類名調(diào)用或?qū)嵗{(diào)用丸冕。因此需要注意的是耽梅,static方法中不能用this或super關(guān)鍵字,不能直接訪問此方法所在類的實(shí)例變量或?qū)嵗椒?/strong>胖烛,只能訪問該類的靜態(tài)成員變量和方法眼姐。因?yàn)閷?shí)例變量和方法有特定的對象,而靜態(tài)方法占據(jù)一個特定的數(shù)據(jù)區(qū)域佩番。
舉例:
Class StaticTest{
static int i = 47;
int j = 10;
}
Class Incrementable{
static void increment(){
//通過類名直接對i進(jìn)行操作
StaticTest.i++;
//此處無法對j進(jìn)行訪問众旗,因?yàn)槠錇閷?shí)例變量
}
}
2.final的作用
final,在Java中通常解釋為不可變的趟畏,也就是說贡歧,final關(guān)鍵字是用來防止變化。一般我們在兩種情況下使用:設(shè)計(jì)(design)或效率(efficiency)。下面分情況來分析final關(guān)鍵字的作用:
- final數(shù)據(jù)——聲明類中屬性或變量
每種編程語言都有一種聲明常量的方法利朵,java中便是final律想。基本類型(Primitive Type)和引用類型(Object Reference Type)的屬性在聲明final后哗咆,里面存放的值都不可再改變蜘欲。但有所不同的是益眉,在基本類型中晌柬,這個值是實(shí)在的,比如100,"java"郭脂;在引用類型中存放的是地址年碘,所以final只是使其地址不可改變,這個地址所指的對象展鸡、數(shù)組皆是可以改變的屿衅。需要注意的是,static final的基本變量命名法莹弊,全大寫字母涤久,單詞之間用"_"(underscore)連接。舉例:
public static final int VALUE_ONE = 9;
- final方法
使用final聲明方法主要是為了給方法加鎖以防止繼承的類對其內(nèi)容進(jìn)行修改忍弛,也就是使其不可重寫(override)响迂。因此final方法可以被繼承,但不能被重寫细疚。
- final類
在前面加上final關(guān)鍵字的類是因?yàn)槟悴幌M祟惐焕^承蔗彤,換句話說,在某種設(shè)計(jì)情況下你的類永遠(yuǎn)不需要去作出改變疯兼,或者為了安全原因你不希望它有子類然遏。簡單舉例:
class SmallBrain{}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f(){}
}
//class Further extends Dinosaur{}此句無法執(zhí)行,因?yàn)镈inosaur類為final
public class Jurassic {
Public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++
}
}
還有一個網(wǎng)上看到的例子吧彪,忘記出處了:
final byte bt1 = 1;
final byte bt2 = 2;
byte bt3 = bt1 + bt2;
此例中若沒有final便會報(bào)錯待侵,因?yàn)槿绻サ鬴inal,bt1和bt2在運(yùn)算時JVM將其自動轉(zhuǎn)換為了int類型變量姨裸,最后相當(dāng)于將一個int類型賦值給了一個byte類型秧倾。
3.Overload與Override
為了對比,我們先來看一下兩者的英文定義:
- Overriding
Having two methods with the same arguments, but different implementations.
- Overloading
A feature that allows a class to have two or more methods having same name, if their argument lists are different.
不難看出啦扬,Overload和Override都是Java多態(tài)性(Polymorphism)的體現(xiàn)中狂。其中Overriding(重寫)是指父類與子類中,同一方法扑毡,相同名稱和參數(shù)胃榕,重新寫其內(nèi)容,相當(dāng)于“推翻”了父類中的定義。而Overloading(重載)是指在同一類中勋又,定義多個同名方法苦掘,但其中的參數(shù)類型或次序不同。例子如下:
- Overriding
class Dog {
public void bark(){
System.out.println("woof ");
}
}
class Hound extends Dog{
public void sniff(){
System.out.println("sniff");
}
public void bark(){
System.out.println("bowl ");
}
}
- Overloading
class Dog {
public void bark(){
System.out.println("woof ");
}
//overloading method
public void bark(int num){
for (int i = 0; i < num; i++ ) {
System.out.println("woof ");
}
}
}
4.組合與繼承
組合(Composition)與繼承(Inheritance)是Java最常用的兩種類的復(fù)用方法楔壤。要了解他們的區(qū)別鹤啡,我們先來看一下英文定義。
- 組合
Achieved by using instance variables that refers to other objects.
- 繼承
A mechanism wherein a new class is derived from an existing class.
從字面意思來看蹲嚣,組合是在一個類中使用一個類的引用递瑰,通常說明一個類具有某種屬性,有"has a"的關(guān)系隙畜。比如下面的例子抖部,灑水機(jī)"has a"水源:
class WaterSource{
private String s;
WaterSource(){
System.out.println("WaterSource()");
s = "Constructed";
}
public String toString() {
return s;
}
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
private WaterSource source = new WaterSource();
private int i;
private float f;
public String toString() {
return
"valve1 = " + valve1 + " " +
"valve2 = " + valve2 + " " +
"valve3 = " + valve3 + " " +
"valve4 = " + valve4 + "\n" +
"i = " + i + " " + "f = " + f + " " +
"source = " + source;
}
public static void main(String[] args) {
SprinklerSystem sprinklers = new SprinklerSystem();
System.out.println(sprinklers);
}
}
例子中的SprinklerSystem類中創(chuàng)建了WaterSource類的參照,因此此段代碼中最先輸出的為"WaterSource"议惰,這就是最簡單的組合慎颗。
而繼承是一個新類派生于舊類的關(guān)系,那么也就是說具有舊類的屬性言询,有"is a"的關(guān)系俯萎,大家應(yīng)該都對此非常熟悉,因此在此處不再舉例运杭。
不難看出夫啊,當(dāng)某物具有多項(xiàng)屬性時使用組合,比如汽車有引擎县习,車門涮母,輪胎。當(dāng)某物屬于一種某物時使用繼承躁愿,比如哈士奇是一種狗叛本。
5.clone的作用
clone()是Java中用來復(fù)制對象的方法,由于Java取消了指針的概念彤钟,很多人對引用和對象的區(qū)別有所忽視来候,全面理解clone()將對此有很大幫助。
需要注意的是逸雹,當(dāng)寫出如下代碼時
Student st1 = new Student("Daniel");
Student st2 = st1;
System.out.println(st1);
System.out.println(st2);
打印結(jié)果(地址)是完全相同的营搅,因?yàn)閟t1與st2為一個對象的兩個引用。因此我們在使用"="操作符賦值是梆砸,只是進(jìn)行了引用的復(fù)制转质。而clone()方法,才是真正實(shí)現(xiàn)對象復(fù)制的途徑帖世,與上面的做對比:
Student st1 = new Student("Daniel");
Student st2 = (Student)st1.clone();
System.out.println(st1);
System.out.println(st2);
在上面的代碼中休蟹,由于clone()返回的是Object對象,所以需要進(jìn)行向下強(qiáng)制轉(zhuǎn)換為Student。此時打印的結(jié)果就是兩個不同的地址赂弓,真正地完成了對象的復(fù)制绑榴。
- 深拷貝(Deep Copy)和淺拷貝(Shallow Copy)
這里我們又要再次提到基本數(shù)據(jù)類型(Primitive Type)和非基礎(chǔ)類型(Non-Primitive Type)的區(qū)別了,看下面一段代碼:
Public class Student {
private int id;
private String name;
Public Student(int id, String name){
this.age = age;
this.name = name;
}
public Student() {}
public int getId() {
return age;
}
public String getName(){
return name;
}
}
其中id是基本數(shù)據(jù)類型盈魁,在拷貝后沒有什么問題翔怎,然而name是String類型,因此前面提到的方法拷貝過來的只是一個引用值杨耙,便是淺拷貝赤套。相對而言,深復(fù)制就是再創(chuàng)建一個相同的String對象按脚,將這個對象的飲用賦值給拷貝出的新對象于毙。
要實(shí)現(xiàn)深拷貝敦冬,我們需要先實(shí)現(xiàn)Cloneable接口并重寫clone()方法辅搬,將上述代碼略作修改:
Public class Student implements Cloneable{
private int id;
private String name;
Public Student(int id, String name){
this.age = age;
this.name = name;
}
public Student() {}
public int getId() {
return age;
}
public String getName(){
return name;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return (Student)super.clone();
}
}
需要注意的是,在套用多層對象的組合方法中脖旱,徹底的深拷貝需要在每一層對象中都實(shí)現(xiàn)cloneable接口并重寫clone()方法堪遂。非常復(fù)雜,在實(shí)際開發(fā)中用處不多萌庆,但如前文所題溶褪,有助于讀者對內(nèi)存結(jié)構(gòu)有深一步的了解。
6.內(nèi)部類(Inner Classes)
顧名思義践险,內(nèi)部類是指講一個類定義內(nèi)置于另一個類定義之中猿妈。關(guān)于它的作用,《Thinking in Java》里是這樣說明的:
The inner class is a valuable feature because it allows you to group classes that logically belong together and to control the visibility of one within the other.
將邏輯相通的類聲明為內(nèi)部類使代碼更易控制或處理巍虫,在安卓開發(fā)中我們會經(jīng)常見到此類用法彭则。因?yàn)槊總€內(nèi)部類都能獨(dú)立地繼承一個(接口的)實(shí)現(xiàn),所以無論外圍類是否已經(jīng)繼承了某個(接口的)實(shí)現(xiàn)占遥,對于內(nèi)部類都沒有影響俯抖。——以上黑體摘自《Thinking in Java》瓦胎,如翻譯拗口請見諒芬萍。
下面我們來看一個包裹的例子,來簡單分析內(nèi)部類的使用方法搔啊。
public class Parcel1 {
class Contents {
private int i = 11;
public value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel1 p = new Parcel1();
p.ship("Tasmania");
}
}
其中Destination和Contents便是內(nèi)部類我們可以在非靜態(tài)(non-static)方法中對其進(jìn)行調(diào)用柬祠,當(dāng)再靜態(tài)方法如main()中對其進(jìn)行調(diào)用時,需使用“外部類.內(nèi)部類”的方式對其進(jìn)行引用负芋。如漫蛔,將上述代碼進(jìn)行修改。
public class Parcel2 {
class Contents {
private int i = 11;
public value() { return i; }
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel() { return label; }
}
public Destination to(String s) {
return new Destination(s);
}
public Contents contents() {
return new Contents();
}
public void ship(String dest) {
Contents c = new Contents();
Destination d = new Destination(dest);
System.out.println(d.readLabel());
}
public static void main(String[] args) {
Parcel2 p = new Parcel2();
p.ship("Tasmania");
Parcel2 q = new Parcel2();
Parcel2.Contents c = q.contents();
Parcel2.Destination d = q.to("Borneo")
}
}
在此例子中調(diào)用Contents以及Destination對象時,使用了“外部類.內(nèi)部類”的調(diào)用方法惩猫。
7.接口與抽象類
在國外論壇上對于接口(Interface)和抽象類(Abstract class)的討論就一直不斷芝硬。我們還是先來看一下它們的英文定義。
- 抽象類
Classes that contain one or more abstract methods.
- 接口
It is a collection of abstract methods.
乍一看貌似差不多轧房,都是有關(guān)抽象方法的集合,關(guān)于它們的區(qū)別拌阴,僅從定義上來看,有兩點(diǎn)不同:
1.接口暗示著全部方法為抽象奶镶,且不能有任何的implementation迟赃。需要注意的是,這里有一個吵д颍考的點(diǎn)就是纤壁,既然不能implement,那么接口可以繼承嗎捺信?答案是可以的酌媒。相對于接口,抽象類中可以有執(zhí)行默認(rèn)行為的實(shí)例方法迄靠,也就是可以有implementation秒咨。
2.在接口中聲明的變量默認(rèn)為final類型,然而在抽象類中可以有非final類型的變量掌挚。
那么在應(yīng)用上該如何選用這兩者呢雨席,首先我們需要知道他們的本質(zhì)。接口是對動作的抽象吠式,而抽象類則是對根源的抽象陡厘。比如人要吃東西,狗也要吃東西特占,那么我們就可以把吃東西作為一個接口糙置。其中人和狗都屬于生物,那么我們就可以將生物定為一個抽象類摩钙。需要注意的是罢低,一個類只可以繼承一個類(抽象類),但卻可以實(shí)現(xiàn)多個接口胖笛。理解這一點(diǎn)不難网持,上例中人和狗是生物,但不可能同時是非生物體长踊。
我們來看一個經(jīng)典的報(bào)警門的例子功舀,相信大部分人都見過:
interface Alram {
void alarm();
}
abstract class Door {
void open();
void close();
}
class AlarmDoor extends Door implements Alarm {
void oepn() { }
void close() { }
void alarm() { }
}
此例中,只要是門身弊,固有屬性都會有開和關(guān)辟汰,所以將門設(shè)置為抽象類列敲,報(bào)警門只是門的一種。然而報(bào)警門的特殊功能是報(bào)警帖汞,因此我們將報(bào)警設(shè)置為一個接口戴而。在報(bào)警門的這個類中,便繼承了門翩蘸,實(shí)現(xiàn)了報(bào)警接口所意。
8.stack和heap
關(guān)于堆(heap)和棧(stack)的理解不只局限于java,無論是任何語言催首,對此概念區(qū)分理解都是非常重要的扶踊。stackoverflow是這么說的:
Stack is used for static memory allocation and Heap for dynamic memory allocation
stack是靜態(tài)內(nèi)存,而heap是動態(tài)分配郎任。通俗來說秧耗,棧就是用來放引用的,當(dāng)在一段代碼塊定義一個變量時舶治,Java就在棧中為這個變量分配內(nèi)存空間分井,當(dāng)超過變量的作用域后,Java會自動釋放掉為該變量所分配的內(nèi)存空間歼疮,該內(nèi)存空間可以立即被另作他用杂抽。而堆是用來放對象和數(shù)組的,在Java中韩脏,這些對象和數(shù)組是由new來創(chuàng)建的,而在堆中分配的內(nèi)存铸磅,將由Java虛擬機(jī)的自動垃圾回收器來管理赡矢。
關(guān)于實(shí)例中的關(guān)系,只要你能理解上文中提到過的clone()方法阅仔,理解起來非常輕松吹散。
9.== 和 equals
作為經(jīng)常出現(xiàn)在condition里的判斷方法,區(qū)分==和equals是非常重要的八酒,尤其是在安卓的開發(fā)中空民。關(guān)于兩者的比較,我們要聯(lián)合前文提到的堆和棧來理解羞迷。" == "是比較兩個變量棧中的內(nèi)容是否相等界轩,而與之相對應(yīng)的"equals"則是比較堆中的內(nèi)容是否相等。讓我們來舉例說明:
public class Test {
public static void main(String[] args) {
String s1 = "Daniel";
String s2 = new String("Daniel");
if (s1 == s2){
System.out.println("s1 == s2");
}else{
System.out.println("s1 != s2");
}
if (s1 .equals(s2)){
System.out.println("s1 equals s2");
}else{
System.out.println("s1 not equals s2");
}
}
}
本例中輸出的為s1 != s2以及s1 equals s2衔瓮。由此不難理解,s1與s2地址不同浊猾,但內(nèi)容相同。
10. 反射機(jī)制
反射機(jī)制是我們在開發(fā)過程中用的最多的热鞍,但可能你對他的文字定義并不了解:
Java Reflection makes it possible to inspect classes, interfaces, fields and methods at runtime, without knowing the names of the classes, methods etc. at compile time. It is also possible to instantiate new objects, invoke methods and get/set field values using reflection
也就是說葫慎,對于一個類衔彻,通過反射機(jī)制,我能知道他的屬性和方法偷办;對于我創(chuàng)建的一個對象艰额,我能夠調(diào)用他的屬性與方法。這種獲得信息以及調(diào)用對象是動態(tài)(Dynamic)的椒涯。我們通過stackoverflow上一個簡單的例子來理解:
public class TestReflect {
public static void main(String[] args) throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java reflection");
System.out.println(list.get(0));
}
}
Java中所有的對象都有g(shù)etClass()方法悴晰,用以獲取Class對象。不難看出逐工,上述代碼在泛型為Integer的ArrayList中存放一個String類型的對象铡溪。
需要注意的是,除了getClass()還有兩種方式可以獲取class對象泪喊,一是forName()棕硫,另一個則是使用名稱.class來代表。最后我將放上一個CSDN的Winiex's Blog中給出的一個例子給讀者去進(jìn)行分析袒啼,相信在你成功分析了本例之后哈扮,對反射機(jī)制的運(yùn)用將會感覺"Easy as pie"了。
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* <a class='replace_word' title="Java 知識庫" target='_blank' style='color:#df3434; font-weight:bold;'>Java </a>Reflection Cookbook
*
* @author Michael Lee
* @since 2006-8-23
* @version 0.1a
*/
public class Reflection {
/**
* 得到某個對象的公共屬性
*
* @param owner, fieldName
* @return 該屬性對象
* @throws Exception
*
*/
public Object getProperty(Object owner, String fieldName) throws Exception {
Class ownerClass = owner.getClass();
Field field = ownerClass.getField(fieldName);
Object property = field.get(owner);
return property;
}
/**
* 得到某類的靜態(tài)公共屬性
*
* @param className 類名
* @param fieldName 屬性名
* @return 該屬性對象
* @throws Exception
*/
public Object getStaticProperty(String className, String fieldName)
throws Exception {
Class ownerClass = Class.forName(className);
Field field = ownerClass.getField(fieldName);
Object property = field.get(ownerClass);
return property;
}
/**
* 執(zhí)行某對象方法
*
* @param owner
* 對象
* @param methodName
* 方法名
* @param args
* 參數(shù)
* @return 方法返回值
* @throws Exception
*/
public Object invokeMethod(Object owner, String methodName, Object[] args)
throws Exception {
Class ownerClass = owner.getClass();
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(owner, args);
}
/**
* 執(zhí)行某類的靜態(tài)方法
*
* @param className
* 類名
* @param methodName
* 方法名
* @param args
* 參數(shù)數(shù)組
* @return 執(zhí)行方法返回的結(jié)果
* @throws Exception
*/
public Object invokeStaticMethod(String className, String methodName,
Object[] args) throws Exception {
Class ownerClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Method method = ownerClass.getMethod(methodName, argsClass);
return method.invoke(null, args);
}
/**
* 新建實(shí)例
*
* @param className
* 類名
* @param args
* 構(gòu)造函數(shù)的參數(shù)
* @return 新建的實(shí)例
* @throws Exception
*/
public Object newInstance(String className, Object[] args) throws Exception {
Class newoneClass = Class.forName(className);
Class[] argsClass = new Class[args.length];
for (int i = 0, j = args.length; i < j; i++) {
argsClass[i] = args[i].getClass();
}
Constructor cons = newoneClass.getConstructor(argsClass);
return cons.newInstance(args);
}
/**
* 是不是某個類的實(shí)例
* @param obj 實(shí)例
* @param cls 類
* @return 如果 obj 是此類的實(shí)例蚓再,則返回 true
*/
public boolean isInstance(Object obj, Class cls) {
return cls.isInstance(obj);
}
/**
* 得到數(shù)組中的某個元素
* @param array 數(shù)組
* @param index 索引
* @return 返回指定數(shù)組對象中索引組件的值
*/
public Object getByArray(Object array, int index) {
return Array.get(array,index);
}
}
關(guān)于反射機(jī)制更多的信息在作者的另一篇文章中有詳細(xì)說明:http://www.reibang.com/p/381ec446a318
結(jié)語
本文到此處告一段落滑肉,感謝大家的閱讀。我也曾拜讀很多“高科技”的文章去學(xué)習(xí)實(shí)現(xiàn)很多拉風(fēng)的安卓特效摘仅,然而基礎(chǔ)知識無時無刻都在敲響警鐘靶庙,我堅(jiān)信基礎(chǔ)知識是解決問題的最強(qiáng)后勤保障。
最后娃属,文章還有很多不盡人意指出六荒,如果有錯誤和建議還請大家指出,作為新人只希望通過總結(jié)能與大家一起提高矾端。本文將持續(xù)連載掏击,希望大家多多支持。有疑問的歡迎加我的weibo:LightningDC進(jìn)行交流秩铆。