詳解Java的構(gòu)造方法及類的初始化
一. 利用構(gòu)造方法給對(duì)象初始化
1. 構(gòu)造方法的概念
構(gòu)造方法(也稱為構(gòu)造器)是一個(gè)特殊的成員方法,其名字必須與類名相同,在創(chuàng)建對(duì)象時(shí),由編譯器自動(dòng)調(diào)用,并且在整個(gè)對(duì)象的生命周期內(nèi)只調(diào)用一次。
構(gòu)造方法的作用就是給對(duì)象中的成員進(jìn)行初始化,并不負(fù)責(zé)給對(duì)象開辟空間。
public class Date { public int year; public int month; public int day; // 構(gòu)造方法: // 名字與類名相同,沒(méi)有返回值類型,設(shè)置為void也不行 // 一般情況下使用public修飾 // 在創(chuàng)建對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,并且在對(duì)象的生命周期內(nèi)只調(diào)用一次 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; System.out.println("Date(int,int,int)方法被調(diào)用了"); } public void printDate() { System.out.println(year + "-" + month + "-" + day); } public static void main(String[] args) { // 此處創(chuàng)建了一個(gè)Date類型的對(duì)象,并沒(méi)有顯式調(diào)用構(gòu)造方法 Date d = new Date(2021, 6, 9); // 輸出Date(int,int,int)方法被調(diào)用了 d.printDate(); // 2021-6-9 } }
2. 構(gòu)造方法的特性
1.名字必須與類名相同
2.沒(méi)有返回值類型,設(shè)置為void也不行
3.創(chuàng)建對(duì)象時(shí)由編譯器自動(dòng)調(diào)用,并且在對(duì)象的生命周期內(nèi)只調(diào)用一次
4.絕大多數(shù)情況下使用public來(lái)修飾,特殊場(chǎng)景下會(huì)被private修飾
5.構(gòu)造方法可以重載(用戶根據(jù)自己的需求提供不同參數(shù)的構(gòu)造方法); 下面兩個(gè)構(gòu)造方法:名字相同,參數(shù)列表不同,因此構(gòu)成了方法重載
public class Date { public int year; public int month; public int day; // 無(wú)參構(gòu)造方法 public Date(){ this.year = 1900; this.month = 1; this.day = 1; } // 帶有三個(gè)參數(shù)的構(gòu)造方法 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.如果用戶沒(méi)有顯式定義,編譯器會(huì)生成一份默認(rèn)的構(gòu)造方法,生成的默認(rèn)構(gòu)造方法一定是無(wú)參的; 一旦用戶定義,編譯器則不再生成;下面代碼中,沒(méi)有定義任何構(gòu)造方法,編譯器會(huì)默認(rèn)生成一個(gè)不帶參數(shù)的構(gòu)造方法。
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.構(gòu)造方法中,可以通過(guò)this調(diào)用其他構(gòu)造方法來(lái)簡(jiǎn)化代碼
【注意事項(xiàng)】
- 構(gòu)造方法中,通過(guò)this(…)去調(diào)用其他構(gòu)造方法,這條語(yǔ)句必須是構(gòu)造方法中第一條語(yǔ)句
- 多個(gè)構(gòu)造方法不可以互相調(diào)用(不能形成環(huán)), 會(huì)形成構(gòu)造器的遞歸調(diào)用,但卻沒(méi)有調(diào)用的結(jié)束條件
public class Date { public int year; public int month; public int day; // 無(wú)參構(gòu)造方法--內(nèi)部給各個(gè)成員賦值初始值,該部分功能與三個(gè)參數(shù)的構(gòu)造方法重復(fù) // 此處可以在無(wú)參構(gòu)造方法中通過(guò)this調(diào)用帶有三個(gè)參數(shù)的構(gòu)造方法 // 但是this(2022,8,16);必須是構(gòu)造方法中第一條語(yǔ)句 public Date(){ //System.out.println(year); 注釋取消掉,編譯會(huì)失敗 this(2022, 8, 16); //this.year = 1900; //this.month = 1; //this.day = 1; } // 帶有三個(gè)參數(shù)的構(gòu)造方法 public Date(int year, int month, int day) { this.year = year; this.month = month; this.day = day; } }
3. 子類構(gòu)造方法
在繼承基礎(chǔ)上,子類對(duì)象構(gòu)造時(shí),需要先調(diào)用基類構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法。
在子類構(gòu)造方法中,并沒(méi)有寫任何關(guān)于基類構(gòu)造的代碼,但是在構(gòu)造子類對(duì)象時(shí),先執(zhí)行基類的構(gòu)造方法,然后執(zhí)行子類的構(gòu)造方法,
原因在于:子類對(duì)象中成員是有兩部分組成的,基類繼承下來(lái)的以及子類新增加的部分 。父類和子類, 肯定是先有父再有子,所以在構(gòu)造子類對(duì)象時(shí)候 ,子類構(gòu)造方法中先要調(diào)用基類的構(gòu)造方法,將從基類繼承下來(lái)的成員構(gòu)造完整 ,然后再完成子類自己的構(gòu)造,將子類自己新增加的成員初始化完整 。
【注意事項(xiàng)】
1.若父類顯式定義無(wú)參或者默認(rèn)的構(gòu)造方法,在子類構(gòu)造方法第一行默認(rèn)有隱含的super()調(diào)用,即調(diào)用基類構(gòu)造方法
public class Base { public Base(){ System.out.println("Base()"); } } public class Derived extends Base{ public Derived(){ // super(); // 注意子類構(gòu)造方法中默認(rèn)會(huì)調(diào)用基類的無(wú)參構(gòu)造方法:super(), // 用戶沒(méi)有寫時(shí),編譯器會(huì)自動(dòng)添加,而且super()必須是子類構(gòu)造方法中第一條語(yǔ)句, // 并且只能出現(xiàn)一次 System.out.println("Derived()"); } } public class Test { public static void main(String[] args) { Derived d = new Derived(); } }
2.如果父類構(gòu)造方法是帶有參數(shù)的,此時(shí)需要用戶為子類顯式定義構(gòu)造方法,并在子類構(gòu)造方法中選擇合適的父類構(gòu)造方法調(diào)用,否則編譯失敗。
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.在子類構(gòu)造方法中,super(…)調(diào)用父類構(gòu)造時(shí),必須是子類構(gòu)造方法中第一條語(yǔ)句。
4.super(…)只能在子類構(gòu)造方法中出現(xiàn)一次,由與this(…)調(diào)用時(shí)也要在第一條語(yǔ)句,所以super(…)不能和this(…)同時(shí)出現(xiàn),也就是是說(shuō)子類構(gòu)造方法中不能使用this(…)
4. 避免在構(gòu)造方法中調(diào)用重寫的方法
一段有坑的代碼. 我們創(chuàng)建兩個(gè)類, B 是父類, D 是子類. D 中重寫 func 方法. 并且在 B 的構(gòu)造方法中調(diào)用 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(); } }
執(zhí)行結(jié)果:
- 構(gòu)造 D 對(duì)象的同時(shí), 會(huì)調(diào)用 B 的構(gòu)造方法.
- B 的構(gòu)造方法中調(diào)用了 func 方法, 此時(shí)會(huì)觸發(fā)動(dòng)態(tài)綁定, 會(huì)調(diào)用到 D 中的 func
- 此時(shí) D 對(duì)象自身還沒(méi)有構(gòu)造, num 處在未初始化的狀態(tài), 值為 0;如果具備多態(tài)性,num的值應(yīng)該是1.
- 所以在構(gòu)造函數(shù)內(nèi),盡量避免使用實(shí)例方法,除了final和private方法。
【結(jié)論】:
“用盡量簡(jiǎn)單的方式使對(duì)象進(jìn)入可工作狀態(tài)”, 盡量不要在構(gòu)造器中調(diào)用方法(如果這個(gè)方法被子類重寫, 就會(huì)觸發(fā)動(dòng)態(tài)綁定, 但是此時(shí)子類對(duì)象還沒(méi)構(gòu)造完成), 可能會(huì)出現(xiàn)一些隱藏的但是又極難發(fā)現(xiàn)的問(wèn)題.
二. 對(duì)象的默認(rèn)初始化
在Java方法內(nèi)部定義一個(gè)局部變量時(shí),用戶必須要將其賦值或者初始化,否則會(huì)編譯失??;
但對(duì)象中的字段(成員變量),用戶不需要將其初始化就可直接訪問(wèn)使用,這里其原因在于new對(duì)象時(shí),jvm會(huì)給出字段的默認(rèn)初始化。
下面是new對(duì)象是時(shí),jvm層面執(zhí)行的概述:
1.檢測(cè)對(duì)象對(duì)應(yīng)的類是否加載了,如果沒(méi)有加載則加載
2.為對(duì)象分配內(nèi)存空間
3.處理并發(fā)安全問(wèn)題
比如:多個(gè)線程同時(shí)申請(qǐng)對(duì)象,JVM要保證給對(duì)象分配的空間不沖突
4.初始化所分配的空間
即:對(duì)象空間被申請(qǐng)好之后,對(duì)象中包含的成員已經(jīng)設(shè)置好了初始值
數(shù)據(jù)類型 | 默認(rèn)值 |
---|---|
byte | 0 |
short | 0 |
int | 0 |
long | 0 |
float | 0.0f |
double | 0.0 |
char | /u0000 |
boolean | false |
reference (引用類型) | null |
5.設(shè)置對(duì)象頭信息(關(guān)于對(duì)象內(nèi)存模型后面會(huì)介紹)
6.調(diào)用構(gòu)造方法,給對(duì)象中各個(gè)成員賦值
三. 就地初始化對(duì)象
在聲明成員變量時(shí),就直接給出了初始值。
代碼編譯完成后,編譯器會(huì)將所有給成員初始化的這些語(yǔ)句添加到各個(gè)構(gòu)造方法中
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. 普通類(沒(méi)有繼承關(guān)系)
靜態(tài)部分(靜態(tài)變量、常量,靜態(tài)代碼塊)
- 在類加載階段執(zhí)行,類中存在多個(gè)靜態(tài)部分時(shí),會(huì)按順序執(zhí)行
- 靜態(tài)代碼塊只會(huì)執(zhí)行一次,且靜態(tài)的變量、常量等只會(huì)創(chuàng)建一份
非靜態(tài)部分(實(shí)例變量、常量、實(shí)例代碼塊)
當(dāng)有對(duì)象創(chuàng)建時(shí)才會(huì)執(zhí)行,按順序執(zhí)行
最后執(zhí)行構(gòu)造方法,當(dāng)有對(duì)象創(chuàng)建時(shí)才會(huì)執(zhí)行
代碼演示:
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("構(gòu)造方法執(zhí)行"); } { System.out.println("實(shí)例代碼塊執(zhí)行"); } static { System.out.println("靜態(tài)代碼塊執(zhí)行"); } } class Organ { //... public Organ() { System.out.println("實(shí)例變量::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); } }
執(zhí)行結(jié)果:
2. 派生類( 有繼承關(guān)系)
靜態(tài)部分(靜態(tài)變量、常量,靜態(tài)代碼塊)
- 父類靜態(tài)代碼塊優(yōu)先于子類靜態(tài)代碼塊執(zhí)行,且是最早執(zhí)行
- 只有第一次實(shí)例化子類對(duì)象時(shí),父類和子類的靜態(tài)部分會(huì)執(zhí)行; 之后再實(shí)例化子類對(duì)象時(shí),父類和子類的靜態(tài)部分都不會(huì)再執(zhí)行
父類非靜態(tài)部分(實(shí)例變量、常量、實(shí)例代碼塊)和父類構(gòu)造方法
子類非靜態(tài)部分(實(shí)例變量、常量、實(shí)例代碼塊)和子類構(gòu)造方法
class Person { public String name; public int age; public Person(String name, int age) { this.name = name; this.age = age; System.out.println("Person:構(gòu)造方法執(zhí)行"); } { System.out.println("Person:實(shí)例代碼塊執(zhí)行"); } static { System.out.println("Person:靜態(tài)代碼塊執(zhí)行"); } } class Student extends Person{ public Student(String name,int age) { super(name,age); System.out.println("Student:構(gòu)造方法執(zhí)行"); } { System.out.println("Student:實(shí)例代碼塊執(zhí)行"); } static { System.out.println("Student:靜態(tài)代碼塊執(zhí)行"); } } 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); } }
執(zhí)行結(jié)果:
到此這篇關(guān)于詳解Java的構(gòu)造方法及類的初始化的文章就介紹到這了,更多相關(guān)Java初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Myeclipse鏈接Oracle等數(shù)據(jù)庫(kù)時(shí)lo exception: The Network Adapter coul
今天小編就為大家分享一篇關(guān)于Myeclipse鏈接Oracle等數(shù)據(jù)庫(kù)時(shí)lo exception: The Network Adapter could not establish the connection,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-03-03關(guān)于Java HashMap自動(dòng)排序的簡(jiǎn)單剖析
這篇文章主要給大家介紹了關(guān)于Java HashMap自動(dòng)排序的簡(jiǎn)單剖析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Spring占位符Placeholder的實(shí)現(xiàn)原理解析
這篇文章主要介紹了Spring占位符Placeholder的實(shí)現(xiàn)原理,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03SpringBoot自動(dòng)裝配之@Enable深入講解
這篇文章主要介紹了SpringBoot自動(dòng)裝配之@Enable,SpringBoot中提供了很多Enable開頭的注解,這些注解都是用于動(dòng)態(tài)啟用某些功能的。而其底層原理是使用@Import注?解導(dǎo)入一些配置類,實(shí)現(xiàn)Bean的動(dòng)態(tài)加載2023-01-01