看看我在Object 通用方法埋了什么坑
equals方法用過吧?看看這道題,說說看equals方法在前和在后有什么區(qū)別?

沒什么區(qū)別。
<u style="box-sizing: border-box;">錯,初學(xué)者或者代碼打得少的人都會犯這個錯。</u>
test1會直接報空指針異常,你想想看,null.equals看不起來不就怪怪的嗎?空指針怎么可能有方法呢是吧,
拓展:我們一般在企業(yè)開發(fā)中都會將已知的字面量放在equals,未知的參數(shù)放在equals后面,這樣可以避免空指針,編程新手容易犯這個異常,我review過的代碼這么實現(xiàn)的,說實話,挺多次的,不止編程新人,兩三年工作經(jīng)驗的都會這么做,實在不應(yīng)該,
說說看equlas 跟 == 的區(qū)別
equals方法比較的是字符串的內(nèi)容是否相等,而 == 比較的則是對象地址。
這么回答也是對的,但是,我們更希望聽到更專業(yè)的回答,比如:
首先Java中的數(shù)據(jù)類型可以分為兩種,一種是基本數(shù)據(jù)類型,也稱原始數(shù)據(jù)類型,如byte,short,char,int,long,float,double,boolean 他們之間的比較,應(yīng)用雙等號(==),比較的是他們的值。
另一種是復(fù)合數(shù)據(jù)類型,包括類,當(dāng)他們用(==)進行比較的時候,比較的是他們在內(nèi)存中的存放地址,所以,除非 是同一個new出來的對象,他們的比較后的結(jié)果為true,否則比較后結(jié)果為false。
而JAVA當(dāng)中所有的類都是繼承于Object這個基類的,在Object中的基類中定義了一個equals的方法,這個方法的初始行為是比較對象的內(nèi)存地址,但在一些類庫當(dāng)中這個方法被覆蓋掉了,如String,Integer,Date在這些類當(dāng)中equals有其自身的實現(xiàn),而不再是比較類在堆內(nèi)存中的存放地址了。
<u style="box-sizing: border-box;">這么回答顯的既專業(yè)又牛逼,還大氣上檔次,你學(xué)廢了嗎?</u>
聊聊對hashCode和equals方法的理解
一般問到這種問題,候選人都可以回答:
如果兩個對象equals方法相等,則它們的hashCode一定相同;
如果兩個對象的hashCode相同,它們的equals()方法則不一定相等。
而兩個對象的hashCode()返回值相等不能判斷這兩個對象是相等的,但兩個對象的hashcode()返回值不相等則可以判定兩個對象一定不相等。
<u style="box-sizing: border-box;">考察對hashCode的理解</u> 那請問hashCode有什么作用呢?為什么要這么規(guī)范
基本上初學(xué)者或者應(yīng)屆生在這一步就被我卡主了, 支支吾吾的回答不了多少,基本上都在這里掉分,因為上面的規(guī)范回答百度一查一大把,但是對hashCode作用有深入了解的卻很少,這道題也可以看出一個人的專業(yè)水平。
正確的回答是:
hashCode的作用實際上是為了提高在散列結(jié)構(gòu)存儲中查找的效率,自然只有每個對象的hashCode盡可能的不同才能保證散列存儲性能的提高,這也是為什么Object默認提供hash碼都是不同的原因。
舉個例子:
以HashSet為例,要想保證元素不重復(fù)則需要調(diào)用對象的equals方法比較一次,但是如果這個結(jié)構(gòu)放了很多元素,比如5000次,如果沒有hashCode的話則需要在每次加元素的時候?qū)Ρ?000次,而如果有hashCode則不一樣了,當(dāng)集合要添加新的元素的時候先調(diào)用hashCode方法,這樣便可以定位到元素的地址,而無需多次判斷。
<u style="box-sizing: border-box;">考察對clone方法的理解</u>看看這道題,結(jié)果輸出什么?

每次問到這道題,大部分人都是回答2,小部分人是回答1。
<u style="box-sizing: border-box;">都錯,正確答案是直接報錯</u>

