看看我在基礎(chǔ)數(shù)據(jù)類型方面埋了什么坑
說說看,Java有多少種基本的數(shù)據(jù)類型?多大?
Java有8中基本的數(shù)據(jù)類型,分別是
- byte,占據(jù)1個(gè)字節(jié)8位
- char,占據(jù)2個(gè)字節(jié)16位
- short,占據(jù)2個(gè)字節(jié)16位
- int,占據(jù)4個(gè)字節(jié)32位
- float,占據(jù)4個(gè)字節(jié)32位
- long,占據(jù)8個(gè)字節(jié)64位
- double,占據(jù)8個(gè)字節(jié)64位
- boolean,占據(jù)一個(gè)字節(jié)8位
<u>錯(cuò),將boolean默認(rèn)為一個(gè)字節(jié)基本是所有初學(xué)者的通。</u>
注意:boolean的大小是未知的,雖然我們看boolean只有:true、false兩種情況,可以使用 1 bit 來存儲(chǔ),但是實(shí)際上沒有明確規(guī)定是1bit,因?yàn)橐驗(yàn)閷?duì)虛擬機(jī)來說根本就不存在 boolean 這個(gè)類型。在《Java虛擬機(jī)規(guī)范》中給出了兩種定義,分別是4個(gè)字節(jié)和boolean數(shù)組時(shí)1個(gè)字節(jié)的定義,但是具體還要看虛擬機(jī)實(shí)現(xiàn)是否按照規(guī)范來,1個(gè)字節(jié)、4個(gè)字節(jié)都是有可能的。這其實(shí)是運(yùn)算效率和存儲(chǔ)空間之間的博弈,兩者都非常的重要。
那Integer這些算什么呢?
這些算是包裝類型,每個(gè)基本類型都有對(duì)應(yīng)的包裝類型,基本類型與其對(duì)應(yīng)的包裝類型之間的賦值使用自動(dòng)裝箱與拆箱完成。比如:
Integer number1 = 2; // 裝箱 調(diào)用了 Integer.valueOf(2)
int number2 = number1; // 拆箱 調(diào)用了 number1.intValue()
記得挺牢的,那 new Integer(1024) 和Integer.valueOf(1024) 有沒有什么區(qū)別呢?
首先,new Integer(1024) 每次都會(huì)新建一個(gè)對(duì)象,而Integer.valueOf(1024) 會(huì)使用緩沖池中的對(duì)象,多次調(diào)用會(huì)取得同一個(gè)對(duì)象的引用。
我舉個(gè)例子:

<u>錯(cuò),這是我埋著的坑點(diǎn),我曾經(jīng)用這一招坑了多個(gè)候選人。</u>

注意:Integer.valueOf(1024) 和Integer.valueOf(1024) 缺不等于true,而是false。Integer.valueOf從緩沖池取的數(shù)值是有大小限制的,并不是任何數(shù)
我們可以看看valueOf() 的源碼,其實(shí)也比較簡單,就是先判斷值是否在緩存池中,如果在的話就直接返回緩沖池的內(nèi)容。

目前我的jdk版本是 8 ,在jdk8中Integer 緩沖池的大小默認(rèn)為 -128~127。

<u>做為一個(gè)面試官,我很喜歡挖別人回答問題時(shí)暴露的細(xì)節(jié)點(diǎn)</u>。你剛剛說到了自動(dòng)裝箱和拆箱,說說看你的理解?
編譯器會(huì)在自動(dòng)裝箱過程中調(diào)用 valueOf() 方法,因此多個(gè)值相同且值在緩存池范圍內(nèi)的 Integer 實(shí)例使用自動(dòng)裝箱來創(chuàng)建,那么就會(huì)引用相同的對(duì)象,因此對(duì)比的時(shí)候會(huì)返回true。

<u>繼續(xù)往深挖,看看候選人對(duì)知識(shí)點(diǎn)的掌握有多深。</u>那說說看你知道的緩沖池有哪些?
目前基本類型對(duì)應(yīng)的緩沖池如下:
- boolean 緩沖池,true and false
- byte緩沖池
- short 緩沖池
- int 緩沖池
- char 緩沖池
因此我們?cè)谑褂眠@些基本類型對(duì)應(yīng)的包裝類型時(shí),如果該數(shù)值范圍在緩沖池范圍內(nèi),那么就可以直接使用緩沖池中的對(duì)象。
<u>繼續(xù)挖,看看他有沒有看過緩沖池的源碼。</u>你說的這些緩沖池的上限下限都是不變的嗎?還是說可以設(shè)定的?
基本上都是不可變的,不過在 jdk 1.8 中,Integer 的緩沖池 IntegerCache 很特殊,這個(gè)緩沖池的下界是 - 128,上界默認(rèn)是 127,但是這個(gè)上界是可調(diào)的。我們可以看源碼

