《好好面試》專欄,聊聊我這些年面別人的經(jīng)歷

去年面了多個(gè)候選人,深知java面試中埋了哪些坑,我用對(duì)話的形式記錄了這個(gè)過(guò)程,寫(xiě)了一個(gè)專欄《好好面試》,包括最合適的答案要給你了,看完這些,java基礎(chǔ)基本沒(méi)問(wèn)題了

看看我在基礎(chǔ)數(shù)據(jù)類型方面埋了什么坑

說(shuō)說(shuō)看,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 來(lái)存儲(chǔ),但是實(shí)際上沒(méi)有明確規(guī)定是1bit,因?yàn)橐驗(yàn)閷?duì)虛擬機(jī)來(lái)說(shuō)根本就不存在 boolean 這個(gè)類型。在《Java虛擬機(jī)規(guī)范》中給出了兩種定義,分別是4個(gè)字節(jié)和boolean數(shù)組時(shí)1個(gè)字節(jié)的定義,但是具體還要看虛擬機(jī)實(shí)現(xiàn)是否按照規(guī)范來(lái),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) 有沒(méi)有什么區(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í)也比較簡(jiǎn)單,就是先判斷值是否在緩存池中,如果在的話就直接返回緩沖池的內(nèi)容。

image-20210109100401281

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

image-20210109100503563

<u>做為一個(gè)面試官,我很喜歡挖別人回答問(wèn)題時(shí)暴露的細(xì)節(jié)點(diǎn)</u>。你剛剛說(shuō)到了自動(dòng)裝箱和拆箱,說(shuō)說(shuō)看你的理解?

編譯器會(huì)在自動(dòng)裝箱過(guò)程中調(diào)用 valueOf() 方法,因此多個(gè)值相同且值在緩存池范圍內(nèi)的 Integer 實(shí)例使用自動(dòng)裝箱來(lái)創(chuàng)建,那么就會(huì)引用相同的對(duì)象,因此對(duì)比的時(shí)候會(huì)返回true。

image-20210109101748474

<u>繼續(xù)往深挖,看看候選人對(duì)知識(shí)點(diǎn)的掌握有多深。</u>那說(shuō)說(shuō)看你知道的緩沖池有哪些?

目前基本類型對(duì)應(yīng)的緩沖池如下:

  • boolean 緩沖池,true and false
  • byte緩沖池
  • short 緩沖池
  • int 緩沖池
  • char 緩沖池

因此我們?cè)谑褂眠@些基本類型對(duì)應(yīng)的包裝類型時(shí),如果該數(shù)值范圍在緩沖池范圍內(nèi),那么就可以直接使用緩沖池中的對(duì)象。

<u>繼續(xù)挖,看看他有沒(méi)有看過(guò)緩沖池的源碼。</u>你說(shuō)的這些緩沖池的上限下限都是不變的嗎?還是說(shuō)可以設(shè)定的?

基本上都是不可變的,不過(guò)在 jdk 1.8 中,Integer 的緩沖池 IntegerCache 很特殊,這個(gè)緩沖池的下界是 - 128,上界默認(rèn)是 127,但是這個(gè)上界是可調(diào)的。我們可以看源碼

image-20210109102339939

在啟動(dòng) jvm 的時(shí)候,我們可以通過(guò)通過(guò) -XX:AutoBoxCacheMax=<size> 來(lái)指定這個(gè)緩沖池的大小,在JVM初始化的時(shí)候,這個(gè)設(shè)置會(huì)設(shè)定一個(gè)名為 java.lang.IntegerCache.high 系統(tǒng)屬性,然后 IntegerCache 初始化的時(shí)候就會(huì)讀取該系統(tǒng)屬性來(lái)決定上界。

總結(jié):上面的坑分別有boolean的大小、緩沖池的大小、自動(dòng)拆箱和裝箱、緩沖池的大小是否可變,基本上這幾個(gè)坑點(diǎn)可以坑倒百分之六十的候選人,其次是做為一個(gè)面試官,我很喜歡挖候選人回答問(wèn)題的細(xì)節(jié),畢竟深挖可以看得出你是不是真的有料!?。?/em>

看看我在String方面埋了什么坑

你剛剛說(shuō)了基本類型了,說(shuō)說(shuō)看你對(duì)String的了解吧

String 被聲明為 final,因此它不可被繼承。在 Java 8 中,String 內(nèi)部使用 char 數(shù)組存儲(chǔ)數(shù)據(jù),并且聲明為 final,這意味著 value 數(shù)組初始化之后就不能再引用其它數(shù)組,String 內(nèi)部也沒(méi)有改變 value 數(shù)組的方法,因此可以保證 String 不可變。

<u>繼續(xù)深挖</u> 說(shuō)說(shuō)看不可變的好處?

這個(gè)問(wèn)題的回答比較泛,可以說(shuō)的點(diǎn)比較多,大致可以分為:

  • 首先是不可變自然意味著安全,當(dāng)String 作為參數(shù)引用的時(shí)候,不可變性可以保證參數(shù)不可變。

  • 其次是可以緩存 hash 值,實(shí)際上,我們開(kāi)發(fā)的時(shí)候經(jīng)常會(huì)用來(lái)當(dāng)做map的key,不可變的特性可以使得 hash 值也不可變,因此只需要進(jìn)行一次計(jì)算。

  • 最后自然是String Pool 的需要,如果一個(gè) String 對(duì)象已經(jīng)被創(chuàng)建過(guò)了,那么就會(huì)從 String Pool 中取得引用,而自然只有 String 是不可變的,才可能使用 String Pool。如果是可變的,那么 String Pool也就無(wú)法被設(shè)計(jì)出來(lái)了。