為什么?因為clone方法是Object的protect方法,需要子類顯示的去重寫clone方法,并且實現(xiàn)Cloneable 接口,這是規(guī)定。
那么問題來了?加上Cloneable 接口后呢?到底是2還是1呢?
<u style="box-sizing: border-box;">這又是我挖的坑,主要為了考察候選人對淺拷貝和深拷貝的理解</u>
最終結(jié)果其實是2,因為直接用clone拷貝的時候是一種淺拷貝,最終拷貝對象和原始對象的引用類型引用還是同一個對象。
如果想要實現(xiàn)深拷貝,則需要開發(fā)人員自己在clone方法內(nèi)自己進行重寫。
拓展:在企業(yè)開發(fā)中一般我們不允許直接用clone方法來做拷貝,存在拋出異常的風(fēng)險,還需要進行類型轉(zhuǎn)換。如果是我review到這種代碼,都是直接告訴同事:在Effective Java 書上有講到,最好不要去使用 clone(),我們可以使用拷貝構(gòu)造函數(shù)或者拷貝工廠來拷貝一個對象,比如這樣

總結(jié):我們都知道Java是面向?qū)ο缶幊痰?,我的理解實際上就是面對Object編程,能夠合理的使用和了解原理equals和hashCode是一個初級程序員必須具備的素養(yǎng),而對淺拷貝和深拷貝的理解也基本是基礎(chǔ)中的基礎(chǔ),希望看完這系列,大家可以了然于胸,下次遇見這種面試題,直接吹。
看看我在抽象類和接口埋了什么坑
<u style="box-sizing: border-box;">考察候選人編碼水平</u> 說說看你對抽象類的理解
抽象類和抽象方法都使用 abstract 關(guān)鍵字進行聲明,如果一個類中包含抽象方法,那么這個類必須聲明為抽象類,并且抽象類和普通類最大的區(qū)別是,抽象類不能被實例化,只能被繼承。
<u style="box-sizing: border-box;">bingo,答案是對的,但是還不夠好</u>
上面的是標(biāo)準(zhǔn)答案,隨便一本Java書籍都可以看到,但是可以補充下你們對應(yīng)用的理解,這可以體現(xiàn)你的編碼水平到了哪一步,我這邊補充我理想的答案:
抽象類可以實現(xiàn)代碼的重用,模板方法設(shè)計模式是抽象類的一個典型應(yīng)用,例如我們游戲的所有活動都要進行一樣的初始化,但是初始化的時候需要根據(jù)不同活動進行不同功能開啟判斷,那么就可以定義一個活動的基類,將功能開啟判斷做成一個抽象方法,讓所有活動都繼承這個基類,然后在子類自行重寫功能開啟判斷。
補充上自己對應(yīng)用的理解,可以充分體現(xiàn)你的專業(yè),面試官聽完,肯定是內(nèi)心一頓尼瑪?shù)呐1啤?/p>
<u style="box-sizing: border-box;">繼續(xù)考察候選人編碼水平</u>說說看你對接口的理解
接口可以說成是抽象類的一種延伸,接口中的所有方法都必須是抽象的。實際上,接口中的方法定義默認為public abstract類型,接口中的成員變量類型默認為public static final。
<u style="box-sizing: border-box;">你們懂的,這又是一個標(biāo)準(zhǔn)回答,實際上,不夠好,可以補充以下幾句話</u>
從Java8開始,接口也可以擁有默認的方法實現(xiàn)了,為啥Java團隊要做這個支持呢?
實際上,在Java上深耕多年的老司機其實都被接口沒有默認實現(xiàn)搞過,維護成本太高了,你想想,如果你現(xiàn)在有100個類實現(xiàn)了一個接口,而在這個時候,需要給接口新增一個方法,特么Java8之前不允許給接口加默認實現(xiàn),所以你就得改100個類,這特么不得搞出人命嗎?
<u style="box-sizing: border-box;">真心話:能夠說出對抽象類和接口應(yīng)用的理解的候選人不多,基本上說出來的我內(nèi)心都是往牛逼去吹的,大寫的服,因為這充分體現(xiàn)一個開發(fā)人員對編碼方面的思考,你不知道一個寫代碼有思考和整潔,而不是只顧實現(xiàn)功能的程序員,主管會多喜歡?。?!</u>
<u style="box-sizing: border-box;">真正的直接背的答案</u>說說看抽象類和接口的區(qū)別
抽象類可以有構(gòu)造方法,接口中不能有構(gòu)造方法。
抽象類中可以有普通成員變量,接口中沒有普通成員變量
抽象類中可以包含非抽象的普通方法,接口中的所有方法必須都是抽象的,不能有非抽象的普通方法。
抽象類中可以包含靜態(tài)方法,接口中不能包含靜態(tài)方法
抽象類和接口中都可以包含靜態(tài)成員變量,抽象類中的靜態(tài)成員變量的訪問類型可以任意,但接口中定義的變量只能是public static final類型,并且默認即為public static final類型。
一個類可以實現(xiàn)多個接口,但只能繼承一個抽象類。
這個就是標(biāo)準(zhǔn)答案了,不過我們面試官比較希望可以在回答區(qū)別題的時候自己做些拓展回答,將上面接口和抽象類的理解都說一遍,而不是背書一樣的回答,<u style="box-sizing: border-box;">切記,回答問題,遇見自己懂的知識點,盡量做拓展,加上自己的理解,一是可以向面試官展示你的專業(yè),二是可以拖時間,盡量避免讓面試官想更多難題坑你</u>
總結(jié):看到這里,估計很多人對抽象類和接口都嗤之以鼻,都是隨口可以說出的答案,但是,你以為我們只是想要聽你背書式的回答嗎,錯了,我們想知道的是后面你的拓展和理解,這才可以體現(xiàn)你的編碼能力和專業(yè)。
看看我在重寫和重載里埋了什么坑
<u style="box-sizing: border-box;">給大家來一個筆試中超級常見的題</u> 看看以下這道題,說說看結(jié)果是啥?

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

