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