<u>繼續(xù)深挖</u> 有沒(méi)有用過(guò)StringBuffer 和 StringBuilder,說(shuō)說(shuō)看String, StringBuffer 以及StringBuilder三者的區(qū)別?

首先他們都是被final修飾的類,都是不可被繼承,不過(guò)從可變性上來(lái)說(shuō),String 我們剛剛說(shuō)到了,是不可變的,而StringBuffer 和 StringBuilder 可變的,這是內(nèi)部結(jié)構(gòu)導(dǎo)致的,StringBuffer 和StringBuilder 內(nèi)部放數(shù)據(jù)的數(shù)組沒(méi)有被final修飾。

其次從線程安全方面來(lái)說(shuō)

  • String 不可變,是線程安全的

  • StringBuilder 不是線程安全的,因?yàn)閮?nèi)部并沒(méi)有使用任何的安全處理

  • StringBuffer 是線程安全的,內(nèi)部使用 synchronized 進(jìn)行同步

<u>繼續(xù)挖細(xì)節(jié)點(diǎn)</u>你剛剛有說(shuō)到String Pool ,說(shuō)說(shuō)看你的理解

String Pool也就是我們經(jīng)常說(shuō)的字符串常量池,它保存著所有字符串字面量,而且是在編譯時(shí)期就確定了。

String Pool是在編譯時(shí)期就確定了,那么請(qǐng)問(wèn)是否不可變的呢?

是的。

<u>錯(cuò),所有初學(xué)者都會(huì)犯的一個(gè)問(wèn)題,那就是忽略了String.intern的存在,我經(jīng)常用這個(gè)坑點(diǎn)來(lái)區(qū)分初學(xué)者和中級(jí)水平的候選人的區(qū)別!??!</u>

image-20210109174601615

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

我們可以看到

image-20210109111528457

這是一個(gè)本地方法,看不到源碼,不過(guò)我們可以看到注釋

大致意思就是當(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來(lái)解釋這個(gè)流程

image-20210109111744460

我上面的s1 和 s2 采用 new String() 的方式新建了兩個(gè)不同字符串,而 s3 和 s4 是通過(guò) s1.intern() 和 s2.intern() 方法取得同一個(gè)字符串引用。第一個(gè)intern() 首先把 "飯談編程" 放到 String Pool 中,然后返回這個(gè)字符串引用,而第二個(gè)intern()則直接從String Pool 讀取了,因此 s3 和 s4 引用的是同一個(gè)字符串。

<u>繼續(xù)挖坑,準(zhǔn)備埋了候選人</u>剛剛說(shuō)到 new String("飯談編程") != new String("飯談編程") ,那么 "飯談編程" 和 "飯談編程"相等嗎?說(shuō)下流程?

是相等的,我們可以看到

image-20210109112317572

流程是因?yàn)椋翰捎眠@種字面量的形式創(chuàng)建字符串,JVM會(huì)自動(dòng)地將字符串放入 String Pool 中,因此它們兩個(gè)是相等的。

<u>繼續(xù)往細(xì)節(jié)挖,這是一個(gè)比較刁鉆的問(wèn)題</u> new String("飯談編程") JVM做了啥?

首先使用這種方式一共會(huì)創(chuàng)建兩個(gè)字符串對(duì)象,當(dāng)然了,前提是 String Pool 中還沒(méi)有 "飯談編程" 這個(gè)字符串對(duì)象,因此編譯時(shí)期會(huì)在 String Pool 中創(chuàng)建一個(gè)字符串對(duì)象,指向這個(gè) "飯談編程" 字符串字面量。

然后在使用 new 的方式的時(shí)候,在堆中創(chuàng)建一個(gè)字符串對(duì)象,這一步我們可以結(jié)合String的構(gòu)造函數(shù)來(lái)看看

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ì)象取出來(lái),當(dāng)做參數(shù)傳進(jìn)String的構(gòu)造函數(shù)中,將 value 數(shù)組和hash值賦予這個(gè)新的對(duì)象。

總結(jié):String我們?cè)谌粘i_(kāi)發(fā)中經(jīng)常用到,不過(guò)一個(gè)合格的候選人應(yīng)該要吃透String、StringBuilder 和StringBuffer的區(qū)別,并且要對(duì)String Pool的原理了解的盡量多一些,不要被我上面挖的坑給埋了。

看看我在運(yùn)算方面埋了什么坑

請(qǐng)問(wèn)在Java中方法參數(shù)的傳遞方式是引用傳遞呢?還是值傳遞呢?

這個(gè)要分情況,如果參數(shù)是基本類型的話,就是值傳遞,如果是引用類型的話,則是引用傳遞。

<u>錯(cuò),這是很多初學(xué)者容易搞錯(cuò)的地方,也是我日常挖坑埋人的地方</u>

Java 的參數(shù)全都是是以值傳遞的形式傳入方法中,而不是引用傳遞。如果參數(shù)是基本類型,則傳遞的是基本類型的字面量值的拷貝。而如果參數(shù)是引用類型的話,傳遞的則值該參數(shù)所引用的對(duì)象在堆中地址值的拷貝。

請(qǐng)看題 float f = 2.2,這么寫(xiě)有沒(méi)有問(wèn)題?