<u style="box-sizing: border-box;">給大家說說如何理解這種題,理解以下的原則就可以了:</u>
JVM在調(diào)用一個方法時,會優(yōu)先從本類中查找看是否有對應(yīng)的方法,如果沒有再到父類中查看是否從父類繼承來。
如果沒有怎么辦?那JVM就會對參數(shù)進行轉(zhuǎn)型,轉(zhuǎn)成父類之后看是否有對應(yīng)的方法。
總的來說,方法調(diào)用的優(yōu)先級如下:
this.func(this)
super.func(this)
this.func(super)
super.func(super)
記住這個過程就可以了。
<u style="box-sizing: border-box;">開始挖坑</u> 說說看以下的是不是方法重載?
<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="Java" cid="n3925" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">class TestMain {
?
public int show(int x) {
return x;
}
?
public void show(int x) {
System.out.println(x);
}
}</pre>
我面試過的大部分初級或者應(yīng)屆都會回答是,不得不說這道題實在太坑了,不開玩笑,你看那堆被這道題埋過的人里邊就有我。
<u style="box-sizing: border-box;">千萬記住了,重載的定義是:在同一個類中,一個方法與已經(jīng)存在的方法名稱上相同,并且參數(shù)類型、個數(shù)、順序至少有一個不同。這句話里邊并沒有包含著返回值,如果只是返回值不同,其它都相同根本不算是重載。</u>
當(dāng)然,idea是會直接提示報錯的,這也是為什么我沒有截idea的源碼出來給大家看的原因,一看有爆紅你們就知道有問題了。不過還是那句話,這并不意味著idea可以檢測出來的東西,你就可以不懂,這是基礎(chǔ)。
總結(jié):說實話,在筆試題中出現(xiàn)重載和重寫那兩道題實在是太常見了,百分之九十的應(yīng)屆生或者初級開發(fā)都被坑過,所以看完這篇文章記得收藏啊,并不是你看了就記住了,而是記得在面試前重新看一遍,這可是關(guān)系到你offer上那個金光閃閃的數(shù)字的上限的啊
看看我在反射方面埋了什么坑
<u style="box-sizing: border-box;">終于到了反射了,我們面試官其實很喜歡這個知識點,可以用它來篩掉一大批的api工程師</u>
了解過反射嗎?說說看什么是反射?
在運行時,Java 反射,可以獲取任意類的名稱、package 信息、所有屬性、方法、注解、類型、類加載器、現(xiàn)實接口等,并且可以調(diào)用任意方法和實例化任意一個類的獨享,通過反射我們可以實現(xiàn)動態(tài)裝配,降低代碼的耦合度、實現(xiàn)動態(tài)代理等,不過反射的過度使用會嚴重消耗系統(tǒng)資源。
<u style="box-sizing: border-box;">上面的回答很標(biāo)準(zhǔn),不過開始深挖了</u> 你剛剛的回答有提到了反射的作用,說說哪里用到了反射機制?
例子很多,比如:
JDBC中,利用反射動態(tài)加載了數(shù)據(jù)庫驅(qū)動程序。
Web服務(wù)器中利用反射調(diào)用了Sevlet的服務(wù)方法。
還有多框架都用到反射機制,注入屬性,調(diào)用方法,如Spring等。
基本上回答幾個就可以了,這里因為還沒到框架模塊,所以先不對Spring、JDBC等源碼進行深挖,一般都是基礎(chǔ)題面完了,才開始挖對框架的使用和源碼的理解。
<u style="box-sizing: border-box;">繼續(xù)挖,我們面試官很喜歡抓細節(jié),所以最好保證你說的東西你都有一定的了解,否則嘿嘿嘿</u>你剛剛還說到了動態(tài)代理,說說你對動態(tài)代理的理解,以及有什么用
動態(tài)代理其實就是是運行時動態(tài)生成代理類。 動態(tài)代理的應(yīng)用有 Spring AOP、測試框架的后端 mock、rpc,Java注解對象獲取等應(yīng)用。
怎么實現(xiàn)動態(tài)代理呢?
目前動態(tài)代理可以提供兩種,分為JDK 原生動態(tài)代理和 CGLIB動態(tài)代理;
JDK 原生動態(tài)代理是基于接口實現(xiàn)的,而 CGLIB是基于繼承當(dāng)前類的子類實現(xiàn)的,當(dāng)目標(biāo)類有接口的時候才會使用JDK動態(tài)代理,其實是因為JDK動態(tài)代理無法代理一個沒有接口的類,因為JDK動態(tài)代理是利用反射機制生成一個實現(xiàn)代理接口的匿名類;
而CGLIB是針對類實現(xiàn)代理,主要是對指定的類生成一個子類,并且覆蓋其中的方法。
<u style="box-sizing: border-box;">挖一個大坑</u> 你上面有說到 Spring AOP有用到了動態(tài)代理,那你說說看AOP用到了哪種方式?
大部分都會回答JDK 原生動態(tài)代理,小部分人會回答CGLIB。
<u style="box-sizing: border-box;">但其實兩者都是錯的,這是我挖好的坑,太多候選人都只知道AOP用了動態(tài)代理了,但是能夠完整回答出來用了哪種的卻寥寥無幾,畢竟只有看過源碼的人才能回答這個問題,一般能夠回答出來的,我這邊都會額外加分</u>
實際答案是:在Spring中默認使用的是JDK動態(tài)代理,除非目標(biāo)類沒有實現(xiàn)接口,才會轉(zhuǎn)為CGLIB代理,如果想要強行使用CGLIB代理免責(zé)需要在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class="true" />,
然而在SpringBoot中,從2.0開始就默認使用CGLIB代理。**
說說看反射機制的優(yōu)缺點
優(yōu)點:可以動態(tài)執(zhí)行,在運行期間根據(jù)業(yè)務(wù)功能動態(tài)執(zhí)行方法、訪問屬性,最大限度發(fā)揮了java的靈活性。 缺點:對性能有影響,這類操作總是慢于直接執(zhí)行java代碼。
對性能有影響,那請問怎么解決這個問題?
到了這里基本上很多候選人都無法回答了,我見過太多一開始就說性能性能的,可是一說到如何解決,就基本支支吾吾的回答不出來。
<u style="box-sizing: border-box;">記住了,如果聊到性能,記得想好怎么回答優(yōu)化方面的問題,否則就是自己搬起石頭砸自己的腳。</u>
實際上可以從以下幾個方面回答:
在系統(tǒng)啟動時,將反射得到元數(shù)據(jù)保存起來,使用時,只需從內(nèi)存中調(diào)用即可。
盡量用高點的JDK,因為高版本的虛擬機會對執(zhí)行次數(shù)較多的方法進行優(yōu)化,例如使用jit技術(shù),而低版本的那個時候還沒實現(xiàn),
可以考慮使用高性能的反射庫,Spring內(nèi)部也有提供一些。
總結(jié):這里其實建議大家多去玩玩反射,不要只會api調(diào)用,對于我們面試官而言,熟悉和理解反射是一個中級程序員必備的條件。
看看我在異常和注解方面埋的坑
做為一個Javer,應(yīng)該對Error 和 Exception很熟悉吧,說說看它們的區(qū)別
目前來說,可以作為異常拋出的類,分為兩種: Error 和 Exception。
而其中 Error 用來表示 JVM 無法處理的錯誤,然后Exception 也分為兩種,分別是:
受檢異常 :需要用 try...catch... 語句捕獲并進行處理,并且可以從異常中恢復(fù);
非受檢異常 :是程序運行時錯誤,例如除 0 會引發(fā) Arithmetic Exception,此時程序崩潰并且無法恢復(fù)。
說說看什么是注解?以及有什么用
注解提供了一種類似注釋的機制,用來將任何的信息或數(shù)據(jù)與類、方法、或者成員變量等進行關(guān)聯(lián)。
Annontation像一種修飾符一樣,應(yīng)用于包、類型、構(gòu)造方法、方法、成員變量、參數(shù)及本地變量的聲明語句中。
注解的用處很多,舉兩個最常見的例子:
生成文檔。這是最常見的,也是java 最早提供的注解,系統(tǒng)會在掃描到使用了注解的類后,根據(jù)注解信息生成文檔。
在編譯時進行格式檢查。如@override 放在方法前,如果你這個方法并不是覆蓋了超類方法,則編譯時就能檢查出。
Java.lang.annotation 提供了四種元注解,是哪四種?有什么用呢?
JAVA 中有以下幾個『元注解』:
@Target:注解的作用目標(biāo)
@Retention:注解的生命周期
@Documented:注解是否應(yīng)當(dāng)被包含在 JavaDoc 文檔中
@Inherited:是否允許子類繼承該注解
沒錯,這幾個元注解太常見了,但是卻很少人能夠真的用的好。
說說看@Target作用目標(biāo)有哪些?
關(guān)于@Target的作用目標(biāo)有多個目標(biāo)類型,直接截圖如下:

基本上回答幾個就可以了,不過記住了,上面的TYPE就是指的是類,也就是說這個注解是作用在類上的。
@Retention注解的生命周期有哪幾種?有什么區(qū)別?
@Retention注解的生命周期可以分為以下幾種:
etentionPolicy.SOURCE:當(dāng)前注解編譯期可見,不會寫入 class 文件
RetentionPolicy.CLASS:類加載階段丟棄,會寫入 class 文件
RetentionPolicy.RUNTIME:永久保存,可以反射獲取
區(qū)別在于:
第一種是只能在編譯期可見,編譯后會被丟棄;
第二種會被編譯器編譯進 class 文件中,無論是類或是方法,乃至字段,他們都是有屬性表的,而 JAVA 虛擬機也定義了幾種注解屬性表用于存儲注解信息,但是這種可見性不能帶到方法區(qū),類加載時會予以丟棄;
第三種則是永久存在的可見性,可以反射獲取,基本上用這種居多。
<u style="box-sizing: border-box;">對于注解生命周器的理解可以避免一些坑,還是那句話,基本上注解這塊能回答的比較流暢的,說明都是玩過組件或者看過源碼的,因為對于開發(fā)一個組件來說,注解太常用了。</u>
<u style="box-sizing: border-box;">送命題,百分之九十的候選人答不上來</u> 父類使用了注解,請問子類繼承了這個父類后,是否也攜帶了這個注解呢?如果沒有,要怎么實現(xiàn)這個過程?
這道題很少人會回答的上來,畢竟大家對@Inherited這個元注解太陌生了。
<u style="box-sizing: border-box;">在注解上使用元注解@Inherited,表示該注解會被子類所繼承,注意注意,僅針對類喲,成員、方法并不受該元注解的影響</u>
因此,如果想要讓子類也繼承父類的注解,則需要給注解加上元注解@Inherited。
拓展:一般初級開發(fā)都基本不會用上注解,因為注解更多的是用來寫組件用的,我們項目組這邊就很喜歡自定義一些注解來實現(xiàn)組件,因為用起來實在是太舒服了。
總結(jié):關(guān)于異常這塊,Error 和 Exception是經(jīng)常會被問到的基礎(chǔ)考點,而注解這塊是一個能夠加分的點,希望大家別錯過這個知識點,學(xué)透它,不過記住上面幾個坑點,其實也差不多了。
最后
上一篇文章:https://mp.weixin.qq.com/s/F73r_f5YcBOayPdsin4gBg 點贊和閱讀量都很高,說明文章質(zhì)量是ok的,感謝大家的支持;
Java基礎(chǔ)系列也基本差不多了,說實話,這兩篇文章的坑點,只要你記住了,Java基礎(chǔ)這塊基本上沒問題了。
后續(xù)安排:
【好好面試】Java集合系列
【好好面試】Java崗位常考算法
Caffeine源碼解析
有興趣的關(guān)注我一波,Java面試官帶你們跨過一個個的面試坑,保證不虧。
為了感謝最近大家的支持,我這邊特地跳了一些Java相關(guān)的資源,很多專題,比如JAVA+TCP、Java反射機制、Java多線程專題等,都是針對性訓(xùn)練的利器,建議人手一份。


關(guān)注公眾號,回復(fù):Java面試 即可拿到資源。
公眾號:飯談編程 原文鏈接:https://mp.weixin.qq.com/s/F73r_f5YcBOayPdsin4gBg
謝謝點贊支持??????!