對于初學java的同學,應該都有個疑惑,我們在定義一個數據類的時候,為什么不把字段直接寫成public的,硬是要把屬性定義成private的,然后給屬性加上getset方法,比如下面這兩種寫法
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方法中加入一些邏輯,擴展性更好,所以沒有深究
直到后來學習了類的加載過程,看到了一道這樣的題
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;
}
}
這是一道典型的面向對象的多態的例子,我們當初接觸java的面向對象的時候,最開始接觸的就是這種寫法。
大家猜測一下輸出結果是什么
1
張三
李四
不知道大家猜對了沒有。
下面來解釋一下原因:
我們都知道每個類里面都有一個this關鍵字指向本類對象,用來告訴我們當前使用變量的屬于哪個實例對象,也就是所謂的上下文。
類初始化的時候,每個非靜態方法內部都會有一個變量this,這個可以通過查看自字節碼看出來,包括匿名內部類,也會持有當前類的this對象(這個就是android handler內存泄露的原因之一)。
所以在在方法內調用屬性,其實相當于這么寫
public String getName(){
return this.name;
}
但是屬性是沒有this指針的,也就是說屬性是沒有多態的,我們調用的對象是什么,輸出之后就是哪個對象的屬性。
//大家可以試試這個例子,輸出結果都是=號前面的對象的屬性。
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());
}
而這種多態的寫法是面向對象的核心所在,在一些大的工具框架內隨處可見,比如安卓的源碼中的Context類,Application,Activity,Service等一眾關鍵類都繼承自Context。
那為啥同樣編譯成字節碼的kotlin卻可以直接調用屬性呢,那是因為kotlin默認幫你實現了屬性的getset方法
//這時候編譯器會提示我們Redundant getter ,多余的getset方法
class Data{
var name:String=""
get() {return field}
set(value) {
field=value
}
}
其實我們在刷面試題的時候有很多對應的面試題:比如下面這個,屬于經典面試題
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("調用的Father");
System.out.println(this.name);
}
}
class Son extends Father {
public String name = "李四";
public Son() {
}
public void getName() {
System.out.println("調用的son");
System.out.println(this.name);
}
}
大家可以想想輸出結果是啥
張三
張三
調用的son
null
李四
這道題其實和類的加載順序有關系,先加載父類的屬性(屬性加載會先把屬性初始化為基本值,比如int為0,String為null),再加載父類的構造方法,然后再是子類的屬性和子類的構造方法。這里最難理解的其實是這個null,所以我特意在getName中多加了一個打印。
子類初始化的時候,先調用父類的構造方法,父類調用構造方法的時候,去調用getName方法,被調用的被重寫的子類的getName方法,而這時候子類的屬性賦值操作還沒執行,只做了初始化操作,所以打印出來的name值就是null。
至于static的靜態變量和方法,都是屬于class對象本身,誰調用就是用誰的,相對來說簡單一點。