看起來(lái)是沒(méi)問(wèn)題的,其實(shí)是有問(wèn)題的,這個(gè)其實(shí)一般我不會(huì)用來(lái)面試,而是用來(lái)放在筆試題中。

2.2這個(gè)字面量屬于 double 類型的,因此不能直接將 2.2 直接賦值給 float 變量,因?yàn)檫@是向下轉(zhuǎn)型,記住Java 不能隱式執(zhí)行向下轉(zhuǎn)型,因?yàn)檫@會(huì)使得精度降低。

正常寫(xiě)法是

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

上面的語(yǔ)句相當(dāng)于將 s1 + 1 的計(jì)算結(jié)果進(jìn)行了向下轉(zhuǎn)型:

f = (float) (f + 2.2);

總結(jié):在運(yùn)算方面埋的坑比較基礎(chǔ),一般是放在面試題中,而且其實(shí)用idea開(kāi)發(fā)的話實(shí)際上可以在開(kāi)發(fā)期就會(huì)報(bào)錯(cuò)了,但是這并不意味著idea可以檢測(cè)出來(lái)的東西,你就可以不懂,特別是要來(lái)我司面試,這意味著你的專業(yè)能力是否過(guò)關(guān)。

看看我在修飾符方面埋了什么坑

說(shuō)說(shuō)看對(duì)修飾符final的理解

首先是在變量上使用了final,意味著聲明數(shù)據(jù)為常量,可以是編譯時(shí)常量,也可以是在運(yùn)行時(shí)被初始化后不能被改變的常量,作用可以分為:

  • 對(duì)于基本類型,final 使數(shù)值不變;
  • 對(duì)于引用類型,final 使引用不變,也就不能引用其它對(duì)象。

如果是在方法上使用了final,則聲明方法不能被子類重寫(xiě)。

如果是在類上使用了final,則聲明方法不允許被繼承。

<u>開(kāi)始挖坑了,等著你跳</u> 挺好的,按照你的說(shuō)法,final int b = 1; b之后是不可以改的;那如果是這樣的例子,A對(duì)象的x可以改嗎

image-20210109123855200

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

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

image-20210109124438515

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

image-20210109174614608

其實(shí)他回答的理論是對(duì)的,只是他沒(méi)有實(shí)際上嘗試過(guò)我這種寫(xiě)法。實(shí)際上在private 方法隱式地被指定為 final的時(shí)候,如果在子類中定義的方法和基類中的一個(gè) private 方法簽名相同,此時(shí)子類的方法并不是重寫(xiě)了基類方法,而是在子類中定義了一個(gè)新的方法。

聊聊看你對(duì)修飾符static的了解

首先是用在變量上的話,這個(gè)變量我們一般稱之為靜態(tài)變量,也可以稱之為類變,也就是說(shuō)這個(gè)變量屬于類的,類所有的實(shí)例都共享靜態(tài)變量,一般我們是直接通過(guò)類名來(lái)訪問(wèn)它,需要注意的一點(diǎn)事,靜態(tài)變量在內(nèi)存中只存在一份。

而如果是用在方法上的話,就被稱之為靜態(tài)方法,這個(gè)靜態(tài)方法在類加載的時(shí)候就存在了,它不依賴于任何實(shí)例,因此靜態(tài)方法必須有實(shí)現(xiàn),也就是說(shuō)它不能是抽象方法。

<u>開(kāi)始挖坑</u> 可以在靜態(tài)方法內(nèi)使用this或者super關(guān)鍵字嗎

不可以的,只能訪問(wèn)所屬類的靜態(tài)字段和靜態(tài)方法,方法中不能有 this 和 super 關(guān)鍵字,可以說(shuō)static和this和super是互相矛盾的存在。

<u>坑點(diǎn)來(lái)了</u> 之前來(lái)了個(gè)實(shí)習(xí)生,寫(xiě)代碼的時(shí)候就犯了這個(gè)錯(cuò),你看看下面的執(zhí)行結(jié)果是啥

image-20210109125553717

正確答案是

image-20210109125704464

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

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

image-20210109130202218

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

image-20210109130258183

也就是說(shuō),存在繼承的情況下,初始化順序?yàn)椋?/p>

  • 父類(靜態(tài)變量、靜態(tài)語(yǔ)句塊)
  • 子類(靜態(tài)變量、靜態(tài)語(yǔ)句塊)
  • 父類(實(shí)例變量、普通語(yǔ)句塊)
  • 父類(構(gòu)造函數(shù))
  • 子類(實(shí)例變量、普通語(yǔ)句塊)
  • 子類(構(gòu)造函數(shù))

看看我在Object 通用方法埋了什么坑

equals方法用過(guò)吧?看看這道題,說(shuō)說(shuō)看equals方法在前和在后有什么區(qū)別?

image-20210110172344885

沒(méi)什么區(qū)別。

<u>錯(cuò),初學(xué)者或者代碼打得少的人都會(huì)犯這個(gè)錯(cuò)。</u>

test1會(huì)直接報(bào)空指針異常,你想想看,null.equals看不起來(lái)不就怪怪的嗎?空指針怎么可能有方法呢是吧,

拓展:我們一般在企業(yè)開(kāi)發(fā)中都會(huì)將已知的字面量放在equals,未知的參數(shù)放在equals后面,這樣可以避免空指針,編程新手容易犯這個(gè)異常,我review過(guò)的代碼這么實(shí)現(xiàn)的,說(shuō)實(shí)話,挺多次的,不止編程新人,兩三年工作經(jīng)驗(yàn)的都會(huì)這么做,實(shí)在不應(yīng)該,

