對(duì)于初學(xué)java的同學(xué)兰珍,應(yīng)該都有個(gè)疑惑,我們?cè)诙x一個(gè)數(shù)據(jù)類(lèi)的時(shí)候询吴,為什么不把字段直接寫(xiě)成public的掠河,硬是要把屬性定義成private的,然后給屬性加上getset方法猛计,比如下面這兩種寫(xiě)法
class Data{
public String name="";
public int age=1;
}
class Data{
private String name="";
private int age=1;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
之前一直以為是為了做方法封裝唠摹,我們可以在屬性的getset方法中加入一些邏輯,擴(kuò)展性更好奉瘤,所以沒(méi)有深究
直到后來(lái)學(xué)習(xí)了類(lèi)的加載過(guò)程勾拉,看到了一道這樣的題
public void main(){
Father fa=new Son();
System.out.println(fa.age);
System.out.println(fa.name);
System.out.println(fa.getName());
}
class Father{
public String name="張三";
public int age=1;
public String getName(){
return name;
}
}
class Son extends Father{
public String name="李四";
public int age=2;
public String getName(){
return name;
}
}
這是一道典型的面向?qū)ο蟮亩鄳B(tài)的例子,我們當(dāng)初接觸java的面向?qū)ο蟮臅r(shí)候毛好,最開(kāi)始接觸的就是這種寫(xiě)法望艺。
大家猜測(cè)一下輸出結(jié)果是什么
1
張三
李四
不知道大家猜對(duì)了沒(méi)有。
下面來(lái)解釋一下原因:
我們都知道每個(gè)類(lèi)里面都有一個(gè)this關(guān)鍵字指向本類(lèi)對(duì)象肌访,用來(lái)告訴我們當(dāng)前使用變量的屬于哪個(gè)實(shí)例對(duì)象找默,也就是所謂的上下文。
類(lèi)初始化的時(shí)候吼驶,每個(gè)非靜態(tài)方法內(nèi)部都會(huì)有一個(gè)變量this惩激,這個(gè)可以通過(guò)查看自字節(jié)碼看出來(lái)店煞,包括匿名內(nèi)部類(lèi),也會(huì)持有當(dāng)前類(lèi)的this對(duì)象(這個(gè)就是android handler內(nèi)存泄露的原因之一)。
所以在在方法內(nèi)調(diào)用屬性风钻,其實(shí)相當(dāng)于這么寫(xiě)
public String getName(){
return this.name;
}
但是屬性是沒(méi)有this指針的顷蟀,也就是說(shuō)屬性是沒(méi)有多態(tài)的,我們調(diào)用的對(duì)象是什么骡技,輸出之后就是哪個(gè)對(duì)象的屬性鸣个。
//大家可以試試這個(gè)例子,輸出結(jié)果都是=號(hào)前面的對(duì)象的屬性布朦。
public void main(){
Son son=new Son();
System.out.println(son.age);
System.out.println(son.name);
System.out.println(son.getName());
Father fa=new Father();
System.out.println(fa.age);
System.out.println(fa.name);
System.out.println(fa.getName());
}
而這種多態(tài)的寫(xiě)法是面向?qū)ο蟮暮诵乃诙谟谝恍┐蟮墓ぞ呖蚣軆?nèi)隨處可見(jiàn),比如安卓的源碼中的Context類(lèi)是趴,Application,Activity涛舍,Service等一眾關(guān)鍵類(lèi)都繼承自Context。
那為啥同樣編譯成字節(jié)碼的kotlin卻可以直接調(diào)用屬性呢唆途,那是因?yàn)閗otlin默認(rèn)幫你實(shí)現(xiàn)了屬性的getset方法
//這時(shí)候編譯器會(huì)提示我們Redundant getter 富雅,多余的getset方法
class Data{
var name:String=""
get() {return field}
set(value) {
field=value
}
}
其實(shí)我們?cè)谒⒚嬖囶}的時(shí)候有很多對(duì)應(yīng)的面試題:比如下面這個(gè),屬于經(jīng)典面試題
public void main() {
// Father father = new Father();
Father fa = new Son();
// Son son = new Son();
}
class Father {
public String name = "張三";
public Father() {
System.out.println(name);
System.out.println(this.name);
getName();
}
public void getName() {
System.out.println("調(diào)用的Father");
System.out.println(this.name);
}
}
class Son extends Father {
public String name = "李四";
public Son() {
}
public void getName() {
System.out.println("調(diào)用的son");
System.out.println(this.name);
}
}
大家可以想想輸出結(jié)果是啥
張三
張三
調(diào)用的son
null
李四
這道題其實(shí)和類(lèi)的加載順序有關(guān)系肛搬,先加載父類(lèi)的屬性(屬性加載會(huì)先把屬性初始化為基本值没佑,比如int為0,String為null)温赔,再加載父類(lèi)的構(gòu)造方法图筹,然后再是子類(lèi)的屬性和子類(lèi)的構(gòu)造方法。這里最難理解的其實(shí)是這個(gè)null让腹,所以我特意在getName中多加了一個(gè)打印。
子類(lèi)初始化的時(shí)候扣溺,先調(diào)用父類(lèi)的構(gòu)造方法骇窍,父類(lèi)調(diào)用構(gòu)造方法的時(shí)候,去調(diào)用getName方法锥余,被調(diào)用的被重寫(xiě)的子類(lèi)的getName方法腹纳,而這時(shí)候子類(lèi)的屬性賦值操作還沒(méi)執(zhí)行,只做了初始化操作驱犹,所以打印出來(lái)的name值就是null嘲恍。
至于static的靜態(tài)變量和方法,都是屬于class對(duì)象本身雄驹,誰(shuí)調(diào)用就是用誰(shuí)的佃牛,相對(duì)來(lái)說(shuō)簡(jiǎn)單一點(diǎn)。