Java零基礎(chǔ)快速入門|對象的創(chuàng)建和使用(下)

本篇文章主要內(nèi)容:
構(gòu)造方法Constructor
空指針異常
當實例變量是一個引用
方法調(diào)用時參數(shù)的傳遞問題
難點解惑
?
構(gòu)造方法Constructor
什么是構(gòu)造方法?構(gòu)造方法怎么定義?構(gòu)造方法怎么調(diào)用?構(gòu)造方法有什么作用?構(gòu)造方法可以重載嗎?接下來學習一下。
構(gòu)造方法是類中特殊的方法,通過調(diào)用構(gòu)造方法來完成對象的創(chuàng)建,以及對象屬性的初始化操作。
構(gòu)造方法怎么定義,請看以下的語法格式:
[修飾符列表] 構(gòu)造方法名(形式參數(shù)列表){ 構(gòu)造方法體;
}
① 構(gòu)造方法名和類名一致。
② 構(gòu)造方法用來創(chuàng)建對象,以及完成屬性初始化操作。
③ 構(gòu)造方法返回值類型不需要寫,寫上就報錯,包括 void?也不能寫。
④ 構(gòu)造方法的返回值類型實際上是當前類的類型。
⑤ 一個類中可以定義多個構(gòu)造方法,這些構(gòu)造方法構(gòu)成方法重載。
怎么調(diào)用構(gòu)造方法呢,語法格式是:new 構(gòu)造方法名(實際參數(shù)列表);接下來,看以下代碼:

以上程序運行結(jié)果如下圖所示:

以上程序的輸出結(jié)果中雖然第一行看不懂,但起碼說明程序是能夠正常執(zhí)行的。我們看到以上 Date 類當中的代碼,并沒有發(fā)現(xiàn)任何構(gòu)造方法,為什么可以調(diào)用呢?接下來我們來測試一下,把構(gòu)造方法顯示的定義出來:


運行結(jié)果如下圖所示:

通過以上程序執(zhí)行結(jié)果確實看到了“new Date()”確實調(diào)用了Date 類當中的無參數(shù)構(gòu)造方法。再看以下程序:


?編譯報錯了,錯誤信息如下圖所示:

通過以上的測試,得出這樣一個結(jié)論(這是java??中語法的規(guī)定,記住就行):當一個類沒有顯示的定義任何構(gòu)造方法的時候,系統(tǒng)默認提供無參數(shù)構(gòu)造方法,當顯示的定義構(gòu)造方法之 后,系統(tǒng)則不再提供無參數(shù)構(gòu)造方法。無參數(shù)構(gòu)造方法又叫做缺省構(gòu)造器,或者默認構(gòu)造方法。一般在開發(fā)中為了方便編程,建議程序員手動的將無參數(shù)構(gòu)造方法寫上,因為不寫無參數(shù)構(gòu)造 方法的時候,這個默認的構(gòu)造方法很有可能就不存在了,另外也是因為無參數(shù)構(gòu)造方法使用的 頻率較高。例如以下代碼:


運行結(jié)果如下圖所示:

通過以上的測試可以看出一個類當中可以定義多個構(gòu)造方法,構(gòu)造方法是支持重載機制的,具體調(diào)用哪個構(gòu)造方法,那要看調(diào)用的時候傳遞的實際參數(shù)列表符合哪個構(gòu)造方法了。構(gòu)造方法雖然在返回值類型方面不寫任何類型,但它執(zhí)行結(jié)束之后實際上會返回該對象在堆內(nèi)存當中的內(nèi)存地址,這個時候可以定義變量接收對象的內(nèi)存地址,這個變量就是之前所學的“引用”,請看以下代碼:

運行結(jié)果如下圖所示:

以上程序中 time1,time2,time3,time4 都是引用,輸出這些引用的結(jié)果是“Date@xxxxx”, 對于這個結(jié)果目前可以把它等同看做是對象的內(nèi)存地址(嚴格來說不是真實的對象內(nèi)存地址)。通過這個引用就可以訪問對象的內(nèi)存了,例如以下代碼:

運行結(jié)果如下圖所示:

為什么無論通過哪個構(gòu)造方法創(chuàng)建Date 對象,最終的結(jié)果都是“0 年 0 月 0 日”呢?
這是因為所有的構(gòu)造方法在執(zhí)行過程中沒有給對象的屬性手動賦值,系統(tǒng)則自動賦默認值,實際上 大部分情況下我們需要在構(gòu)造方法中手動的給屬性賦值,這本來就是構(gòu)造方法的主要的職責, 要不然重載多次構(gòu)造方法就沒有意義了,以上的代碼應(yīng)該這樣寫,請看:


運行結(jié)果如下圖所示:

為什么第一個日期輸出的是“0 年 0 月 0 日”?這是因為調(diào)用的是無參數(shù)構(gòu)造方法創(chuàng)建的第一個日期對象,在無參數(shù)構(gòu)造方法中沒有給屬性賦值,則系統(tǒng)賦默認值,所以年月日都是0。
那為什么第二個日期輸出的是“2008 年 0 月 0 日”呢?這是因為調(diào)用的是“public Date(int year1){ year?= year1; }”構(gòu)造方法創(chuàng)建的第二個日期對象,在這個構(gòu)造方法當中只是顯示的給Date 對象的year 屬性賦值,month 和day 仍然是系統(tǒng)賦默認值,所以 month 和day 都是 0。第三個日期對象是“2008 年8 月8日”,這是因為在這個對應(yīng)的構(gòu)造方法中顯示的為year,month,day 三個屬性都賦值了。
在編寫以上構(gòu)造方法的時候需要注意變量名的問題,請看下圖:

通過以上內(nèi)容的學習得知,構(gòu)造方法的作用是專門用來創(chuàng)建對象同時給屬性賦值的,它的語法很簡單,比普通方法還要簡單,因為構(gòu)造方法名和類名一致,還不需要寫返回值類型,使用 new?就可以調(diào)用了。在一個類當中可以同時定義多個構(gòu)造方法,它們之間構(gòu)成重載關(guān)系。這樣就做到了在java 中你想要什么就new 什么,每一次new 都會在堆內(nèi)存中創(chuàng)建對象,并且對象內(nèi)部的實例變量被初始化了。
一定要注意,實例變量沒有手動賦值的時候系統(tǒng)會默認賦值,但不管是手動賦值還是系統(tǒng)賦默認值,都是在構(gòu)造方法執(zhí)行的時候才會進行賦值操作,類加載的時候并不會初始化實例變量的空間,那是因為實例變量是對象級別的變量,沒有對象,哪來實例變量,這也是為什么實例變量不能采用“類名”去訪問的原因。

編譯報錯了:

?
空指針異常
當一個空的引用去訪問實例變量會出現(xiàn)什么問題嗎?請看以下代碼:


運行結(jié)果如下圖所示:

java.lang.NullPointerException 被稱為空指針異常,在java 編程當中屬于很常見的異常,接下來研究一下以上程序執(zhí)行過程的內(nèi)存圖是如何變化的。請看下圖:


以上程序語法正確,編譯通過,因為程序在編譯階段檢測出“引用 ball”屬于 Balloon 類型,在Balloon 類中有color 屬性,所以編譯器允許通過 ball 引用去訪問color 屬性,例如以上代碼的ball.color。但是程序在運行階段會通過ball 引用查找堆內(nèi)存當中的對象,因為color 是實例變量,該變量存儲在 java 對象內(nèi)部,當 ball?= null 執(zhí)行之后表示“引用 ball”不再保存 java 對象的內(nèi)存地址,換句話說通過 ball 引用已經(jīng)無法找到堆內(nèi)存當中的 java 對象了,對于程序來說這個時候就沒有辦法正常訪問了,這種情況下就會發(fā)生空指針異常。就好比一個小孩兒放風箏,通過拽線來操控風箏,結(jié)果線斷了,再拽風箏線的時候,已經(jīng)無法再操控風箏了,這對于小孩兒來說是一種異常。而java 程序中把這種異常叫做NullPointerException。
總之,當一個“空的引用”去訪問“對象相關(guān)/實例相關(guān)”數(shù)據(jù)的時候,此時一定會發(fā)生空指針異常。
?
當實例變量是一個引用
在以上內(nèi)容學習的過程當中,其實大家已經(jīng)接觸過實例變量是引用的情況,不知道吧!例如在Student?學生類當中有一個屬性“String?name;”,這個屬性/實例變量name?描述的是學生的姓名,name?變量的數(shù)據(jù)類型是String?類型,String?類型不屬于基本數(shù)據(jù)類型的范疇,也就是說 String?類型屬于引用數(shù)據(jù)類型,換句話說 String?類型應(yīng)該對應(yīng)一個 String.class?文件才對, String?是一個類,和我們自己定義的類沒什么區(qū)別,是這樣嗎?一起來看看 JDK?的 java?源代碼:



通過查看源代碼得知,其實 String 是一個class,和我們定義的類沒有區(qū)別,它和基本數(shù)據(jù)類型還是不一樣的(int i?= 10,i 變量是基本數(shù)據(jù)類型,i 變量中存儲的就是10),也就是說String?name?= “zhangsan”,實際上 name 變量中存儲的并不是”zhangsan”這個字符串,因為 name 是一個引用,那 name??中必然存儲的是”zhangsan”字符串對象的內(nèi)存地址。
因為之前我們說過引用的概念,什么是引用:引用就是一個變量,只不過該變量中存儲的是 java???對象的內(nèi)存地址。也就是說,以前所講內(nèi)容中使用內(nèi)存圖描述字符串的時候都是有偏差的。我們來修正一下, 請看代碼:


以上程序的運行結(jié)果請看下圖:

將以上內(nèi)存結(jié)構(gòu)圖畫出來,請看:

通過上圖可以看到,Student 對象當中的name 這個“屬性/實例變量”是一個引用,保存的不是”zhangsan”字符串,而是字符串對象的內(nèi)存地址。(按照實際來說,字符串”zhangsan”是 在方法區(qū)的字符串常量池當中,這個后期再繼續(xù)進行修正)。接下來,我們再來看看當屬性是其他類型引用的時候,請看代碼:



運行結(jié)果如下圖所示:

以上程序main 方法的內(nèi)存結(jié)構(gòu)圖如下所示:


通過以上內(nèi)容的學習大家掌握當對象的屬性是一個引用的時候內(nèi)存圖是怎樣的了嗎?其 實只要記住一點,任何“引用”當中存儲一定是對象的內(nèi)存地址,“引用”不一定只是以局部 變量的形式存在,例如以上程序,其中 Vip 類當中的 birth 屬性就是一個“引用”,它是一個實例變量。當一個對象的屬性是引用的時候應(yīng)該如何訪問這個引用所指向的對象呢?這里其實 有一個規(guī)律,大家記住就行:類當中有什么就可以“.”什么,例如:Vip 類中有 birth 屬性, 那么就可以?v.birth,那么?v.birth?是Date?類型,Date?類當中有?year?屬性,那么就可以?v.birth.year,你懂了嗎?
?
方法調(diào)用時參數(shù)的傳遞問題(理解)
方法在調(diào)用的時候參數(shù)是如何傳遞的呢?其實在調(diào)用的時候參數(shù)傳遞給方法,這個過程就是賦值的過程,參數(shù)傳遞和“賦值規(guī)則”完全相同,只不過參數(shù)傳遞在代碼上看不見“=”運算符。我們先來深入的研究一下“賦值規(guī)則”吧!