說(shuō)說(shuō)看equlas 跟 == 的區(qū)別

equals方法比較的是字符串的內(nèi)容是否相等,而 == 比較的則是對(duì)象地址。

這么回答也是對(duì)的,但是,我們更希望聽(tīng)到更專業(yè)的回答,比如:

首先Java中的數(shù)據(jù)類型可以分為兩種,一種是基本數(shù)據(jù)類型,也稱原始數(shù)據(jù)類型,如byte,short,char,int,long,float,double,boolean
他們之間的比較,應(yīng)用雙等號(hào)(==),比較的是他們的值。

另一種是復(fù)合數(shù)據(jù)類型,包括類,當(dāng)他們用(==)進(jìn)行比較的時(shí)候,比較的是他們?cè)趦?nèi)存中的存放地址,所以,除非 是同一個(gè)new出來(lái)的對(duì)象,他們的比較后的結(jié)果為true,否則比較后結(jié)果為false。

而JAVA當(dāng)中所有的類都是繼承于Object這個(gè)基類的,在Object中的基類中定義了一個(gè)equals的方法,這個(gè)方法的初始行為是比較對(duì)象的內(nèi)存地址,但在一些類庫(kù)當(dāng)中這個(gè)方法被覆蓋掉了,如String,Integer,Date在這些類當(dāng)中equals有其自身的實(shí)現(xiàn),而不再是比較類在堆內(nèi)存中的存放地址了。

<u>這么回答顯的既專業(yè)又牛逼,還大氣上檔次,你學(xué)廢了嗎?</u>

聊聊對(duì)hashCode和equals方法的理解

一般問(wèn)到這種問(wèn)題,候選人都可以回答:

如果兩個(gè)對(duì)象equals方法相等,則它們的hashCode一定相同;

如果兩個(gè)對(duì)象的hashCode相同,它們的equals()方法則不一定相等。

而兩個(gè)對(duì)象的hashCode()返回值相等不能判斷這兩個(gè)對(duì)象是相等的,但兩個(gè)對(duì)象的hashcode()返回值不相等則可以判定兩個(gè)對(duì)象一定不相等。

<u>考察對(duì)hashCode的理解</u> 那請(qǐng)問(wèn)hashCode有什么作用呢?為什么要這么規(guī)范

基本上初學(xué)者或者應(yīng)屆生在這一步就被我卡主了, 支支吾吾的回答不了多少,基本上都在這里掉分,因?yàn)樯厦娴囊?guī)范回答百度一查一大把,但是對(duì)hashCode作用有深入了解的卻很少,這道題也可以看出一個(gè)人的專業(yè)水平。

正確的回答是:

hashCode的作用實(shí)際上是為了提高在散列結(jié)構(gòu)存儲(chǔ)中查找的效率,自然只有每個(gè)對(duì)象的hashCode盡可能的不同才能保證散列存儲(chǔ)性能的提高,這也是為什么Object默認(rèn)提供hash碼都是不同的原因。

舉個(gè)例子:

以HashSet為例,要想保證元素不重復(fù)則需要調(diào)用對(duì)象的equals方法比較一次,但是如果這個(gè)結(jié)構(gòu)放了很多元素,比如5000次,如果沒(méi)有hashCode的話則需要在每次加元素的時(shí)候?qū)Ρ?000次,而如果有hashCode則不一樣了,當(dāng)集合要添加新的元素的時(shí)候先調(diào)用hashCode方法,這樣便可以定位到元素的地址,而無(wú)需多次判斷。

<u>考察對(duì)clone方法的理解</u>看看這道題,結(jié)果輸出什么?

image-20210110183459155

每次問(wèn)到這道題,大部分人都是回答2,小部分人是回答1。

<u>都錯(cuò),正確答案是直接報(bào)錯(cuò)</u>

image-20210110183635113

為什么?因?yàn)閏lone方法是Object的protect方法,需要子類顯示的去重寫(xiě)clone方法,并且實(shí)現(xiàn)Cloneable 接口,這是規(guī)定。

那么問(wèn)題來(lái)了?加上Cloneable 接口后呢?到底是2還是1呢?

<u>這又是我挖的坑,主要為了考察候選人對(duì)淺拷貝和深拷貝的理解</u>

最終結(jié)果其實(shí)是2,因?yàn)橹苯佑胏lone拷貝的時(shí)候是一種淺拷貝,最終拷貝對(duì)象和原始對(duì)象的引用類型引用還是同一個(gè)對(duì)象。

如果想要實(shí)現(xiàn)深拷貝,則需要開(kāi)發(fā)人員自己在clone方法內(nèi)自己進(jìn)行重寫(xiě)。

拓展:在企業(yè)開(kāi)發(fā)中一般我們不允許直接用clone方法來(lái)做拷貝,存在拋出異常的風(fēng)險(xiǎn),還需要進(jìn)行類型轉(zhuǎn)換。如果是我review到這種代碼,都是直接告訴同事:在Effective Java 書(shū)上有講到,最好不要去使用 clone(),我們可以使用拷貝構(gòu)造函數(shù)或者拷貝工廠來(lái)拷貝一個(gè)對(duì)象,比如這樣

image-20210111224630388

總結(jié):我們都知道Java是面向?qū)ο缶幊痰?,我的理解?shí)際上就是面對(duì)Object編程,能夠合理的使用和了解原理equals和hashCode是一個(gè)初級(jí)程序員必須具備的素養(yǎng),而對(duì)淺拷貝和深拷貝的理解也基本是基礎(chǔ)中的基礎(chǔ),希望看完這系列,大家可以了然于胸,下次遇見(jiàn)這種面試題,直接吹。