在啟動(dòng) jvm 的時(shí)候,我們可以通過通過 -XX:AutoBoxCacheMax=<size> 來指定這個(gè)緩沖池的大小,在JVM初始化的時(shí)候,這個(gè)設(shè)置會(huì)設(shè)定一個(gè)名為 java.lang.IntegerCache.high 系統(tǒng)屬性,然后 IntegerCache 初始化的時(shí)候就會(huì)讀取該系統(tǒng)屬性來決定上界。
總結(jié):上面的坑分別有boolean的大小、緩沖池的大小、自動(dòng)拆箱和裝箱、緩沖池的大小是否可變,基本上這幾個(gè)坑點(diǎn)可以坑倒百分之六十的候選人,其次是做為一個(gè)面試官,我很喜歡挖候選人回答問題的細(xì)節(jié),畢竟深挖可以看得出你是不是真的有料?。?!
看看我在String方面埋了什么坑
你剛剛說了基本類型了,說說看你對(duì)String的了解吧
String 被聲明為 final,因此它不可被繼承。在 Java 8 中,String 內(nèi)部使用 char 數(shù)組存儲(chǔ)數(shù)據(jù),并且聲明為 final,這意味著 value 數(shù)組初始化之后就不能再引用其它數(shù)組,String 內(nèi)部也沒有改變 value 數(shù)組的方法,因此可以保證 String 不可變。
<u>繼續(xù)深挖</u> 說說看不可變的好處?
這個(gè)問題的回答比較泛,可以說的點(diǎn)比較多,大致可以分為:
首先是不可變自然意味著安全,當(dāng)String 作為參數(shù)引用的時(shí)候,不可變性可以保證參數(shù)不可變。
其次是可以緩存 hash 值,實(shí)際上,我們開發(fā)的時(shí)候經(jīng)常會(huì)用來當(dāng)做map的key,不可變的特性可以使得 hash 值也不可變,因此只需要進(jìn)行一次計(jì)算。
最后自然是String Pool 的需要,如果一個(gè) String 對(duì)象已經(jīng)被創(chuàng)建過了,那么就會(huì)從 String Pool 中取得引用,而自然只有 String 是不可變的,才可能使用 String Pool。如果是可變的,那么 String Pool也就無法被設(shè)計(jì)出來了。
<u>繼續(xù)深挖</u> 有沒有用過StringBuffer 和 StringBuilder,說說看String, StringBuffer 以及StringBuilder三者的區(qū)別?
首先他們都是被final修飾的類,都是不可被繼承,不過從可變性上來說,String 我們剛剛說到了,是不可變的,而StringBuffer 和 StringBuilder 可變的,這是內(nèi)部結(jié)構(gòu)導(dǎo)致的,StringBuffer 和StringBuilder 內(nèi)部放數(shù)據(jù)的數(shù)組沒有被final修飾。
其次從線程安全方面來說
String 不可變,是線程安全的
StringBuilder 不是線程安全的,因?yàn)閮?nèi)部并沒有使用任何的安全處理
StringBuffer 是線程安全的,內(nèi)部使用 synchronized 進(jìn)行同步
<u>繼續(xù)挖細(xì)節(jié)點(diǎn)</u>你剛剛有說到String Pool ,說說看你的理解
String Pool也就是我們經(jīng)常說的字符串常量池,它保存著所有字符串字面量,而且是在編譯時(shí)期就確定了。
String Pool是在編譯時(shí)期就確定了,那么請(qǐng)問是否不可變的呢?
是的。
<u>錯(cuò),所有初學(xué)者都會(huì)犯的一個(gè)問題,那就是忽略了String.intern的存在,我經(jīng)常用這個(gè)坑點(diǎn)來區(qū)分初學(xué)者和中級(jí)水平的候選人的區(qū)別!??!</u>

我們可以使用 String 的 intern() 方法在運(yùn)行過程將字符串添加到 String Pool 中。
我們可以看到

這是一個(gè)本地方法,看不到源碼,不過我們可以看到注釋
大致意思就是當(dāng)一個(gè)字符串調(diào)用 intern() 方法時(shí),如果 String Pool 中已經(jīng)存在一個(gè)字符串和該字符串值相等(使用 equals() 方法進(jìn)行確定),那么就會(huì)返回 String Pool 中字符串的引用;否則,就會(huì)在 String Pool 中添加一個(gè)新的字符串,并返回這個(gè)新字符串的引用。
用個(gè)demo來解釋這個(gè)流程

