去年面了多個(gè)候選人,看看我挖的坑還有他們應(yīng)該要補(bǔ)的Java基礎(chǔ)(一)

看看我在基礎(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è)例子:

image-20210109100030722

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

image-20210109174541713

注意:Integer.valueOf(1024) 和Integer.valueOf(1024) 缺不等于true,而是false。Integer.valueOf從緩沖池取的數(shù)值是有大小限制的,并不是任何數(shù)

我們可以看看valueOf() 的源碼,其實(shí)也比較簡單,就是先判斷值是否在緩存池中,如果在的話就直接返回緩沖池的內(nèi)容。

image-20210109100401281

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

image-20210109100503563

<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。

image-20210109101748474

<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)的。我們可以看源碼

image-20210109102339939

在啟動(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>

image-20210109174601615

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

我們可以看到

image-20210109111528457

這是一個(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è)流程

image-20210109111744460

我上面的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("飯談編程") ,那么 "飯談編程" 和 "飯談編程"相等嗎?說下流程?

是相等的,我們可以看到

image-20210109112317572

流程是因?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可以改嗎

image-20210109123855200

是可以改的,這也是引用類型的那一種,fianl是作用在A對(duì)象的引用上,而不是作用在A對(duì)象的數(shù)據(jù)成員x上,因此是可以改的。

<u>繼續(xù)挖坑</u> 剛剛你說到聲明方法不能被子類重寫,那么問題來了,為啥這樣可以

image-20210109124438515

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

image-20210109174614608

其實(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é)果是啥

image-20210109125553717

正確答案是

image-20210109125704464

這里記住一個(gè)點(diǎn)就可以了,靜態(tài)語句塊優(yōu)先于普通語句塊,而普通語句塊優(yōu)先于構(gòu)造函數(shù)。

<u>繼續(xù)深坑</u> 那么如果是有繼承關(guān)系在的時(shí)候呢?比如這道題,說說他們的執(zhí)行順序

image-20210109130202218

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

image-20210109130258183

也就是說,存在繼承的情況下,初始化順序?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)贊支持??????!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、數(shù)據(jù)類型基本類型包裝類型緩存池 二、String概覽不可變的好處String, StringBuffer an...
    Juntech閱讀 333評(píng)論 0 0
  • (ps:該文章是本人在其他網(wǎng)站的面試題中看到并且收集下來的) 一、數(shù)據(jù)類型 1.基本類型 byte/8 char/...
    義無反顧00閱讀 212評(píng)論 0 0
  • 一、數(shù)據(jù)類型基本類型包裝類型緩存池 二、String概覽不可變的好處String, StringBuffer an...
    魔都云濤閱讀 224評(píng)論 0 0
  • 前言equals() 和 hashCode() 都是 Object 對(duì)象中的非 final 方法,它們?cè)O(shè)計(jì)的目的就...
    sortinnauto閱讀 312評(píng)論 0 1
  • 漸變的面目拼圖要我怎么拼? 我是疲乏了還是投降了? 不是不允許自己墜落, 我沒有滴水不進(jìn)的保護(hù)膜。 就是害怕變得面...
    悶熱當(dāng)乘涼閱讀 4,471評(píng)論 0 13

友情鏈接更多精彩內(nèi)容