看看我在抽象類和接口埋了什么坑

<u>考察候選人編碼水平</u> 說(shuō)說(shuō)看你對(duì)抽象類的理解

抽象類和抽象方法都使用 abstract 關(guān)鍵字進(jìn)行聲明,如果一個(gè)類中包含抽象方法,那么這個(gè)類必須聲明為抽象類,并且抽象類和普通類最大的區(qū)別是,抽象類不能被實(shí)例化,只能被繼承。

<u>bingo,答案是對(duì)的,但是還不夠好</u>

上面的是標(biāo)準(zhǔn)答案,隨便一本Java書(shū)籍都可以看到,但是可以補(bǔ)充下你們對(duì)應(yīng)用的理解,這可以體現(xiàn)你的編碼水平到了哪一步,我這邊補(bǔ)充我理想的答案:

抽象類可以實(shí)現(xiàn)代碼的重用,模板方法設(shè)計(jì)模式是抽象類的一個(gè)典型應(yīng)用,例如我們游戲的所有活動(dòng)都要進(jìn)行一樣的初始化,但是初始化的時(shí)候需要根據(jù)不同活動(dòng)進(jìn)行不同功能開(kāi)啟判斷,那么就可以定義一個(gè)活動(dòng)的基類,將功能開(kāi)啟判斷做成一個(gè)抽象方法,讓所有活動(dòng)都繼承這個(gè)基類,然后在子類自行重寫(xiě)功能開(kāi)啟判斷。

補(bǔ)充上自己對(duì)應(yīng)用的理解,可以充分體現(xiàn)你的專業(yè),面試官聽(tīng)完,肯定是內(nèi)心一頓尼瑪?shù)呐1啤?/p>

<u>繼續(xù)考察候選人編碼水平</u>說(shuō)說(shuō)看你對(duì)接口的理解

接口可以說(shuō)成是抽象類的一種延伸,接口中的所有方法都必須是抽象的。實(shí)際上,接口中的方法定義默認(rèn)為public abstract類型,接口中的成員變量類型默認(rèn)為public static final。

<u>你們懂的,這又是一個(gè)標(biāo)準(zhǔn)回答,實(shí)際上,不夠好,可以補(bǔ)充以下幾句話</u>

從Java8開(kāi)始,接口也可以擁有默認(rèn)的方法實(shí)現(xiàn)了,為啥Java團(tuán)隊(duì)要做這個(gè)支持呢?

實(shí)際上,在Java上深耕多年的老司機(jī)其實(shí)都被接口沒(méi)有默認(rèn)實(shí)現(xiàn)搞過(guò),維護(hù)成本太高了,你想想,如果你現(xiàn)在有100個(gè)類實(shí)現(xiàn)了一個(gè)接口,而在這個(gè)時(shí)候,需要給接口新增一個(gè)方法,特么Java8之前不允許給接口加默認(rèn)實(shí)現(xiàn),所以你就得改100個(gè)類,這特么不得搞出人命嗎?

<u>真心話:能夠說(shuō)出對(duì)抽象類和接口應(yīng)用的理解的候選人不多,基本上說(shuō)出來(lái)的我內(nèi)心都是往牛逼去吹的,大寫(xiě)的服,因?yàn)檫@充分體現(xiàn)一個(gè)開(kāi)發(fā)人員對(duì)編碼方面的思考,你不知道一個(gè)寫(xiě)代碼有思考和整潔,而不是只顧實(shí)現(xiàn)功能的程序員,主管會(huì)多喜歡?。。?lt;/u>

<u>真正的直接背的答案</u>說(shuō)說(shuō)看抽象類和接口的區(qū)別

  • 抽象類可以有構(gòu)造方法,接口中不能有構(gòu)造方法。

  • 抽象類中可以有普通成員變量,接口中沒(méi)有普通成員變量

  • 抽象類中可以包含非抽象的普通方法,接口中的所有方法必須都是抽象的,不能有非抽象的普通方法。

  • 抽象類中可以包含靜態(tài)方法,接口中不能包含靜態(tài)方法

  • 抽象類和接口中都可以包含靜態(tài)成員變量,抽象類中的靜態(tài)成員變量的訪問(wèn)類型可以任意,但接口中定義的變量只能是public static final類型,并且默認(rèn)即為public static final類型。

  • 一個(gè)類可以實(shí)現(xiàn)多個(gè)接口,但只能繼承一個(gè)抽象類。

這個(gè)就是標(biāo)準(zhǔn)答案了,不過(guò)我們面試官比較希望可以在回答區(qū)別題的時(shí)候自己做些拓展回答,將上面接口和抽象類的理解都說(shuō)一遍,而不是背書(shū)一樣的回答,<u>切記,回答問(wèn)題,遇見(jiàn)自己懂的知識(shí)點(diǎn),盡量做拓展,加上自己的理解,一是可以向面試官展示你的專業(yè),二是可以拖時(shí)間,盡量避免讓面試官想更多難題坑你</u>

總結(jié):看到這里,估計(jì)很多人對(duì)抽象類和接口都嗤之以鼻,都是隨口可以說(shuō)出的答案,但是,你以為我們只是想要聽(tīng)你背書(shū)式的回答嗎,錯(cuò)了,我們想知道的是后面你的拓展和理解,這才可以體現(xiàn)你的編碼能力和專業(yè)。