我上面的s1 和 s2 采用 new String() 的方式新建了兩個(gè)不同字符串,而 s3 和 s4 是通過 s1.intern() 和 s2.intern() 方法取得同一個(gè)字符串引用。第一個(gè)intern() 首先把 "飯談編程" 放到 String Pool 中,然后返回這個(gè)字符串引用,而第二個(gè)intern()則直接從String Pool 讀取了,因此 s3 和 s4 引用的是同一個(gè)字符串。
<u>繼續(xù)挖坑,準(zhǔn)備埋了候選人</u>剛剛說到 new String("飯談編程") != new String("飯談編程") ,那么 "飯談編程" 和 "飯談編程"相等嗎?說下流程?
是相等的,我們可以看到

流程是因?yàn)椋翰捎眠@種字面量的形式創(chuàng)建字符串,JVM會(huì)自動(dòng)地將字符串放入 String Pool 中,因此它們兩個(gè)是相等的。
<u>繼續(xù)往細(xì)節(jié)挖,這是一個(gè)比較刁鉆的問題</u> new String("飯談編程") JVM做了啥?
首先使用這種方式一共會(huì)創(chuàng)建兩個(gè)字符串對(duì)象,當(dāng)然了,前提是 String Pool 中還沒有 "飯談編程" 這個(gè)字符串對(duì)象,因此編譯時(shí)期會(huì)在 String Pool 中創(chuàng)建一個(gè)字符串對(duì)象,指向這個(gè) "飯談編程" 字符串字面量。
然后在使用 new 的方式的時(shí)候,在堆中創(chuàng)建一個(gè)字符串對(duì)象,這一步我們可以結(jié)合String的構(gòu)造函數(shù)來看看
public String(String original) {
this.value = original.value;
this.hash = original.hash;
}
可以看到,在將一個(gè)字符串對(duì)象作為另一個(gè)字符串對(duì)象的構(gòu)造函數(shù)參數(shù)時(shí),JVM會(huì)從String Pool 中將這個(gè)字符串對(duì)象取出來,當(dāng)做參數(shù)傳進(jìn)String的構(gòu)造函數(shù)中,將 value 數(shù)組和hash值賦予這個(gè)新的對(duì)象。
總結(jié):String我們?cè)谌粘i_發(fā)中經(jīng)常用到,不過一個(gè)合格的候選人應(yīng)該要吃透String、StringBuilder 和StringBuffer的區(qū)別,并且要對(duì)String Pool的原理了解的盡量多一些,不要被我上面挖的坑給埋了。
看看我在運(yùn)算方面埋了什么坑
請(qǐng)問在Java中方法參數(shù)的傳遞方式是引用傳遞呢?還是值傳遞呢?
這個(gè)要分情況,如果參數(shù)是基本類型的話,就是值傳遞,如果是引用類型的話,則是引用傳遞。
<u>錯(cuò),這是很多初學(xué)者容易搞錯(cuò)的地方,也是我日常挖坑埋人的地方</u>
Java 的參數(shù)全都是是以值傳遞的形式傳入方法中,而不是引用傳遞。如果參數(shù)是基本類型,則傳遞的是基本類型的字面量值的拷貝。而如果參數(shù)是引用類型的話,傳遞的則值該參數(shù)所引用的對(duì)象在堆中地址值的拷貝。
請(qǐng)看題 float f = 2.2,這么寫有沒有問題?
看起來是沒問題的,其實(shí)是有問題的,這個(gè)其實(shí)一般我不會(huì)用來面試,而是用來放在筆試題中。
2.2這個(gè)字面量屬于 double 類型的,因此不能直接將 2.2 直接賦值給 float 變量,因?yàn)檫@是向下轉(zhuǎn)型,記住Java 不能隱式執(zhí)行向下轉(zhuǎn)型,因?yàn)檫@會(huì)使得精度降低。
正常寫法是
float f = 2.2f;
<u>繼續(xù)挖坑</u>,那么float f = 2.2f; f += 2.2;可以嗎
這同樣是我會(huì)放進(jìn)筆試題考研候選人基礎(chǔ)的一道題,是可以的,因?yàn)槭褂?+= 或者 ++ 運(yùn)算符,JVM會(huì)執(zhí)行隱式類型轉(zhuǎn)換。
上面的語句相當(dāng)于將 s1 + 1 的計(jì)算結(jié)果進(jìn)行了向下轉(zhuǎn)型:
f = (float) (f + 2.2);
總結(jié):在運(yùn)算方面埋的坑比較基礎(chǔ),一般是放在面試題中,而且其實(shí)用idea開發(fā)的話實(shí)際上可以在開發(fā)期就會(huì)報(bào)錯(cuò)了,但是這并不意味著idea可以檢測出來的東西,你就可以不懂,特別是要來我司面試,這意味著你的專業(yè)能力是否過關(guān)。
看看我在修飾符方面埋了什么坑
說說看對(duì)修飾符final的理解
首先是在變量上使用了final,意味著聲明數(shù)據(jù)為常量,可以是編譯時(shí)常量,也可以是在運(yùn)行時(shí)被初始化后不能被改變的常量,作用可以分為:
- 對(duì)于基本類型,final 使數(shù)值不變;
- 對(duì)于引用類型,final 使引用不變,也就不能引用其它對(duì)象。
如果是在方法上使用了final,則聲明方法不能被子類重寫。
如果是在類上使用了final,則聲明方法不允許被繼承。
<u>開始挖坑了,等著你跳</u> 挺好的,按照你的說法,final int b = 1; b之后是不可以改的;那如果是這樣的例子,A對(duì)象的x可以改嗎

