關於Java中基類構造器的調用問題

  在《Java編程思想》第7章複用類中有這樣一段話,值得深思。當子類繼承了父類時,就涉及到了基類和導出類(子類)這兩個類。從外部來看,導出類就像是一個與基類具備相同接口的新類,或許還會有一些額外的方法和域。但繼承並不僅是複製基類的接口。當建立一個導出類對象時,該對象包含了一個基類的子對象,這個子對象與你用基類直接建立的對象是同樣的,兩者區別在於,後者來自於外部,而基類的子對象是被包裹在導出類對象內部。java

  這就引起出了一個很重要的問題,對基類子對象的正確初始化也是相當重要的(咱們可能在子類的使用基類中繼承的方法和域),並且也僅有一種方法來保證這一點:在子類構造器中調用基類構造器來執行初始化。sql

  無參的基類構造器編程

  咱們知道,當一個類你沒有給他構造函數,Java會自動幫你調用無參的構造器,同時Java也會在導出類的構造器中插入對基類構造器的調用。下面的代碼說明了這個工做機制:架構

  //: reusing/Cartoon.java併發

  // Constructor calls during inheritance.分佈式

  import static net.mindview.util.Print.*;函數

  class Art {高併發

  Art() { print(Art constructor); }性能

  }學習

  class Drawing extends Art {

  Drawing() { print(Drawing constructor); }

  }

  public class Cartoon extends Drawing {

  public Cartoon() { print(Cartoon constructor); }

  public static void main(String[] args) {

  Cartoon x = new Cartoon();

  }

  } / Output:

  Art constructor

  Drawing constructor

  Cartoon constructor

  ///:~

  觀察上述代碼的運行結果,在建立Cartoon對象時,會先調用其父類Drawing的構造器,而其父類又繼承自Art類,因此又會調用Art類的構造器,就像層層往上。雖然在其構造器中都沒有顯式調用其父類構造器,可是Java會自動調用其父類的構造器。即便不爲Cartoon()建立構造器,編譯器也會合成一個默認的無參構造器,該構造器將調用基類的構造器。

  帶參數的基類構造器

  當基類中的構造器都是帶有參數時,編譯器就不會自動調用,必須用關鍵字super顯式地調用基類構造器,而且傳入適當的參數,相應的例子代碼以下:

  //: reusing/Chess.java

  // Inheritance, constructors and arguments.

  import static net.mindview.util.Print.*;

  class Game {

  Game(int i) {

  print(Game constructor);

  }

  }

  class BoardGame extends Game {

  BoardGame(int i) {

  super(i);

  print(BoardGame constructor);

  }

  }

  public class Chess extends BoardGame {

  Chess() {

  super(11);

  print(Chess constructor);

  }

  public static void main(String[] args) {

  Chess x = new Chess();

  }

  } / Output:

  Game constructor

  BoardGame constructor

  Chess constructor

  ///:~

  從上述代碼中能夠觀察到,必須在子類Chess構造器中顯示的使用super調用父類構造器並傳入適當參數。並且,調用基類構造器必須是在子類構造器中作的第一件事。

  基類構造器的調用順序問題

  在此以前,咱們先來探討一下對象引用的初始化問題。在Java中,類中域爲基本類型時可以自動被初始化爲零,可是對象引用會被初始化爲null。咱們每每須要在合適的位置對其進行初始化,下面是幾個能夠進行初始化的位置:

  1.在定義對象的地方。這意味着它們老是可以在構造器被調用以前被初始化。

  2.在類的構造器中。

  3.就在正要使用這些對象以前,這種方式稱爲惰性初始化。

  記住上面的第1點,下面看一個比較複雜的例子來看一下基類構造器的調用順序問題。

  // reusing/Ex7/C7.java

  // TIJ4 Chapter Reusing, Exercise 7, page 246

  /* Modify Exercise 5 so that A and B have constructors with arguments instead

  of default constructors. Write a constructor for C and perform all

  initialization within C's constructor.

  */

  import static org.greggordon.tools.Print.*;

  class A {

  A(char c, int i) { println(A(char, int));}

  }

  class B extends A {

  B(String s, float f){

  super(' ', 0);

  println(B(String, float));

  }

  }

  class C7 extends A {

  private char c;

  private int i;

  C7(char a, int j) {

  super(a, j);

  c = a;

  i = j;

  }

  B b = new B(hi, 1f); // will then construct another A and then a B

  public static void main(String[] args) {

  C7 c = new C7('b', 2); // will construct an A first

  }

  }

  上述這段代碼輸出:

  A(char, int)

  A(char, int)

  B(String, float)

  注意基類構造器、子類構造器、類的成員對象初始化的順序:

  1.在new一個類的對象時,首先調用其父類構造器(能夠是無參的和有參的,無參的系統會自動調用,有參的須要本身指定)。如上述C7中的super(a, j)

  2.而後執行其成員對象初始化語句,調用B類構造器,如上述中的

  B b = new B(hi, 1f),而B的構造器又會先調用基類A的構造器 。 歡迎工做一到五年的Java工程師朋友們加入Java羣: 891219277

  羣內提供免費的Java架構學習資料(裏面有高可用、高併發、高性能及分佈式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用本身每一分每一秒的時間來學習提高本身,不要再用沒有時間「來掩飾本身思想上的懶惰!趁年輕,使勁拼,給將來的本身一個交代!

  3.最後返回到C7中的構造器,繼續執行c=a,i=j。

相關文章
相關標籤/搜索