看看我在重寫(xiě)和重載里埋了什么坑

<u>給大家來(lái)一個(gè)筆試中超級(jí)常見(jiàn)的題</u> 看看以下這道題,說(shuō)說(shuō)看結(jié)果是啥?

image-20210111235033082

這道題簡(jiǎn)直就是了,很多年前我來(lái)我司應(yīng)聘的時(shí)候也遇見(jiàn)過(guò)這道題,說(shuō)實(shí)話,應(yīng)屆生和初級(jí)繞不過(guò)這道題,現(xiàn)在直接給大家亮牌:

image-20210111235450101

<u>給大家說(shuō)說(shuō)如何理解這種題,理解以下的原則就可以了:</u>

JVM在調(diào)用一個(gè)方法時(shí),會(huì)優(yōu)先從本類中查找看是否有對(duì)應(yīng)的方法,如果沒(méi)有再到父類中查看是否從父類繼承來(lái)。

如果沒(méi)有怎么辦?那JVM就會(huì)對(duì)參數(shù)進(jìn)行轉(zhuǎn)型,轉(zhuǎn)成父類之后看是否有對(duì)應(yīng)的方法。

總的來(lái)說(shuō),方法調(diào)用的優(yōu)先級(jí)如下:

  • this.func(this)
  • super.func(this)
  • this.func(super)
  • super.func(super)

記住這個(gè)過(guò)程就可以了。

<u>開(kāi)始挖坑</u> 說(shuō)說(shuō)看以下的是不是方法重載?

class TestMain {

    public int show(int x) {
        return x;
    }

    public void show(int x) {
        System.out.println(x);
    }
}

我面試過(guò)的大部分初級(jí)或者應(yīng)屆都會(huì)回答是,不得不說(shuō)這道題實(shí)在太坑了,不開(kāi)玩笑,你看那堆被這道題埋過(guò)的人里邊就有我。

<u>千萬(wàn)記住了,重載的定義是:在同一個(gè)類中,一個(gè)方法與已經(jīng)存在的方法名稱上相同,并且參數(shù)類型、個(gè)數(shù)、順序至少有一個(gè)不同。這句話里邊并沒(méi)有包含著返回值,如果只是返回值不同,其它都相同根本不算是重載。</u>

當(dāng)然,idea是會(huì)直接提示報(bào)錯(cuò)的,這也是為什么我沒(méi)有截idea的源碼出來(lái)給大家看的原因,一看有爆紅你們就知道有問(wèn)題了。不過(guò)還是那句話,這并不意味著idea可以檢測(cè)出來(lái)的東西,你就可以不懂,這是基礎(chǔ)。

總結(jié):說(shuō)實(shí)話,在筆試題中出現(xiàn)重載和重寫(xiě)那兩道題實(shí)在是太常見(jiàn)了,百分之九十的應(yīng)屆生或者初級(jí)開(kāi)發(fā)都被坑過(guò),所以看完這篇文章記得收藏啊,并不是你看了就記住了,而是記得在面試前重新看一遍,這可是關(guān)系到你offer上那個(gè)金光閃閃的數(shù)字的上限的啊

看看我在反射方面埋了什么坑

<u>終于到了反射了,我們面試官其實(shí)很喜歡這個(gè)知識(shí)點(diǎn),可以用它來(lái)篩掉一大批的api工程師</u>

了解過(guò)反射嗎?說(shuō)說(shuō)看什么是反射?

在運(yùn)行時(shí),Java 反射,可以獲取任意類的名稱、package 信息、所有屬性、方法、注解、類型、類加載器、現(xiàn)實(shí)接口等,并且可以調(diào)用任意方法和實(shí)例化任意一個(gè)類的獨(dú)享,通過(guò)反射我們可以實(shí)現(xiàn)動(dòng)態(tài)裝配,降低代碼的耦合度、實(shí)現(xiàn)動(dòng)態(tài)代理等,不過(guò)反射的過(guò)度使用會(huì)嚴(yán)重消耗系統(tǒng)資源。

<u>上面的回答很標(biāo)準(zhǔn),不過(guò)開(kāi)始深挖了</u> 你剛剛的回答有提到了反射的作用,說(shuō)說(shuō)哪里用到了反射機(jī)制?

例子很多,比如:

  • JDBC中,利用反射動(dòng)態(tài)加載了數(shù)據(jù)庫(kù)驅(qū)動(dòng)程序。
  • Web服務(wù)器中利用反射調(diào)用了Sevlet的服務(wù)方法。
  • 還有多框架都用到反射機(jī)制,注入屬性,調(diào)用方法,如Spring等。

基本上回答幾個(gè)就可以了,這里因?yàn)檫€沒(méi)到框架模塊,所以先不對(duì)Spring、JDBC等源碼進(jìn)行深挖,一般都是基礎(chǔ)題面完了,才開(kāi)始挖對(duì)框架的使用和源碼的理解。

<u>繼續(xù)挖,我們面試官很喜歡抓細(xì)節(jié),所以最好保證你說(shuō)的東西你都有一定的了解,否則嘿嘿嘿</u>你剛剛還說(shuō)到了動(dòng)態(tài)代理,說(shuō)說(shuō)你對(duì)動(dòng)態(tài)代理的理解,以及有什么用

動(dòng)態(tài)代理其實(shí)就是是運(yùn)行時(shí)動(dòng)態(tài)生成代理類。
動(dòng)態(tài)代理的應(yīng)用有 Spring AOP、測(cè)試框架的后端 mock、rpc,Java注解對(duì)象獲取等應(yīng)用。

