詳解Java的構造方法及類的初始化
一. 利用構造方法給對象初始化
1. 構造方法的概念
構造方法(也稱為構造器)是一個特殊的成員方法,其名字必須與類名相同,在創建對象時,由編譯器自動調用,並且在整個對象的生命周期內隻調用一次。
構造方法的作用就是給對象中的成員進行初始化,並不負責給對象開辟空間。
public class Date { public int year; public int month; public int day; // 構造方法: // 名字與類名相同,沒有返回值類型,設置為void也不行 // 一般情況下使用public修飾 // 在創建對象時由編譯器自動調用,並且在對象的生命周期內隻調用一次 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; System.out.println("Date(int,int,int)方法被調用瞭"); } public void printDate() { System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { // 此處創建瞭一個Date類型的對象,並沒有顯式調用構造方法 Date d = new Date(2021, 6, 9); // 輸出Date(int,int,int)方法被調用瞭 d.printDate(); // 2021-6-9 } }
2. 構造方法的特性
1.名字必須與類名相同
2.沒有返回值類型,設置為void也不行
3.創建對象時由編譯器自動調用,並且在對象的生命周期內隻調用一次
4.絕大多數情況下使用public來修飾,特殊場景下會被private修飾
5.構造方法可以重載(用戶根據自己的需求提供不同參數的構造方法); 下面兩個構造方法:名字相同,參數列表不同,因此構成瞭方法重載
public class Date { public int year; public int month; public int day; // 無參構造方法 public Date(){ this.year = 1900; this.month = 1; this.day = 1; } // 帶有三個參數的構造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { Date d = new Date(); d.printDate(); } }
6.如果用戶沒有顯式定義,編譯器會生成一份默認的構造方法,生成的默認構造方法一定是無參的; 一旦用戶定義,編譯器則不再生成;下面代碼中,沒有定義任何構造方法,編譯器會默認生成一個不帶參數的構造方法。
public class Date { public int year; public int month; public int day; public void printDate(){ System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { Date d = new Date(); d.printDate(); } }
7.構造方法中,可以通過this調用其他構造方法來簡化代碼
【註意事項】
- 構造方法中,通過this(…)去調用其他構造方法,這條語句必須是構造方法中第一條語句
- 多個構造方法不可以互相調用(不能形成環), 會形成構造器的遞歸調用,但卻沒有調用的結束條件
public class Date { public int year; public int month; public int day; // 無參構造方法--內部給各個成員賦值初始值,該部分功能與三個參數的構造方法重復 // 此處可以在無參構造方法中通過this調用帶有三個參數的構造方法 // 但是this(2022,8,16);必須是構造方法中第一條語句 public Date(){ //System.out.println(year); 註釋取消掉,編譯會失敗 this(2022, 8, 16); //this.year = 1900; //this.month = 1; //this.day = 1; } // 帶有三個參數的構造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } }
3. 子類構造方法
在繼承基礎上,子類對象構造時,需要先調用基類構造方法,然後執行子類的構造方法。
在子類構造方法中,並沒有寫任何關於基類構造的代碼,但是在構造子類對象時,先執行基類的構造方法,然後執行子類的構造方法,
原因在於:子類對象中成員是有兩部分組成的,基類繼承下來的以及子類新增加的部分 。父類和子類, 肯定是先有父再有子,所以在構造子類對象時候 ,子類構造方法中先要調用基類的構造方法,將從基類繼承下來的成員構造完整 ,然後再完成子類自己的構造,將子類自己新增加的成員初始化完整 。
【註意事項】
1.若父類顯式定義無參或者默認的構造方法,在子類構造方法第一行默認有隱含的super()調用,即調用基類構造方法
public class Base { public Base(){ System.out.println("Base()"); } } public class Derived extends Base{ public Derived(){ // super(); // 註意子類構造方法中默認會調用基類的無參構造方法:super(), // 用戶沒有寫時,編譯器會自動添加,而且super()必須是子類構造方法中第一條語句, // 並且隻能出現一次 System.out.println("Derived()"); } } public class Test { public static void main(String[] args) { Derived d = new Derived(); } }
2.如果父類構造方法是帶有參數的,此時需要用戶為子類顯式定義構造方法,並在子類構造方法中選擇合適的父類構造方法調用,否則編譯失敗。
public class Animal { public String name; public int age; public Animal(String name, int age) { this.name = name; this.age = age; System.out.println("Animal(String , int )"); } } public class Dog extends Animal{ //傻狗 是狗的屬性 public boolean silly; public Dog(String name,int age,boolean silly) { //1. 先幫助父類部分初始化 必須放到第一行 super(name,age); this.silly = silly; System.out.println("Dog(String ,int ,boolean )"); } public static void main(String[] args) { Animal animal2 = new Dog("金毛",6,false); } }
3.在子類構造方法中,super(…)調用父類構造時,必須是子類構造方法中第一條語句。
4.super(…)隻能在子類構造方法中出現一次,由與this(…)調用時也要在第一條語句,所以super(…)不能和this(…)同時出現,也就是是說子類構造方法中不能使用this(…)
4. 避免在構造方法中調用重寫的方法
一段有坑的代碼. 我們創建兩個類, B 是父類, D 是子類. D 中重寫 func 方法. 並且在 B 的構造方法中調用 func
class B { public B() { // do nothing func(); } public void func() { System.out.println("B.func()"); } } class D extends B { private int num = 1; @Override public void func() { System.out.println("D.func() " + num); } } public class Main { public static void main(String[] args) { D d = new D(); } }
執行結果:
- 構造 D 對象的同時, 會調用 B 的構造方法.
- B 的構造方法中調用瞭 func 方法, 此時會觸發動態綁定, 會調用到 D 中的 func
- 此時 D 對象自身還沒有構造, num 處在未初始化的狀態, 值為 0;如果具備多態性,num的值應該是1.
- 所以在構造函數內,盡量避免使用實例方法,除瞭final和private方法。
【結論】:
“用盡量簡單的方式使對象進入可工作狀態”, 盡量不要在構造器中調用方法(如果這個方法被子類重寫, 就會觸發動態綁定, 但是此時子類對象還沒構造完成), 可能會出現一些隱藏的但是又極難發現的問題.
二. 對象的默認初始化
在Java方法內部定義一個局部變量時,用戶必須要將其賦值或者初始化,否則會編譯失敗;
但對象中的字段(成員變量),用戶不需要將其初始化就可直接訪問使用,這裡其原因在於new對象時,jvm會給出字段的默認初始化。
下面是new對象是時,jvm層面執行的概述:
1.檢測對象對應的類是否加載瞭,如果沒有加載則加載
2.為對象分配內存空間
3.處理並發安全問題
比如:多個線程同時申請對象,JVM要保證給對象分配的空間不沖突
4.初始化所分配的空間
即:對象空間被申請好之後,對象中包含的成員已經設置好瞭初始值
數據類型 | 默認值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
reference (引用類型) | null |
5.設置對象頭信息(關於對象內存模型後面會介紹)
6.調用構造方法,給對象中各個成員賦值
三. 就地初始化對象
在聲明成員變量時,就直接給出瞭初始值。
代碼編譯完成後,編譯器會將所有給成員初始化的這些語句添加到各個構造方法中
public class Date { public int year = 1900; public int month = 1; public int day = 1; public Date(){ } public Date(int year, int month, int day) { } public static void main(String[] args) { Date d1 = new Date(2022,8,16); Date d2 = new Date(); } }
四. 類的初始化順序
1. 普通類(沒有繼承關系)
靜態部分(靜態變量、常量,靜態代碼塊)
- 在類加載階段執行,類中存在多個靜態部分時,會按順序執行
- 靜態代碼塊隻會執行一次,且靜態的變量、常量等隻會創建一份
非靜態部分(實例變量、常量、實例代碼塊)
當有對象創建時才會執行,按順序執行
最後執行構造方法,當有對象創建時才會執行
代碼演示:
class Person { public String name; public int age; public Organ organ = new Organ(); public Person(String name, int age) { this.name = name; this.age = age; System.out.println("構造方法執行"); } { System.out.println("實例代碼塊執行"); } static { System.out.println("靜態代碼塊執行"); } } class Organ { //... public Organ() { System.out.println("實例變量::organ"); } } public class TestDemo { public static void main(String[] args) { Person person1 = new Person("xin",21); System.out.println("=============="); Person person2 = new Person("xinxin",20); } }
執行結果:
2. 派生類( 有繼承關系)
靜態部分(靜態變量、常量,靜態代碼塊)
- 父類靜態代碼塊優先於子類靜態代碼塊執行,且是最早執行
- 隻有第一次實例化子類對象時,父類和子類的靜態部分會執行; 之後再實例化子類對象時,父類和子類的靜態部分都不會再執行
父類非靜態部分(實例變量、常量、實例代碼塊)和父類構造方法
子類非靜態部分(實例變量、常量、實例代碼塊)和子類構造方法
class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person:構造方法執行"); } { System.out.println("Person:實例代碼塊執行"); } static { System.out.println("Person:靜態代碼塊執行"); } } class Student extends Person{ public Student(String name,int age) { super(name,age); System.out.println("Student:構造方法執行"); } { System.out.println("Student:實例代碼塊執行"); } static { System.out.println("Student:靜態代碼塊執行"); } } public class TestDemo4 { public static void main(String[] args) { Student student1 = new Student("張三",19); System.out.println("==========================="); Student student2 = new Student("gaobo",20); } public static void main1(String[] args) { Person person1 = new Person("bit",10); System.out.println("============================"); Person person2 = new Person("gaobo",20); } }
執行結果:
到此這篇關於詳解Java的構造方法及類的初始化的文章就介紹到這瞭,更多相關Java初始化內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!