1)父類構造函數
java中當調用某個類的構造方法的時候,系統總會調用父類的非靜態初始化塊進行初始化,這個調用是隱式的,而且父類的靜態初始化代碼
塊總是會被執行,接着調用父類的一個或者多個構造器執行初始化,這個調用也可以通過super進行顯式調用。
例如:
父類代碼如下:
public class Creature {//父類
{//非靜態代碼塊
System.out.println("creature的非靜態代碼塊正在執行");
}
public Creature(){
System.out.println("creature的構造函數正在執行");
}
}
子類代碼如下:
public class Animal extends Creature {
{
System.out.println("animal的初始化代碼塊正在執行");
}
public Animal(){
System.out.println("animal的構造方法正在執行");
}
public static void main(String[] args){
Animal a = new Animal() ;
}
}
則運行程序後的結果爲:
creature的非靜態代碼塊正在執行
creature的構造函數正在執行
animal的初始化代碼塊正在執行
animal的構造方法正在執行
從結果中可以看出:調用某個類的構造方法的時候總是會先執行父類的非靜態代碼塊,然後執行父類的構造方法,最後纔是執行當前類的
非靜態代碼塊和構造方法。執行過程中有先後順序。
若果想要顯式調用父類的構造方法則可以使用super(),來調用,但是super關鍵字和this關鍵字都必須放在構造放的第一行,而且只能使
用一個,爲什麼要放在第一行呢?因爲如果不放在第一行則先調用子類的初始化代碼,再調用父類的初始化代碼,則父類中的初始化後的值
會覆蓋子類中的初始化的值。
注:super用於顯式調用父類的構造器,this可以顯式調用本類中的重載的構造器。
2)訪問子類對象的實例變量
子類的方法可以訪問父類中的實例變量,這是因爲子類繼承父類就會獲得父類中的成員變量和方法,但是父類方法不能訪問子類的實例變量
,因爲父類根本無法知道它將被哪個類繼承,它的子類將會增加怎麼樣的成員變量。但是,在極端的情況下,父類也可以訪問子類中的變量。
例如:
父類代碼如下:
public class Base {//父類
private int i = 2 ;
public Base(){
this.display() ;
}
public void display(){
System.out.println(i);
}
}
子類中代碼如下:
public class Derived extends Base {
private int i = 22 ;
public Derived(){
i = 222 ;
}
public void display(){
System.out.println(i);
}
}
測試用例如下:
public class Test {
public static void main(String[] args) {
new Derived() ;
}
}
程序的執行結果爲:
0
也許你會感到奇怪,爲什麼不是2?22?222?怎麼會是0呢,下面我們看一下程序的執行過程。當我們調用new Derived() ;創建Derived
實例的時候,系統會爲Derived對象分配內存空間,Derived會有兩個i實例變量,會分配兩個空間來保存i的值。分配完空間以後i的值爲0
,如果有引用類型則引用類型的值爲null。接下來程序在執行Derived的構造器之前會執行Base的構造器,表面上看Base的構造器中只有
一行代碼,但是在父類中定義i的時候執行的初始值2,因此經過編譯之後,該構造方法中應該包含如下兩行代碼:
i =2 ;
this.display() ;
程序先將Base中的i賦值爲2,然後執行display方法。此處有一個關鍵字this,this到底代表誰呢?表面上看this代表的是Base的當前實例,
但是實際上代碼是放在Derived的構造器中的,所以this最終代表的是Derived的當前實例(編譯類型是Base而實際引用一個Derived對象),
所以如果在父類的構造方法中直接輸出System.out.println(this.i) ;則輸出的結果爲2。但是調用this.display()方法,此時調用的是
子類中重寫的display方法,輸出的變量i也是子類中的i,但是此時子類中的變量i還沒有賦值,所以輸出結果爲0。
爲了詳細的看清楚this變量到底代表什麼實例,我們將Base的構造方法修改如下:
public Base(){
System.out.println(this.i);
System.out.println(this.getClass());
this.display() ;
}
再次運行程序,結果爲:
2
class edu.qichao.chapter2.Derived
0
可以看到this代表的是Derived的實例,但是編譯的時候類型爲Base,所以輸出this.i的值爲2。
3)調用被子類重寫的方法
默認情況下,子類可以調用父類的方法,但是父類不能調用子類的方法,因爲父類不知道它將被哪個子類繼承,也不知道子類將增加怎麼
樣的方法。
例如:
父類Animal的代碼如下:
public class Animal {
private String desc ;
public Animal(){
this.desc = getDesc() ;
}
public String getDesc(){
return "Animal" ;
}
public String toString(){
return desc ;
}
}
子類Wolf的代碼如下:
public class Wolf extends Animal {
private String name ;
private double weight ;
public Wolf(String name , double weight){
this.name = name ;
this.weight = weight ;
}
public String getDesc(){
return "Wolf[name=" + name + ",weight=" + weight + "]" ;
}
public static void main(String[] args){
System.out.println(new Wolf("灰太狼" , 3));
}
}
程序的運行結果爲:
Wolf[name=null,weight=0.0]
現在我們分析一下程序執行的過程。在main方法中通過new Wolf("灰太狼" , 3);來創建一個Wolf的實例,子類會隱式調用父類的構造方
法,在父類構造方法中初始化desc變量this.desc = getDesc() ;此處需要注意this變量,雖然這個this放在Animal的構造放中,但是是在
Wolf的構造方法中調用父類的構造方法,所以this編譯時類型爲Animal,運行時類型爲Wolf,此處調用的getDesc方法是子類Wolf的方法,
但是子類中的name和weight變量都沒有初始化,默認爲null和0.0.所以程序的最終結果爲:Wolf[name=null,weight=0.0]
4)繼承成員變量和成員方法的區別
java中隊成員變量的繼承和成員方法的繼承是不同的。
例如:
父類代碼如下:
public class Base {
int count = 2 ;
public void display(){
System.out.println(this.count);
}
}
子類代碼如下:
public class Derived extends Base {
int count = 20 ;
@Override
public void display(){
System.out.println(this.count);
}
}
測試用例如下:
public class Test {
public static void main(String[] args) {
Base b = new Base() ;
System.out.println(b.count);
b.display() ;
System.out.println("-----------------");
Derived d = new Derived() ;
System.out.println(d.count);
d.display() ;
System.out.println("-----------------");
Base bd = new Derived() ;
System.out.println(bd.count);
bd.display() ;
System.out.println("-----------------");
Base d2b = d ;
System.out.println(d2b.count);
}
}
程序運行結果爲:
2
2
-----------------
20
20
-----------------
2
20
-----------------
2
在上面的程序中,不管是d變量、還是bd變量、還是都d2b變量。只要他們指向一個Derived對象,則不管他們聲明時用了什麼類型,當通過
這些變量調用方法時,方法的行爲總是表現出他們的實際類型的行爲,但是如果通過這些變量來訪問他們所指向對象的實例變量的時候,
這些實例變量的值總是表現出聲明這些變量所用類型的行爲。由此可見,java處理成員變量和成員方法的繼承時是有區別的。