怎么實(shí)現(xiàn)動(dòng)態(tài)代理呢?

目前動(dòng)態(tài)代理可以提供兩種,分為JDK 原生動(dòng)態(tài)代理和 CGLIB動(dòng)態(tài)代理;

JDK 原生動(dòng)態(tài)代理是基于接口實(shí)現(xiàn)的,而 CGLIB是基于繼承當(dāng)前類的子類實(shí)現(xiàn)的,當(dāng)目標(biāo)類有接口的時(shí)候才會(huì)使用JDK動(dòng)態(tài)代理,其實(shí)是因?yàn)镴DK動(dòng)態(tài)代理無(wú)法代理一個(gè)沒(méi)有接口的類,因?yàn)镴DK動(dòng)態(tài)代理是利用反射機(jī)制生成一個(gè)實(shí)現(xiàn)代理接口的匿名類;

而CGLIB是針對(duì)類實(shí)現(xiàn)代理,主要是對(duì)指定的類生成一個(gè)子類,并且覆蓋其中的方法。

<u>挖一個(gè)大坑</u> 你上面有說(shuō)到 Spring AOP有用到了動(dòng)態(tài)代理,那你說(shuō)說(shuō)看AOP用到了哪種方式?

大部分都會(huì)回答JDK 原生動(dòng)態(tài)代理,小部分人會(huì)回答CGLIB。

<u>但其實(shí)兩者都是錯(cuò)的,這是我挖好的坑,太多候選人都只知道AOP用了動(dòng)態(tài)代理了,但是能夠完整回答出來(lái)用了哪種的卻寥寥無(wú)幾,畢竟只有看過(guò)源碼的人才能回答這個(gè)問(wèn)題,一般能夠回答出來(lái)的,我這邊都會(huì)額外加分</u>

實(shí)際答案是:在Spring中默認(rèn)使用的是JDK動(dòng)態(tài)代理,除非目標(biāo)類沒(méi)有實(shí)現(xiàn)接口,才會(huì)轉(zhuǎn)為CGLIB代理,如果想要強(qiáng)行使用CGLIB代理免責(zé)需要在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true" />,

然而在SpringBoot中,從2.0開(kāi)始就默認(rèn)使用CGLIB代理。**

說(shuō)說(shuō)看反射機(jī)制的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):可以動(dòng)態(tài)執(zhí)行,在運(yùn)行期間根據(jù)業(yè)務(wù)功能動(dòng)態(tài)執(zhí)行方法、訪問(wèn)屬性,最大限度發(fā)揮了java的靈活性。
缺點(diǎn):對(duì)性能有影響,這類操作總是慢于直接執(zhí)行java代碼。

對(duì)性能有影響,那請(qǐng)問(wèn)怎么解決這個(gè)問(wèn)題?

到了這里基本上很多候選人都無(wú)法回答了,我見(jiàn)過(guò)太多一開(kāi)始就說(shuō)性能性能的,可是一說(shuō)到如何解決,就基本支支吾吾的回答不出來(lái)。

<u>記住了,如果聊到性能,記得想好怎么回答優(yōu)化方面的問(wèn)題,否則就是自己搬起石頭砸自己的腳。</u>

實(shí)際上可以從以下幾個(gè)方面回答:

  • 在系統(tǒng)啟動(dòng)時(shí),將反射得到元數(shù)據(jù)保存起來(lái),使用時(shí),只需從內(nèi)存中調(diào)用即可。

  • 盡量用高點(diǎn)的JDK,因?yàn)楦甙姹镜奶摂M機(jī)會(huì)對(duì)執(zhí)行次數(shù)較多的方法進(jìn)行優(yōu)化,例如使用jit技術(shù),而低版本的那個(gè)時(shí)候還沒(méi)實(shí)現(xiàn),

  • 可以考慮使用高性能的反射庫(kù),Spring內(nèi)部也有提供一些。

總結(jié):這里其實(shí)建議大家多去玩玩反射,不要只會(huì)api調(diào)用,對(duì)于我們面試官而言,熟悉和理解反射是一個(gè)中級(jí)程序員必備的條件。

看看我在異常和注解方面埋的坑

做為一個(gè)Javer,應(yīng)該對(duì)Error 和 Exception很熟悉吧,說(shuō)說(shuō)看它們的區(qū)別

目前來(lái)說(shuō),可以作為異常拋出的類,分為兩種: Error 和 Exception。

而其中 Error 用來(lái)表示 JVM 無(wú)法處理的錯(cuò)誤,然后Exception 也分為兩種,分別是:

  • 受檢異常 :需要用 try...catch... 語(yǔ)句捕獲并進(jìn)行處理,并且可以從異常中恢復(fù);
  • 非受檢異常 :是程序運(yùn)行時(shí)錯(cuò)誤,例如除 0 會(huì)引發(fā) Arithmetic Exception,此時(shí)程序崩潰并且無(wú)法恢復(fù)。

說(shuō)說(shuō)看什么是注解?以及有什么用

注解提供了一種類似注釋的機(jī)制,用來(lái)將任何的信息或數(shù)據(jù)與類、方法、或者成員變量等進(jìn)行關(guān)聯(lián)。

Annontation像一種修飾符一樣,應(yīng)用于包、類型、構(gòu)造方法、方法、成員變量、參數(shù)及本地變量的聲明語(yǔ)句中。