是可以改的,這也是引用類型的那一種,fianl是作用在A對(duì)象的引用上,而不是作用在A對(duì)象的數(shù)據(jù)成員x上,因此是可以改的。
<u>繼續(xù)挖坑</u> 剛剛你說到聲明方法不能被子類重寫,那么問題來了,為啥這樣可以

<u>一般候選人都會(huì)在這里支支吾吾的說不出個(gè)所以然來。</u>

其實(shí)他回答的理論是對(duì)的,只是他沒有實(shí)際上嘗試過我這種寫法。實(shí)際上在private 方法隱式地被指定為 final的時(shí)候,如果在子類中定義的方法和基類中的一個(gè) private 方法簽名相同,此時(shí)子類的方法并不是重寫了基類方法,而是在子類中定義了一個(gè)新的方法。
聊聊看你對(duì)修飾符static的了解
首先是用在變量上的話,這個(gè)變量我們一般稱之為靜態(tài)變量,也可以稱之為類變,也就是說這個(gè)變量屬于類的,類所有的實(shí)例都共享靜態(tài)變量,一般我們是直接通過類名來訪問它,需要注意的一點(diǎn)事,靜態(tài)變量在內(nèi)存中只存在一份。
而如果是用在方法上的話,就被稱之為靜態(tài)方法,這個(gè)靜態(tài)方法在類加載的時(shí)候就存在了,它不依賴于任何實(shí)例,因此靜態(tài)方法必須有實(shí)現(xiàn),也就是說它不能是抽象方法。
<u>開始挖坑</u> 可以在靜態(tài)方法內(nèi)使用this或者super關(guān)鍵字嗎
不可以的,只能訪問所屬類的靜態(tài)字段和靜態(tài)方法,方法中不能有 this 和 super 關(guān)鍵字,可以說static和this和super是互相矛盾的存在。
<u>坑點(diǎn)來了</u> 之前來了個(gè)實(shí)習(xí)生,寫代碼的時(shí)候就犯了這個(gè)錯(cuò),你看看下面的執(zhí)行結(jié)果是啥

正確答案是

這里記住一個(gè)點(diǎn)就可以了,靜態(tài)語句塊優(yōu)先于普通語句塊,而普通語句塊優(yōu)先于構(gòu)造函數(shù)。
<u>繼續(xù)深坑</u> 那么如果是有繼承關(guān)系在的時(shí)候呢?比如這道題,說說他們的執(zhí)行順序

大部分初級(jí)的候選人都會(huì)在這道題被絆倒,正確答案應(yīng)該是:

也就是說,存在繼承的情況下,初始化順序?yàn)椋?/p>
- 父類(靜態(tài)變量、靜態(tài)語句塊)
- 子類(靜態(tài)變量、靜態(tài)語句塊)
- 父類(實(shí)例變量、普通語句塊)
- 父類(構(gòu)造函數(shù))
- 子類(實(shí)例變量、普通語句塊)
- 子類(構(gòu)造函數(shù))
總結(jié):雖然看起來修飾符是一個(gè)比較小的東西,但是如果實(shí)際開發(fā)中采坑了,卻會(huì)造成比較大的風(fēng)險(xiǎn),比如執(zhí)行順序搞錯(cuò)了,而且也可以通過候選人對(duì)修飾符的了解情況,可以看出這個(gè)人實(shí)際的編程水平,這也是我們作為面試官想要迫切知道的地方。
最后
后續(xù)系列文章安排:
- 談?wù)勎以贠bject挖的坑
- 談?wù)勎以诩贤诘目?/li>
- 談?wù)勎以趎etty系列挖的坑…
你好,我是Java面試官飯談編程,我將會(huì)從面試官角度告訴你我面試候選人期間挖的坑。
好好面試系列將會(huì)分多篇文章進(jìn)行,基本上看完該系列的文章,Java基礎(chǔ)這塊便可以遇神殺神了,畢竟來來去去就這些,后續(xù)精彩請(qǐng)等待!
公眾號(hào):飯談編程
原文鏈接:https://mp.weixin.qq.com/s/F73r_f5YcBOayPdsin4gBg
謝謝點(diǎn)贊支持??????!