Head First Java學(xué)習(xí):第九章-構(gòu)造器和垃圾收集器
對象的前世今生
對象如何創(chuàng)建、存在何處以及如何讓保存和拋棄更有效率。
會述及堆、棧、范圍、構(gòu)造器、超級構(gòu)造器、空引用等。
1、內(nèi)存的兩個區(qū)域:堆和棧
堆(heap):對象的生存空間,又稱為可垃圾回收的堆
棧(stack):方法調(diào)用和局部變量。
2、變量的生存空間
- 實例變量:聲明在類中方法之外的地方,存在于所屬的對象中,因此保存在堆中。
- 局部變量:局部變量和方法的參數(shù)都聲明在方法中,是暫時的,生命周期僅限于方法被放在棧的這段時間(方法調(diào)用至執(zhí)行完畢),保存在棧中。
3、方法在棧中的存放順序
根據(jù)調(diào)用順序依次放在棧頂,最先釋放的在最上面。
4、對象的局部變量
非primitive變量只是保存對象的引用。
對象存在堆中,不論對象是否聲明或創(chuàng)建,如果局部變量是個對該對象的引用,只有變量本身會放在棧上。
對象本身存在堆上。
5、對象的實例變量:存放于對象所屬的堆空間中
需要多大的存放空間:
- 實例變量是 primitive主數(shù)據(jù)類型:
java會根據(jù)主數(shù)據(jù)類型的大小,在對象所屬的堆空間為該實例變量留下空間。如int需要32位,long需要64位。
變量所需要的空間在對象中。
- 實例變量是 對象的引用
對象帶有對象引用的變量:此時真正的問題是,是否要保留對象帶有的所有對象的空間?--不帶
舉例1:
public class CellPhone{
// 有聲明變量,未賦值;Antenna對象在堆上只會留下變量的空間
private Antenna ant;
}
舉例2:
public class CellPhone{
// 引用變量被賦值一個新的對象,新的Antenna對象在堆上占有堆空間
private Antenna ant = new Antenna();
}
結(jié)論:引用和對象都在堆中。
6、構(gòu)造函數(shù)
對象創(chuàng)建三部曲:聲明引用變量,創(chuàng)建對象,連接對象和引用。
Duck myDuck = new Duck();
其中,創(chuàng)建對象是在調(diào)用 Duck的構(gòu)造函數(shù)。
- 什么是構(gòu)造函數(shù):構(gòu)造函數(shù)帶有你在初始化對象時,會執(zhí)行的程序代碼。新建一個對象時就會被執(zhí)行。
- 如果沒有寫構(gòu)造函數(shù),編譯器會幫你寫一個:public Duck(){}
- 構(gòu)造函數(shù)的特點:無返回類型;與類同名
7、構(gòu)造Duck
代碼:
public class Duck{
public Duck(){
System.out.println("Quck!");
}
}
public class UseADuck {
public static void main(String[] args) {
Duck d = new Duck();
}
}
輸出:Quck!
總結(jié):構(gòu)造函數(shù)讓你有機(jī)會介入new的過程中。
8、新建Duck狀態(tài)的初始化
代碼:
public class Duck {
int size;
public Duck(){
System.out.println("Quack!");
}
public void setSize(int newSize){
size = newSize;
}
}
代碼:
public class UseADuck {
public static void main(String[] args) {
Duck d = new Duck();
d.setSize(45);
}
}
執(zhí)行結(jié)果:Quack!
總結(jié):
大部分人都是使用構(gòu)造函數(shù)來初始化對象的狀態(tài),即設(shè)置和給對象的實例變量賦值。在上面的代碼中,可以使用setSize()來設(shè)定大小,但這會讓Duck暫時處于沒有大小數(shù)值的狀態(tài)(實例變量沒有默認(rèn)值),且需要兩行搞定。
問題:先構(gòu)造對象再設(shè)置大小會很危險,萬一忘記設(shè)置會出問題。
9、使用構(gòu)造函數(shù)來初始化Duck的狀態(tài)
代碼:
public class Duck02 {
int size;
public Duck02(int duckSize){
System.out.println("Quack!");
// 把初始化的程序代碼放到構(gòu)造函數(shù)中,然后把構(gòu)造函數(shù)設(shè)定成需要參數(shù)的
size = duckSize;
System.out.println("size is "+size);
}
}
public class UseADuck02 {
public static void main(String[] args) {
// 只需要一行就可以創(chuàng)建出新的Duck并且設(shè)定好大小
Duck02 d = new Duck02(43);
}
}
結(jié)果:
Quack!
size is 43
總結(jié):給構(gòu)造函數(shù)加參數(shù),使用參數(shù)的值設(shè)定size的實例變量。只需要一行就可以創(chuàng)建出新的Duck并且設(shè)定好大小。
10、有參和無參構(gòu)造方法
讓用戶創(chuàng)建對象的時候有選擇。
代碼:
/**
* 重載構(gòu)造參數(shù)
*/
public class Duck03 {
int size;
// 無參構(gòu)造方法
public Duck03(){
size = 27;
System.out.println("size is "+ size);
}
// 有參構(gòu)造方法
public Duck03(int duckSize){
size = duckSize;
System.out.println("size is "+ size);
}
}
代碼:
public class useADuck03 {
public static void main(String[] args) {
// 調(diào)用無參構(gòu)造方法
Duck03 d1 = new Duck03();
System.out.println("-------------------");
// 調(diào)用有參構(gòu)造方法
Duck03 d2 = new Duck03(45);
}
}
結(jié)果:
size is 27
-------------------
size is 45
11、編譯器一定會幫你寫出沒有參數(shù)的構(gòu)造函數(shù)嗎?
- 完全沒有設(shè)定構(gòu)造函數(shù):編譯器幫你調(diào)用一個無參構(gòu)造函數(shù)
- 寫了有參構(gòu)造函數(shù):自己要寫上無參構(gòu)造函數(shù),編譯器不會調(diào)用
12、重載構(gòu)造函數(shù)
代碼:
/**
* 重載構(gòu)造參數(shù):
* 代表你有一個以上的構(gòu)造函數(shù)且參數(shù)都不相同
* 不能有相同的參數(shù)類型和順序
*/
public class Mushroom {
//要知道參數(shù)多大
public Mushroom(int size){}
// 不知道參數(shù)多大
public Mushroom(){}
// 知道是否有魔力,不知道多大
public Mushroom(boolean isMagic){}
// 知道是否有魔力,知道多大
public Mushroom(boolean isMagic,int size){}
// 和上面相同,但是參數(shù)順序不同所以過關(guān)
public Mushroom(int size,boolean isMagic){}
}
實例變量的默認(rèn)值:0/0.0/false;引用變量的默認(rèn)值:null
13、父類、繼承和構(gòu)造函數(shù)的關(guān)系
1) 實例變量
繼承下來的父類的實例變量也會保存在對象中。
創(chuàng)建某個對象時(new一個對象),對象會取得所有實例變量所需要的空間,包括繼承下來的實例變量的空間。
2) 父類的構(gòu)造函數(shù)
創(chuàng)建對象時,所有繼承下來的構(gòu)造函數(shù)都會執(zhí)行。
執(zhí)行new的指令,會啟動構(gòu)造函數(shù)連鎖反應(yīng)。
3) 構(gòu)造函數(shù)鏈:
Hippo對象IS-A Animal,Animal IS-A Object。如果你要創(chuàng)建出Hippo,也得創(chuàng)建出 Animal 與 Object的部分。
所以構(gòu)造函數(shù)在執(zhí)行的時候,第一件事情時去執(zhí)行它的父類的構(gòu)造函數(shù),這會連鎖反應(yīng)到Object這個類為止。
4) 調(diào)用過程舉例:
代碼:
public class Animal {
public Animal(){
System.out.println("Making an Anilmal");
}
}
public class Hippo extends Animal{
public Hippo(){
System.out.println("Making a Hippo");
}
}
public class TestHippo {
public static void main(String[] args) {
System.out.println("starting...");
Hippo h = new Hippo();
}
}
結(jié)果:
starting...
Making an Anilmal
Making a Hippo
說明:先調(diào)用父類的構(gòu)造函數(shù),再調(diào)用自身的構(gòu)造函數(shù)。
執(zhí)行過程如下:

5) 如何調(diào)用父類的構(gòu)造函數(shù):super()
唯一方法:super()
含義:調(diào)用其父類的無參構(gòu)造器。
代碼舉例:
public class Animal {
public Animal(){
System.out.println("Making an Anilmal");
}
}
public class Hippo02 extends Animal{
int size;
public Hippo02(int newSize){
// 調(diào)用父類的構(gòu)造函數(shù)
super();
size = newSize;
}
}
6) 如果沒有調(diào)用super() 會發(fā)生什么?
編譯器會幫我們加上super() 的調(diào)用
編譯器有兩種涉入構(gòu)造函數(shù)的方法:
第一種:沒有編寫構(gòu)造函數(shù)
編譯器會加super()及構(gòu)造函數(shù)。
public ClassName(){
super();
}
第二種:有構(gòu)造函數(shù)但是沒有調(diào)用super()
編譯器會幫你對每個重載版本的構(gòu)造函數(shù),加上這種調(diào)用:super().
編譯器幫忙加的一定是沒有參數(shù)的版本,即使父類有多個重載版本,也只有無參數(shù)的版本會被調(diào)用到。
7)對super()的調(diào)用必須是構(gòu)造函數(shù)的第一個語句。
8)有參數(shù)的父類構(gòu)造函數(shù):怎么傳參?