注解的用處很多,舉兩個(gè)最常見(jiàn)的例子:

  • 生成文檔。這是最常見(jiàn)的,也是java 最早提供的注解,系統(tǒng)會(huì)在掃描到使用了注解的類后,根據(jù)注解信息生成文檔。
  • 在編譯時(shí)進(jìn)行格式檢查。如@override 放在方法前,如果你這個(gè)方法并不是覆蓋了超類方法,則編譯時(shí)就能檢查出。

Java.lang.annotation 提供了四種元注解,是哪四種?有什么用呢?

JAVA 中有以下幾個(gè)『元注解』:

  • @Target:注解的作用目標(biāo)
  • @Retention:注解的生命周期
  • @Documented:注解是否應(yīng)當(dāng)被包含在 JavaDoc 文檔中
  • @Inherited:是否允許子類繼承該注解

沒(méi)錯(cuò),這幾個(gè)元注解太常見(jiàn)了,但是卻很少人能夠真的用的好。

說(shuō)說(shuō)看@Target作用目標(biāo)有哪些?

關(guān)于@Target的作用目標(biāo)有多個(gè)目標(biāo)類型,直接截圖如下:

image-20210112230657929

基本上回答幾個(gè)就可以了,不過(guò)記住了,上面的TYPE就是指的是類,也就是說(shuō)這個(gè)注解是作用在類上的。

@Retention注解的生命周期有哪幾種?有什么區(qū)別?

@Retention注解的生命周期可以分為以下幾種:

  • etentionPolicy.SOURCE:當(dāng)前注解編譯期可見(jiàn),不會(huì)寫(xiě)入 class 文件
  • RetentionPolicy.CLASS:類加載階段丟棄,會(huì)寫(xiě)入 class 文件
  • RetentionPolicy.RUNTIME:永久保存,可以反射獲取

區(qū)別在于:

第一種是只能在編譯期可見(jiàn),編譯后會(huì)被丟棄;

第二種會(huì)被編譯器編譯進(jìn) class 文件中,無(wú)論是類或是方法,乃至字段,他們都是有屬性表的,而 JAVA 虛擬機(jī)也定義了幾種注解屬性表用于存儲(chǔ)注解信息,但是這種可見(jiàn)性不能帶到方法區(qū),類加載時(shí)會(huì)予以丟棄;

第三種則是永久存在的可見(jiàn)性,可以反射獲取,基本上用這種居多。

<u>對(duì)于注解生命周器的理解可以避免一些坑,還是那句話,基本上注解這塊能回答的比較流暢的,說(shuō)明都是玩過(guò)組件或者看過(guò)源碼的,因?yàn)閷?duì)于開(kāi)發(fā)一個(gè)組件來(lái)說(shuō),注解太常用了。</u>

<u>送命題,百分之九十的候選人答不上來(lái)</u> 父類使用了注解,請(qǐng)問(wèn)子類繼承了這個(gè)父類后,是否也攜帶了這個(gè)注解呢?如果沒(méi)有,要怎么實(shí)現(xiàn)這個(gè)過(guò)程?

這道題很少人會(huì)回答的上來(lái),畢竟大家對(duì)@Inherited這個(gè)元注解太陌生了。

<u>在注解上使用元注解@Inherited,表示該注解會(huì)被子類所繼承,注意注意,僅針對(duì)類喲,成員、方法并不受該元注解的影響</u>

因此,如果想要讓子類也繼承父類的注解,則需要給注解加上元注解@Inherited。

拓展:一般初級(jí)開(kāi)發(fā)都基本不會(huì)用上注解,因?yàn)樽⒔飧嗟氖怯脕?lái)寫(xiě)組件用的,我們項(xiàng)目組這邊就很喜歡自定義一些注解來(lái)實(shí)現(xiàn)組件,因?yàn)橛闷饋?lái)實(shí)在是太舒服了。

java基礎(chǔ)的基本差不多了,后續(xù)還有

連引用都答不上,憑什么說(shuō)你是Java服務(wù)端開(kāi)發(fā)

開(kāi)發(fā)必學(xué),io&nio

list中for循環(huán)刪除多個(gè)元素為何報(bào)錯(cuò)?

面試官告訴你什么是JMM和??济嬖囶}

https://mp.weixin.qq.com/s/z7yBgd3qqecCAyXwOmwJDQ

高級(jí)開(kāi)發(fā)竟然被構(gòu)造器循環(huán)依賴難住了?

第一次面試,我差點(diǎn)被面試官打,就因?yàn)镃ollections.sort

還有Spring系列

專欄路徑

都給你了,麻煩點(diǎn)個(gè)贊,祝你找到面試順利。

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 本文轉(zhuǎn)自Java 最常見(jiàn) 200+ 面試題全解析:面試必備(轉(zhuǎn)載) 序言 在本篇文章開(kāi)始之前,我想先來(lái)回答一個(gè)問(wèn)題...
    林偉成閱讀 1,238評(píng)論 0 3
  • 前言: 在遨游了一番 Java Web 的世界之后,發(fā)現(xiàn)了自己的一些缺失,所以就著一篇深度好文:知名互聯(lián)網(wǎng)公司校招...
    我沒(méi)有三顆心臟閱讀 4,552評(píng)論 0 23
  • 表情是什么,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了,難過(guò)就哭了。兩者是相互影響密不可...
    Persistenc_6aea閱讀 129,427評(píng)論 2 7
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者,不喜歡去冒險(xiǎn),但是人生放棄了冒險(xiǎn),也就放棄了無(wú)數(shù)的可能。 ...
    yichen大刀閱讀 7,562評(píng)論 0 4

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