深入剖析 Java 構(gòu)造器調(diào)用及類的初始化順序
Java 中的構(gòu)造函數(shù)或稱為構(gòu)造器,其實就是一段代碼,是在創(chuàng)建類對象的實例并為該對象分配內(nèi)存時調(diào)用該代碼塊。它是一種用于初始化對象的特殊方法。在聲明構(gòu)造函數(shù)時使用訪問修飾符也是允許的。
掌握構(gòu)造函數(shù)是有效學習 Java 的重要組成部分。因此,本篇文章就來談?wù)剟?chuàng)建Java構(gòu)造器的有關(guān)規(guī)則、應(yīng)用以及初始化情況,以全面的理解和掌握 Java 構(gòu)造函數(shù)和相關(guān)情況。
1、構(gòu)造器規(guī)則
編寫Java構(gòu)造器必須遵循的規(guī)則有:
- Java 構(gòu)造函數(shù)不得具有顯式返回類型;
- 它不能是抽象的(abstract)、最終的(final)、靜態(tài)的(static)或同步的(synchronized);
- 構(gòu)造函數(shù)名稱必須與屬于其類的名稱相同;
- 構(gòu)造器調(diào)用構(gòu)造器的第一行原則。
2、構(gòu)造器類型
Java中有兩種構(gòu)造函數(shù):
1)默認構(gòu)造函數(shù)或無參數(shù)構(gòu)造函數(shù)
Java 默認構(gòu)造函數(shù)沒有參數(shù)。這就是為什么它也被稱為無參數(shù)構(gòu)造函數(shù)的原因。 Java 默認構(gòu)造函數(shù)的一般語法是:
- <class_name>(){
- //必要的初始化化代碼
- }
需要知道的是,如果 Java 類中沒有顯式定義構(gòu)造函數(shù),那么 Java 編譯器會自動為該類創(chuàng)建一個默認構(gòu)造函數(shù)。根據(jù)對象的類型,默認構(gòu)造函數(shù)為對象提供默認值或說默認初始化。
使用由 javac(java編譯器) 自動創(chuàng)建的默認構(gòu)造函數(shù)的缺點就是之后程序員無法設(shè)置或改變對象屬性的初始值。例如:
- package com.learning;
- /**
- *
- * @author Solo Cui
- */
- public class ConstructorDemo {
- ConstructorDemo(){//無參構(gòu)造器
- System.out.println("成功執(zhí)行構(gòu)造器,完成對象的創(chuàng)建。");
- }
- public static void main(String[] args) {
- ConstructorDemo cd = new ConstructorDemo() ;
- }
- }
運行輸出結(jié)果如下:
成功執(zhí)行構(gòu)造器,完成對象的創(chuàng)建。
2)參數(shù)化構(gòu)造函數(shù)
任何帶有參數(shù)(一個或以上)的 Java 構(gòu)造函數(shù)都稱為參數(shù)化構(gòu)造函數(shù)。盡管參數(shù)化構(gòu)造函數(shù)通常用于為不同的 Java 對象提供不同的值,但它也可以為不同的 Java 對象提供相同的值,比如0或null。示例如下:
- package com.learning;
- /**
- * 參數(shù)化構(gòu)造器示例
- *
- * @author Solo Cui
- */
- public class ParametersConstructor {
- int id;
- String name;
- ParametersConstructor(String n,int i ) {
- name = n;
- id = i;
- }
- void display(){
- System.out.println(""+id+" "+name);
- }
- public static void main(String args[]) {
- ParametersConstructor s1 = new ParametersConstructor("Solo",666);
- ParametersConstructor s2 = new ParametersConstructor("Cui", 999);
- s1.display();
- s2.display();
- }
- }
運行輸出結(jié)果如下:
- 666 Solo
- 999 Cui
3、構(gòu)造器調(diào)用
構(gòu)造器除了創(chuàng)建對象時的常規(guī)調(diào)用,在同一類內(nèi),構(gòu)造器也可調(diào)用其他構(gòu)造器。這里分兩種情況,即調(diào)用父類構(gòu)造器和調(diào)用當前子類構(gòu)造器。示例如下:
1)this形式調(diào)用
- package com.learning;
- /**
- * 多個構(gòu)造器的類,this形式調(diào)用其他構(gòu)造器
- * @author Administrator
- */
- public class MyParent {
- MyParent(){
- System.out.println("無參構(gòu)造器進行初始化處理……");
- }
- MyParent(String hello){
- this();
- System.out.println("打個招呼:"+hello);
- }
- public static void main(String[] args) {
- new MyParent("You're Great!");
- }
- }
這里一定要注意,調(diào)用同一類內(nèi)的其他構(gòu)造器的this()必須是是構(gòu)造器內(nèi)的第一行,是否有參數(shù),則根據(jù)調(diào)用的構(gòu)造器實際情況決定。
2)super形式調(diào)用
此種調(diào)用是針對類繼承關(guān)系的子類調(diào)用父情況。代碼示例如下:
- package com.learning;
- /**
- * 繼承MyParent類,調(diào)用父類
- * @author Administrator
- */
- public class MyChild extends MyParent{
- String name ="default";
- MyChild(){
- //super():編譯器會隱式調(diào)用父類的無參構(gòu)造器
- System.out.println("My Name is "+name);
- }
- MyChild(String n){
- super("大秦帝國");//顯式調(diào)用父類構(gòu)造器
- name = n ;
- }
- void showName(){
- System.out.println("Name is "+name);
- }
- public static void main(String[] args) {
- MyChild child = new MyChild();
- child.showName();
- MyChild child2 = new MyChild("Solo");
- child2.showName();
- }
- }
請注意:在子類未調(diào)用父類構(gòu)造器時,則編譯器會隱式的調(diào)用父類無參構(gòu)造器的,即super();若子類顯式調(diào)用父類的構(gòu)造器,則必須是在構(gòu)造器的第一行,super內(nèi)的參數(shù)根據(jù)需要傳入即可。還有一點需要注意,即this同類構(gòu)造器調(diào)用和super父子類調(diào)用不能同時出現(xiàn)在同一個構(gòu)造器內(nèi)。
4、構(gòu)造器重載
與 方法一樣, Java類中的構(gòu)造函數(shù)可以重載。構(gòu)造函數(shù)重載,是用相同的構(gòu)造函名但具有不同的參數(shù)列表。所有這些構(gòu)造器各自執(zhí)行不同的任務(wù)。
Java 編譯器通過列表中參數(shù)的總數(shù)及其類型來區(qū)分重載的構(gòu)造函數(shù)。以下代碼片段演示了 Java 中的構(gòu)造函數(shù)重載:
- package com.learning;
- /**
- * 構(gòu)造器重載示例
- *
- * @author Solo Cui
- */
- public class OverloadConstructor {
- int id;
- String name;
- int age;
- OverloadConstructor(int i, String n) {
- id = i;
- name = n;
- }
- OverloadConstructor(int i, String n, int a) {
- id = i;
- name = n;
- age = a;
- }
- void display(){
- System.out.println("id="+id+";name="+name+";age="+age);
- }
- public static void main(String args[]) {
- OverloadConstructor s1 = new OverloadConstructor(333, "Solo");
- OverloadConstructor s2 = new OverloadConstructor(666, "Cui", 25);
- s1.display();
- s2.display();
- }
- }
運行輸出結(jié)果如下:
- id=333;name=Solo;age=0
- id=666;name=Cui;age=25
5、構(gòu)造器與方法
簡單來講,Java 方法是一段具有特定名稱的代碼。在可訪問的范圍內(nèi),只需使用方法名稱即可在程序中的任何時間點調(diào)用它。你也可以理解為對數(shù)據(jù)進行操作,然后返回值(某特定在或void)的子程序。
Java 構(gòu)造函數(shù)是一種特殊類型的方法。兩者在許多方面相似,但并不完全相同。以下是 Java 構(gòu)造函數(shù)和 Java 方法之間一些最重要的區(qū)別:
- 調(diào)用——子類通過super()隱式調(diào)用父類構(gòu)造函數(shù),而方法必須顯式調(diào)用;同類內(nèi)有多個構(gòu)造器時,某個構(gòu)造器調(diào)用另一個構(gòu)造器時,使用this()形式,this后括號有無參數(shù)根據(jù)調(diào)用的構(gòu)造器參數(shù)決定;而方法調(diào)用則是通過this+點(.)+方法名;
- 編譯器——Java編譯器從不提供 Java 默認方法。但是,如果 Java 類中未定義構(gòu)造函數(shù),則 Java 編譯器會提供默認構(gòu)造函數(shù);
- 命名約定——Java構(gòu)造函數(shù)的名稱必須與類的名稱相同。但是方法的名稱可能與包含它的類的名稱相同,也可能不同
- 調(diào)用次數(shù)——Java 構(gòu)造函數(shù)只在對象創(chuàng)建期間被調(diào)用一次。而Java 方法可以根據(jù)需要多次調(diào)用;
- 返回類型——Java 方法必須具有返回類型,但對于構(gòu)造函數(shù)則不需要返回類型;
- 用法——方法用于公開 Java 對象的行為,而構(gòu)造函數(shù)用于初始化相同對象的狀態(tài)。
6、復(fù)制構(gòu)造函數(shù)
盡管 Java 中沒有提供復(fù)制構(gòu)造函數(shù),但可以將值從一個 Java 對象復(fù)制到另一個對象,就像在 C++ 中使用復(fù)制構(gòu)造函數(shù)一樣。
除了使用構(gòu)造函數(shù)將值從一個對象復(fù)制到另一個對象外,還可以通過以下方式完成:
將一個對象的值分配給另一個對象;
或者
通過使用 Object 類的 clone() 方法。
以下程序演示了使用 Java 構(gòu)造函數(shù)將值從一個 Java 對象復(fù)制到另一個對象:
- package com.learning;
- /**
- * 復(fù)制當前類的對象
- *
- * @author Solo Cui
- */
- public class SimpleCopy {
- int id;
- String name;
- SimpleCopy(int i, String n) {
- id = i;
- name = n;
- }
- SimpleCopy(SimpleCopy s) {
- id = s.id;
- name = s.name;
- }
- void show() {
- System.out.println("id=" + id + ";name=" + name);
- }
- public static void main(String[] args) {
- SimpleCopy s1 = new SimpleCopy(138, "Solo");
- SimpleCopy s2 = new SimpleCopy(s1);
- s1.show();
- s2.show();
- }
- }
7、類的初始化
在Java中類初始化可以分為兩類,即靜態(tài)初始化和非靜態(tài)初始化。當創(chuàng)建java對象時,程序總是依次調(diào)用每個父類的非靜態(tài)初始化塊、父類構(gòu)造器(總是從Object開始——Java中的始祖類)執(zhí)行初始化,最后再調(diào)用當前(子)類的非靜態(tài)初始化塊、構(gòu)造器執(zhí)行初始化。通過示例演示如下,下面我們來開看其初始化的順序:
1)父類XParent:
- package com.learning;
- /**
- * 類初始化:父類
- *
- * @author Administrator
- */
- public class XParent {
- static {
- System.out.println("XParent:父類【靜態(tài)】初始化塊");
- }
- {
- System.out.println("XParent:父類【非靜態(tài)】初始化塊");
- }
- public XParent() {
- System.out.println("XParent:父類無參構(gòu)造器");
- }
- public XParent(String name) {
- System.out.println("XParent:父類含參構(gòu)造器:name=" + name);
- }
- }
2)子類XChild:
- package com.learning;
- /**
- * 子類XChild初始化
- *
- * @author Solo cui
- */
- public class XChild extends XParent {
- static {
- System.out.println("XChild:子類靜態(tài)初始化塊");
- }
- {
- System.out.println("XChild:子類非靜態(tài)初始化塊");
- }
- public XChild() {
- this("Solo");
- System.out.println("XChild:子類無參構(gòu)造器");
- }
- public XChild(String name) {
- super(name);
- System.out.println("XChild:子類含參構(gòu)造器: name=" + name);
- }
- public static void main(String[] args) {
- XChild c1 = new XChild("Tomy");
- XChild c2 = new XChild();
- }
- }
運行子類輸出結(jié)果為:
- XParent:父類【靜態(tài)】初始化塊
- XChild:子類靜態(tài)初始化塊
- XParent:父類【非靜態(tài)】初始化塊
- XParent:父類含參構(gòu)造器:name=Tomy
- XChild:子類非靜態(tài)初始化塊
- XChild:子類含參構(gòu)造器: name=Tomy
- XParent:父類【非靜態(tài)】初始化塊
- XParent:父類含參構(gòu)造器:name=Solo
- XChild:子類非靜態(tài)初始化塊
- XChild:子類含參構(gòu)造器: name=Solo
- XChild:子類無參構(gòu)造器
所以,無論新實例化多少個對象,該類的所有父類以及自身的靜態(tài)初始化塊只執(zhí)行一次,而且是最先執(zhí)行的初始化,稱作類的初始化。之后的初始化會依次執(zhí)行父類的非靜態(tài)初始化塊、父類的構(gòu)造器和子類的非靜態(tài)初始化塊、子類的構(gòu)造器來完成初始化稱為對象初始化;在子類的構(gòu)造器中可以通過super來顯式調(diào)用父類的構(gòu)造器,可以通過this來調(diào)用該類重載的其他構(gòu)造器,而具體調(diào)用哪個構(gòu)造器決定于調(diào)用時的參數(shù)類型。
8、關(guān)于構(gòu)造器的FAQs
以下是一些關(guān)于 Java 構(gòu)造函數(shù)的最常見問題。
問:構(gòu)造函數(shù)是否有返回值?
答:雖然不能在 Java 構(gòu)造函數(shù)中使用返回類型,但它確實返回一個值。 Java 構(gòu)造函數(shù)返回當前類實例的引用。
問:Java 中的構(gòu)造函數(shù)鏈是什么?
答:構(gòu)造函數(shù)鏈接是在 Java 編程語言中從其他構(gòu)造函數(shù)調(diào)用構(gòu)造函數(shù)的技術(shù)。 this() 方法用于調(diào)用同一個類構(gòu)造函數(shù),而 super() 方法用于調(diào)用超類構(gòu)造函數(shù)。
問:說說在構(gòu)造器調(diào)用構(gòu)造器時,this和super的區(qū)別
答:super和this的調(diào)用只能在構(gòu)造器中,而且都必須作為構(gòu)造器中的第一行,因此super和this不會同時出現(xiàn)在同一個構(gòu)造器中。
問:在Java中可以從超類構(gòu)造函數(shù)中調(diào)用子類構(gòu)造函數(shù)嗎?
答:不行。
問:Java 有析構(gòu)函數(shù)嗎?
答:Java 沒有析構(gòu)函數(shù),因為它是垃圾自回收語言。在 Java 中無法預(yù)測對象何時會被銷毀。
問:Java 構(gòu)造函數(shù)可以執(zhí)行除初始化之外的哪些任務(wù)?
答:Java 構(gòu)造函數(shù)可以執(zhí)行任何可以使用方法執(zhí)行的操作。使用 Java 構(gòu)造函數(shù)執(zhí)行的一些最流行的任務(wù)是:
調(diào)用方法;
對象創(chuàng)建;
開始一個線程等。
問:Java 中何時需要構(gòu)造函數(shù)重載?
A:構(gòu)造函數(shù)重載在 Java 中通常用于需要以多種不同方式初始化 Java 對象的情況。
問:如果為 Java 構(gòu)造函數(shù)添加返回類型會發(fā)生什么?
答:具有返回類型的 Java 構(gòu)造函數(shù)將被視為典型的 Java 方法。
最后
這就是 Java 構(gòu)造函數(shù)的全部內(nèi)容。掌握好如何有效地使用構(gòu)造函數(shù)是Java編程的必備核心技能之一。根據(jù)具體學習情況多加練習和酌情使用吧。