在以上程序當中,有兩個疑問,第一個:a?賦值給?b,a?把什么給了?b?第二個:bird1?賦值給?bird2, bird1?把什么給了 bird2?
其實 a,b,bird1,bird2?就是 4?個普通的變量,唯一的區(qū)別只是 a?和 b?都是基本數(shù)據(jù)類型的變量,bird1?和 bird2?都是引用數(shù)據(jù)類型的變量(或者說都是引用),a?變量中保存的那個“值”是 10, bird1?變量中保存的那個“值”是 0x8888(java?對象內(nèi)存地址),本質(zhì)上來說 10?和 0x8888?都是“值”, 只不過一個“值”是整數(shù)數(shù)字,另一個“值”是 java?對象的內(nèi)存地址,大家不要把內(nèi)存地址特殊化, 它也是一個普通的值。
那么“賦值”是什么意思呢,顧名思義,賦值就是把“值”賦上去。a?賦值給 b,本質(zhì)上不是把 a 給了 b,而是把 a?變量中保存的“值 10”復制了一份給了 b。bird1?賦值給 bird2?本質(zhì)上不是把 bird1 給了 bird2,而是把 bird1?變量中保存的“值 0x8888”復制了一份給了 bird2。請看以下內(nèi)存圖的變化:


通過以上內(nèi)存圖我們可以看出“賦值”運算的時候?qū)嶋H上和變量的數(shù)據(jù)類型無關(guān),無論是 基本數(shù)據(jù)類型還是引用數(shù)據(jù)類型,一律都是將變量中保存的“值”復制一份,然后將復制的這 個“值”賦上去。他們的區(qū)別在于,如果是基本數(shù)據(jù)類型則和堆內(nèi)存當中的對象無關(guān),如果是 引用數(shù)據(jù)類型由于傳遞的這個值是 java?對象的內(nèi)存地址,所以會導致兩個引用指向同一個堆內(nèi)存中的java 對象,通過任何一個引用去訪問堆內(nèi)存當中的對象,此對象內(nèi)存都會受到影響。我們來驗證一下,讓a++,a 應(yīng)該變成了11,但是b 不會變,讓bird1.name = ?“波利”,然后輸出bird2.name 的結(jié)果肯定也是”波利”,請看代碼以及運行結(jié)果:


運行結(jié)果如下圖所示:

上面我就提到了,方法調(diào)用時參數(shù)的傳遞和賦值運算符的原理完全相同,那么請大家根據(jù)以上內(nèi)容所學,畫出以下程序的內(nèi)存圖,以及推算它們的執(zhí)行結(jié)果:



?
難點解惑
對于初學者來說,本章節(jié)內(nèi)容是比較艱難的一個章節(jié),主要是因為涉及到程序執(zhí)行過程中內(nèi)存的變化。本章節(jié)中要想搞明白所有的難點,只需要搞定一點就行,那就是親手畫出每個程序執(zhí)行時的內(nèi)存圖。
在這里我要提醒大家的有兩點:
先要記住Java?虛擬機內(nèi)存管理這塊有哪些內(nèi)存空間, 每一個空間中都存儲什么;
代碼要遵循一行一行逐行執(zhí)行,每執(zhí)行一行則需要在內(nèi)存方面 發(fā)生一些變化。只要大家能把握以上兩點,對于空指針異常、實例變量為引用類型、方法調(diào)用 時參數(shù)傳遞問題等迎刃而解。
小結(jié)
通過本章節(jié)內(nèi)容的學習,需要大家掌握的是一個類定義好之后,怎么創(chuàng)建對象,對象創(chuàng)建之后怎么去使用這個對象,代碼要會寫。另外還需要掌握構(gòu)造方法怎么定義、怎么調(diào)用。需要理解構(gòu)造方法在開發(fā)中的作用。
在本章節(jié)內(nèi)容中大家尤其要對?Java?虛擬機內(nèi)存管理這塊要有深入的理解。要知道Java?虛擬機中的棧和堆各自存儲什么,要能夠畫出程序執(zhí)行過程中內(nèi)存的變化。要知道空指針異常是如何發(fā)生的,為什么會出現(xiàn)空指針。
除以上所描述之外,大家還需要掌握方法調(diào)用時參數(shù)是如何傳遞的,一定要弄明白這個知識點,在以后的開發(fā)中會頻繁的進行方法的調(diào)用,而方法調(diào)用時需要傳遞參數(shù),參數(shù)傳遞時內(nèi)存是如何變化的,弄明白這些有助于你對程序運行結(jié)果的把控。
最后附Java零基礎(chǔ)視頻教程給大家,配合學習效果更佳?。?/strong>

