一文把 Java 的構(gòu)造方法吃得透透滴

同學(xué)們好啊,我是沉默王二。又到了《教妹學(xué) Java》的時(shí)間,我很開心,你很期待,有木有?這是《教妹學(xué) Java》專欄的第 18 篇文章,我們來(lái)談?wù)劇癑ava 中的構(gòu)造方法”。
強(qiáng)烈推薦:JavaBooks 項(xiàng)目又新增了 15 本好書,包括 SpringBoot、MyBatis、Redis 和 SpringSecurity,需要的同學(xué)可以在后臺(tái)回復(fù)「Java」關(guān)鍵字獲取下載地址。
“二哥,你怎么把《教妹學(xué) Java》改成《零基礎(chǔ)學(xué) Java》了啊,我都不習(xí)慣了,能不能改回去啊,‘零基礎(chǔ)’感覺(jué)過(guò)于平庸了,‘教妹’的噱頭更足啊,況且你是真的在教妹,沒(méi)什么不好意思的。”三妹一臉認(rèn)真地對(duì)我說(shuō)。
“那好吧,這篇就改回來(lái)吧。只要保證文章的質(zhì)量,確實(shí)也沒(méi)必要過(guò)于糾結(jié)標(biāo)題和封面?!蔽覕倲偸?,對(duì)三妹繼續(xù)說(shuō),“上一節(jié)學(xué)了 Java 中的方法,接著學(xué)構(gòu)造方法的話,難度就小很多了?!?/p>
“在 Java 中,構(gòu)造方法是一種特殊的方法,當(dāng)一個(gè)類被實(shí)例化的時(shí)候,就會(huì)調(diào)用構(gòu)造方法。只有在構(gòu)造方法被調(diào)用的時(shí)候,對(duì)象才會(huì)被分配內(nèi)存空間。每次使用 new 關(guān)鍵字創(chuàng)建對(duì)象的時(shí)候,構(gòu)造方法至少會(huì)被調(diào)用一次?!?/p>
“如果你在一個(gè)類中沒(méi)有看見構(gòu)造方法,并不是因?yàn)闃?gòu)造方法不存在,而是被缺省了,編譯器會(huì)給這個(gè)類提供一個(gè)默認(rèn)的構(gòu)造方法。往大的方面說(shuō),就是,Java 有兩種類型的構(gòu)造方法:無(wú)參構(gòu)造方法和有參構(gòu)造方法?!?/p>
“注意,之所以叫它構(gòu)造方法,是因?yàn)閷?duì)象在創(chuàng)建的時(shí)候,需要通過(guò)構(gòu)造方法初始化值——就是描寫對(duì)象的那些狀態(tài),對(duì)應(yīng)的是類中的字段。”
01、創(chuàng)建構(gòu)造方法的規(guī)則有哪些
構(gòu)造方法必須符合以下規(guī)則:
構(gòu)造方法的名字必須和類名一樣; 構(gòu)造方法沒(méi)有返回類型,包括 void; 構(gòu)造方法不能是抽象的、靜態(tài)的、最終的、同步的,也就是說(shuō),構(gòu)造方法不能通過(guò) abstract、static、final、synchronized 關(guān)鍵字修飾。
簡(jiǎn)單解析一下最后一條規(guī)則。
由于構(gòu)造方法不能被子類繼承,所以用 final 和 abstract 修飾沒(méi)有意義; 構(gòu)造方法用于初始化一個(gè)對(duì)象,所以用 static 修飾沒(méi)有意義; 多個(gè)線程不會(huì)同時(shí)創(chuàng)建內(nèi)存地址相同的同一個(gè)對(duì)象,所以用 synchronized 修飾沒(méi)有必要。
構(gòu)造方法的語(yǔ)法格式如下:
class?class_name?{
????public?class_name(){}????//?默認(rèn)無(wú)參構(gòu)造方法
????public?ciass_name([paramList]){}????//?定義有參數(shù)列表的構(gòu)造方法
????…
????//?類主體
}
值得注意的是,如果用 void 聲明構(gòu)造方法的話,編譯時(shí)不會(huì)報(bào)錯(cuò),但 Java 會(huì)把這個(gè)所謂的“構(gòu)造方法”當(dāng)成普通方法來(lái)處理。
/**
?*?微信搜索「沉默王二」,回復(fù)?Java
?*
?*?@author?沉默王二
?*?@date?2020/11/26
?*/
public?class?Demo?{
????void?Demo(){?}
}
void Demo(){} 看起來(lái)很符合構(gòu)造方法的寫法(與類名相同),但其實(shí)只是一個(gè)不符合規(guī)范的普通方法,方法名的首字母使用了大寫,方法體為空,它并不是默認(rèn)的無(wú)參構(gòu)造方法,可以通過(guò)反編譯后的字節(jié)碼驗(yàn)證。
public?class?Demo?{
????public?Demo()?{
????}
????void?Demo()?{
????}
}
public Demo() {} 才是真正的無(wú)參構(gòu)造方法。
不過(guò),可以使用訪問(wèn)權(quán)限修飾符(private、protected、public、default)來(lái)修飾構(gòu)造方法,訪問(wèn)權(quán)限修飾符決定了構(gòu)造方法的創(chuàng)建方式。
02、 什么是默認(rèn)構(gòu)造方法
如果一個(gè)構(gòu)造方法中沒(méi)有任何參數(shù),那么它就是一個(gè)默認(rèn)構(gòu)造方法,也稱為無(wú)參構(gòu)造方法。
/**
?*?@author?微信搜「沉默王二」,回復(fù)關(guān)鍵字?PDF
?*/
public?class?Bike?{
????Bike(){
????????System.out.println("一輛自行車被創(chuàng)建");
????}
????public?static?void?main(String[]?args)?{
????????Bike?bike?=?new?Bike();
????}
}
在上面這個(gè)例子中,我們?yōu)?Bike 類中創(chuàng)建了一個(gè)無(wú)參的構(gòu)造方法,它在我們創(chuàng)建對(duì)象的時(shí)候被調(diào)用。
程序輸出結(jié)果如下所示:
一輛自行車被創(chuàng)建
通常情況下,無(wú)參構(gòu)造方法是可以缺省的,我們開發(fā)者并不需要顯式的聲明無(wú)參構(gòu)造方法,把這項(xiàng)工作交給編譯器更輕松一些。