實例變量name私有的,不能被繼承。Hippo有g(shù)etName()方法但是沒有name實例變量,所以需要通過Animal維持name實例變量,然后從getName()來返回這個值。
代碼:
public abstract class Animal02 {
// 每個Animal02都有名字
private String name;
// Hippo03 會繼承這個getter
public String getName(){
return name;
}
// 有參數(shù)的構(gòu)造函數(shù),用來設(shè)定name
public Animal02(String theName){
name = theName;
}
}
public class Hippo03 extends Animal02{
public Hippo03(String name){
// 傳給Animal的構(gòu)造函數(shù)
super(name);
}
}
public class makeHippo {
public static void main(String[] args) {
Hippo03 h= new Hippo03("Buffy");
System.out.println(h.getName());
}
}
結(jié)果:
Buffy
總結(jié):
通過super()來引用父類,所以要從這里把name值都傳進(jìn)去,讓Animal把它存到私有的name 實例變量中。
13、this() 從構(gòu)造函數(shù)調(diào)用另一個重載版的另一個構(gòu)造函數(shù)
- 使用this() 來從某個構(gòu)造函數(shù)調(diào)用同一個類的另外一個構(gòu)造函數(shù)
- this()只能用在構(gòu)造函數(shù)中,且必須是第一行語句
- super() 與 this() 不可兼得
- this() 中的參數(shù),根據(jù)需要調(diào)用的構(gòu)造方法決定
舉例:
import java.awt.*;
public class Mini extends Car{
Color color;
public Mini(){// 無參數(shù)的構(gòu)造函數(shù)以默認(rèn)顏色調(diào)用真正的構(gòu)造函數(shù)
this(Color.RED);
}
public Mini(Color c){// 真正的構(gòu)造函數(shù)
super("Mini");
color = c;
// 初始化動作
}
public Mini(int size){// 有問題,不能同時調(diào)用super()和this()
this(Color.RED);
super(size);
}
}
14、對象會存活多久?
- 對象:生命周期看引用到它的“引用”。如果引用還活著,對象也會繼續(xù)活著;如果引用死了,對象也會跟著“陪葬”
- 引用變量:
實例變量:壽命和對象相同,對象活著,實例變量也活著。
局部變量:只存活在聲明該變量的方法中。
15、局部變量的生命期和作用域
life:只要變量的堆棧塊還存在于堆棧中上,局部變量就算活著,活到方法執(zhí)行完畢。
Scope:局部變量的范圍只限于聲明它的方法之內(nèi)。
局部變量在堆棧中,狀態(tài)保存;
局部變量所在方法在棧頂,才能被使用。
16、引用變量的生命期和作用域
1)變量的生命周期如何影響對象的生命周期
引用活著,對象活著。
當(dāng)對象的最后一個引用消失,對象就會變成可回收的。
2)釋放對象引用的三種方法

浙公網(wǎng)安備 33010602011771號