“二哥,默認(rèn)構(gòu)造方法的目的是什么?它為什么是一個(gè)空的???”三妹疑惑地看著我,提出了這個(gè)尖銳的問(wèn)題。
“三妹啊,默認(rèn)構(gòu)造方法的目的主要是為對(duì)象的字段提供默認(rèn)值,看下面這個(gè)例子你就明白了?!蔽倚赜谐芍竦鼗卮鸬馈?/p>
/**
?*?@author?沉默王二,一枚有趣的程序員
?*/
public?class?Person?{
????private?String?name;
????private?int?age;
????public?static?void?main(String[]?args)?{
????????Person?p?=?new?Person();
????????System.out.println("姓名?"?+?p.name?+?"?年齡?"?+?p.age);
????}
}
輸出結(jié)果如下所示:
姓名?null?年齡?0
在上面的例子中,默認(rèn)構(gòu)造方法初始化了 name 和 age 的值,name 是 String 類型,所以默認(rèn)值為 null,age 是 int 類型,所以默認(rèn)值為 0。如果沒(méi)有默認(rèn)構(gòu)造方法的話,這項(xiàng)工作就無(wú)法完成了。
03、什么是有參構(gòu)造方法
有參數(shù)的構(gòu)造方法被稱為有參構(gòu)造方法,參數(shù)可以有一個(gè)或多個(gè)。有參構(gòu)造方法可以為不同的對(duì)象提供不同的值。當(dāng)然,也可以提供相同的值。
/**
?*?@author?沉默王二,一枚有趣的程序員
?*/
public?class?ParamConstructorPerson?{
????private?String?name;
????private?int?age;
????public?ParamConstructorPerson(String?name,?int?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?void?out()?{
????????System.out.println("姓名?"?+?name?+?"?年齡?"?+?age);
????}
????public?static?void?main(String[]?args)?{
????????ParamConstructorPerson?p1?=?new?ParamConstructorPerson("沉默王二",18);
????????p1.out();
????????ParamConstructorPerson?p2?=?new?ParamConstructorPerson("沉默王三",16);
????????p2.out();
????}
}
在上面的例子中,構(gòu)造方法有兩個(gè)參數(shù)(name 和 age),這樣的話,我們?cè)趧?chuàng)建對(duì)象的時(shí)候就可以直接為 name 和 age 賦值了。
new?ParamConstructorPerson("沉默王二",18);
new?ParamConstructorPerson("沉默王三",16);
如果沒(méi)有有參構(gòu)造方法的話,就需要通過(guò) setter 方法給字段賦值了。
04、如何重載構(gòu)造方法
在 Java 中,構(gòu)造方法和方法類似,只不過(guò)沒(méi)有返回類型。它也可以像方法一樣被重載。構(gòu)造方法的重載也很簡(jiǎn)單,只需要提供不同的參數(shù)列表即可。編譯器會(huì)通過(guò)參數(shù)的數(shù)量來(lái)決定應(yīng)該調(diào)用哪一個(gè)構(gòu)造方法。
/**
?*?@author?沉默王二,一枚有趣的程序員
?*/
public?class?OverloadingConstrutorPerson?{
????private?String?name;
????private?int?age;
????private?int?sex;
????public?OverloadingConstrutorPerson(String?name,?int?age,?int?sex)?{
????????this.name?=?name;
????????this.age?=?age;
????????this.sex?=?sex;
????}
????public?OverloadingConstrutorPerson(String?name,?int?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?void?out()?{
????????System.out.println("姓名?"?+?name?+?"?年齡?"?+?age?+?"?性別?"?+?sex);
????}
????public?static?void?main(String[]?args)?{
????????OverloadingConstrutorPerson?p1?=?new?OverloadingConstrutorPerson("沉默王二",18,?1);
????????p1.out();
????????OverloadingConstrutorPerson?p2?=?new?OverloadingConstrutorPerson("沉默王三",16);
????????p2.out();
????}
}
創(chuàng)建對(duì)象的時(shí)候,如果傳遞的是三個(gè)參數(shù),那么就會(huì)調(diào)用 OverloadingConstrutorPerson(String name, int age, int sex) 這個(gè)構(gòu)造方法;如果傳遞的是兩個(gè)參數(shù),那么就會(huì)調(diào)用 OverloadingConstrutorPerson(String name, int age) 這個(gè)構(gòu)造方法。
05、構(gòu)造方法和方法有什么區(qū)別
構(gòu)造方法和方法之間的區(qū)別還是蠻多的,比如說(shuō)下面這些:

06、如何復(fù)制對(duì)象
復(fù)制一個(gè)對(duì)象可以通過(guò)下面三種方式完成:
通過(guò)構(gòu)造方法 通過(guò)對(duì)象的值 通過(guò) Object 類的 clone()方法
1)通過(guò)構(gòu)造方法
/**
?*?@author?沉默王二,一枚有趣的程序員
?*/
public?class?CopyConstrutorPerson?{
????private?String?name;
????private?int?age;
????public?CopyConstrutorPerson(String?name,?int?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?CopyConstrutorPerson(CopyConstrutorPerson?person)?{
????????this.name?=?person.name;
????????this.age?=?person.age;
????}
????public?void?out()?{
????????System.out.println("姓名?"?+?name?+?"?年齡?"?+?age);
????}
????public?static?void?main(String[]?args)?{
????????CopyConstrutorPerson?p1?=?new?CopyConstrutorPerson("沉默王二",18);
????????p1.out();
????????CopyConstrutorPerson?p2?=?new?CopyConstrutorPerson(p1);
????????p2.out();
????}
}
在上面的例子中,有一個(gè)參數(shù)為 CopyConstrutorPerson 的構(gòu)造方法,可以把該參數(shù)的字段直接復(fù)制到新的對(duì)象中,這樣的話,就可以在 new 關(guān)鍵字創(chuàng)建新對(duì)象的時(shí)候把之前的 p1 對(duì)象傳遞過(guò)去。
2)通過(guò)對(duì)象的值
/**
?*?@author?沉默王二,一枚有趣的程序員
?*/
public?class?CopyValuePerson?{
????private?String?name;
????private?int?age;
????public?CopyValuePerson(String?name,?int?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?CopyValuePerson()?{
????}
????public?void?out()?{
????????System.out.println("姓名?"?+?name?+?"?年齡?"?+?age);
????}
????public?static?void?main(String[]?args)?{
????????CopyValuePerson?p1?=?new?CopyValuePerson("沉默王二",18);
????????p1.out();
????????CopyValuePerson?p2?=?new?CopyValuePerson();
????????p2.name?=?p1.name;
????????p2.age?=?p1.age;
????????
????????p2.out();
????}
}
這種方式比較粗暴,直接拿 p1 的字段值復(fù)制給 p2 對(duì)象(p2.name = p1.name)。
3)通過(guò) Object 類的 clone() 方法
/**
?*?@author?沉默王二,一枚有趣的程序員
?*/
public?class?ClonePerson?implements?Cloneable?{
????private?String?name;
????private?int?age;
????public?ClonePerson(String?name,?int?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????@Override
????protected?Object?clone()?throws?CloneNotSupportedException?{
????????return?super.clone();
????}
????public?void?out()?{
????????System.out.println("姓名?"?+?name?+?"?年齡?"?+?age);
????}
????public?static?void?main(String[]?args)?throws?CloneNotSupportedException?{
????????ClonePerson?p1?=?new?ClonePerson("沉默王二",18);
????????p1.out();
????????ClonePerson?p2?=?(ClonePerson)?p1.clone();
????????p2.out();
????}
}
通過(guò) clone() 方法復(fù)制對(duì)象的時(shí)候,ClonePerson 必須先實(shí)現(xiàn) Cloneable 接口的 clone() 方法,然后再調(diào)用 clone() 方法(ClonePerson p2 = (ClonePerson) p1.clone())。
07、ending
“二哥,我能問(wèn)一些問(wèn)題嗎?”三妹精神煥發(fā),沒(méi)有絲毫的疲憊。
“當(dāng)然可以啊,你問(wèn)?!蔽液苄蕾p三妹孜孜不倦的態(tài)度。
“構(gòu)造方法真的不返回任何值嗎?”
“構(gòu)造方法雖然沒(méi)有返回值,但返回的是類的對(duì)象。”
“構(gòu)造方法只能完成字段初始化的工作嗎?”
“初始化字段只是構(gòu)造方法的一種工作,它還可以做更多,比如啟動(dòng)線程,調(diào)用其他方法等。”
“好的,二哥,我的問(wèn)題問(wèn)完了,今天的學(xué)習(xí)可以結(jié)束了!”三妹一臉得意的樣子。
“那你記得復(fù)習(xí)下一節(jié)的內(nèi)容哦?!备惺艿饺靡呀?jīng)學(xué)到了知識(shí),我也很欣慰。
強(qiáng)調(diào)一下,《教妹學(xué) Java》面向的是零基礎(chǔ)的 Java 學(xué)習(xí)者,我希望能幫助同學(xué)們輕松邁進(jìn)編程世界的大門,為后續(xù)的深入學(xué)習(xí)打下堅(jiān)實(shí)的基礎(chǔ)。
另外,我特意為大家創(chuàng)建了「技術(shù)交流群」,群里氛圍真的很不錯(cuò),希望能給需要的 同學(xué)再多一點(diǎn)幫助。掃描下方我的二維碼,回復(fù)「Java」我拉你入群。

示例代碼已經(jīng)同步到 GitHub,地址為 github.com/itwanger,也可以點(diǎn)擊閱讀原文進(jìn)行跳轉(zhuǎn),歡迎 star。
