Java 基礎(chǔ)面試題總結(jié) - 草稿

點贊關(guān)注蚤假,不再迷路,你的支持對我意義重大吊说!

?? Hi论咏,我是丑丑。本文 「Java 路線」導(dǎo)讀 —— 他山之石颁井,可以攻玉 已收錄厅贪,這里有 Android 進(jìn)階成長路線筆記 & 博客,歡迎跟著彭丑丑一起成長雅宾。(聯(lián)系方式在 GitHub)


前言

面試季又來了养涮,Java 基礎(chǔ)知識又可以拿出來復(fù)習(xí)了~ “基礎(chǔ)不牢,地動山搖”,這些內(nèi)容不難贯吓,但必須要會懈凹,一起加油吧。


1. 誤區(qū)

Java 是解釋型語言還是編譯型語言悄谐?

所謂 “某個語言是編譯型 / 解釋型” 是個偽命題介评,編譯型和解釋型并不是一門編程語言的特性,而是語言實現(xiàn)的特性尊沸。我們經(jīng)常會在文章或教科書上看到 “C 是編譯型語言威沫,因為 C 是編譯執(zhí)行的;Java 是解釋型語言洼专,因為 Java 是 JVM 解釋執(zhí)行的”棒掠,這些都是不準(zhǔn)確的。更準(zhǔn)確的說法是:“某個語言的特定實現(xiàn)是編譯型還是解釋型”屁商。

舉個例子烟很,在早期的 Android Dalvik 虛擬機(jī)只有解釋器,效率低蜡镶。為了優(yōu)化運行效率雾袱,Android 2.2 引入 JIT 即時編譯器,可以在運行時探測熱點代碼進(jìn)行編譯執(zhí)行官还。后續(xù)的 Android 5.0 ART 虛擬機(jī)又推出了 AOT 提前編譯芹橡,Android 7/0 ART 又引入了解釋、JIT 和 AOT 混合的執(zhí)行方式望伦×炙担可以看到,同一段代碼既可以解釋執(zhí)行屯伞,也可以編譯執(zhí)行腿箩,這取決于語言的實現(xiàn)而不是語言本身。

相關(guān)深入文章:《Android 虛擬機(jī) | 從類加載到程序執(zhí)行》


C 語言是面向過程語言劣摇,Java 是面向?qū)ο缶幊陶Z言珠移?

所謂 “某種語言是面向過程 / 面向?qū)ο?/ 函數(shù)式” 是一個偽命題。經(jīng)常會在文章或教科書上看到 “C 是面向過程語言末融,Java 是面向?qū)ο笳Z言钧惧,C++ 是面向?qū)ο笳Z言”,這種分類方法是沒有意義的勾习。舉個例子垢乙,使用 Java 可以使用寫出函數(shù)式風(fēng)格的代碼,也可以寫出面向?qū)ο箫L(fēng)格的代碼语卤。因此,這種分類方式對于編程沒有意義。

更科學(xué)的思考方式是把編程語言理解為一個個特性的組合 語言 = {特性1, 特性2, ..., 特性 N}粹舵,學(xué)習(xí)一門語言應(yīng)該把語言打散為一個個特性钮孵,把每個特性都理解透徹。那么眼滤,當(dāng)你遇到一門新的語言巴席,雖然語法有所不同,但是很多特性是相似的诅需,這樣就只需要專注于兩門語言差集的特性漾唉,學(xué)習(xí)效率就高了。


Java 是靜態(tài)類型檢查還是動態(tài)類型檢查堰塌?

靜態(tài) / 動態(tài)類型語言的區(qū)分赵刑,關(guān)鍵在于類型檢查是否 (傾向于) 編譯時執(zhí)行。例如场刑, Java & C/C++ 是靜態(tài)類型檢查般此,而 JavaScript 是動態(tài)類型檢查。靜態(tài)類型檢查的優(yōu)點是可以在編譯期提前檢查出可能出現(xiàn)的 bug牵现。需要注意的是铐懊,這個定義并不是絕對的,例如 Java 也存在運行時類型檢查的方式瞎疼,例如上面提到的 checkcast 指令本質(zhì)上是在運行時檢查變量的類型與對象的類型是否相同科乎。


2. 類 & 對象

為什么 Java 匿名內(nèi)部類調(diào)用的局部變量需要聲明 final?

局部變量的作用域是從變量定義到代碼塊結(jié)束贼急,在匿名內(nèi)部類訪問局部變量茅茂,其實是超出了局部變量的作用域。為了擴(kuò)大變量的作用域竿裂,編譯后的字節(jié)碼實現(xiàn)是將局部變量的值通過構(gòu)造函數(shù)傳遞到內(nèi)部類內(nèi)部玉吁,存儲在成員變量。這就意味著局部變量的值存在兩份拷貝腻异,又因為 Java 賦值是值傳遞进副,所以當(dāng)任何一份拷貝修改時,另外一份拷貝是無感知的悔常,這會引起語義上的混亂影斑。因此,為了保證數(shù)據(jù)一致性机打,Java 規(guī)定匿名內(nèi)部類訪問的局部變量需要聲明為 final矫户。

有沒有辦法不使用 final 呢?也是有的残邀,那就是使用一層數(shù)組或者對象包裝皆辽,這正是 Kotlin lambda 表達(dá)式可以直接訪問局部變量的原理柑蛇。


== 和 equals() 有什么區(qū)別?為什么重寫 equals() 必須重寫 hashCode()驱闷?


這三者都帶有 相等 的含義耻台,但它們在表示相等的層面又各不相同:

1、== 表示值相等空另,對于基礎(chǔ)數(shù)據(jù)類型是值相等盆耽,對于引用類型是對象地址相等,本質(zhì)上也是值相等扼菠;

2摄杂、equals() 表示內(nèi)容相等,equals() 是 Object 的成員方法循榆,所以基本數(shù)據(jù)類型沒有 equals() 方法析恢。Object#equals() 的默認(rèn)實現(xiàn)時比較對象地址相等 (this == obj);

3冯痢、hashCode() 是 Object 的 native 方法氮昧,底層實現(xiàn)是將對象的內(nèi)存地址進(jìn)行哈希運算計算出的整數(shù)值;

4浦楣、 你說的是 Object.hashCode 通用約定(即:在實際中會有兩個對象相同袖肥,那么對應(yīng)的 hashCode() 一定相同,如果兩個對象 hashCode() 相同振劳,它們不一定相同(哈希沖突))椎组。這個約定是為了確保該類作為散列集合的 Key 時能夠正常運行(包括 HashMap、HashSet 和 Hashtable)历恐。如果不按照約定寸癌,也就是重寫 equals() 但未重寫 hashCode(),就會出現(xiàn)兩個 equals 的對象在哈希表中存儲了兩個獨立的鍵值對弱贼,這與哈希集合的語義矛盾蒸苇。


3、字符 & 字符串

在每種編程語言里吮旅,字符串都是一個躲不開的話題溪烤,也是面試常常出現(xiàn)的問題,關(guān)于「字符串」的面試題我統(tǒng)一整理在這篇文章:《Java | String 常見面試題》


4庇勃、程序設(shè)計

Java 如何實現(xiàn)單例模式檬嘀?

在 Java 中,有五種常規(guī)單例實現(xiàn)责嚷,每種單例實現(xiàn)各有特點鸳兽,沒有哪一種是最佳選擇。這五種實現(xiàn)是進(jìn)程級別的單例罕拂,除此之外還有線程級別單例揍异,可以利用 ThreadLocal 實現(xiàn)全陨。

先說一下五種常規(guī)單例實現(xiàn):

1、餓漢式: 線程安全蒿秦,調(diào)用效率最高烤镐,但不能懶加載

2、懶漢式 + synchronized: 線程安全棍鳖,調(diào)用效率不高,可以懶加載

3碗旅、DCL + volatile: 線程安全渡处,調(diào)用效率高,可以懶加載

4祟辟、靜態(tài)內(nèi)部類: 線程安全医瘫,調(diào)用效率高,可以懶加載旧困,但不能動態(tài)傳遞參數(shù)

5醇份、枚舉: 線程安全,調(diào)用效率高吼具,但不能延遲加載僚纷,天然地防止反射和反序列化破壞單例

相關(guān)深入文章:《我向面試官講解了單例模式,他對我豎起了大拇指》


不使用 synchronized 關(guān)鍵字拗盒,如何實現(xiàn)線程安全的單例怖竭?

常規(guī)的單例模式都顯式或隱式使用了 synchronized 關(guān)鍵字,懶漢式和 DCL 直接使用了陡蝇,而餓漢式和靜態(tài)內(nèi)部類使用了靜態(tài)成員變量痊臭,其原理其實也是使用了 synchronized。具體要從 Javac 編譯講起登夫,Javac 會將靜態(tài)內(nèi)部類和靜態(tài)代碼塊會整合為 <clinit> 方法广匙,這個方法就是類加載階段的最后一個階段 - 初始化階段。JVM 內(nèi)部會保證多線程環(huán)境下只有一個線程會執(zhí)行 <clinit>恼策,而其它線程需要等待鸦致。最后還有枚舉,枚舉底層是依賴 Enum 類戏蔑,每個枚舉對象其實都是類的靜態(tài)成員蹋凝,本質(zhì)上也和餓漢式類似。

那么总棵,有沒有辦法不使用傳統(tǒng)的互斥機(jī)制實現(xiàn)單例呢鳍寂?有的,可以使用 CAS 或 ThreadLocal情龄,這兩種方法都沒有使用互斥機(jī)制迄汛,但也可以保證線程安全捍壤,優(yōu)點是可以避免線程阻塞和喚醒的上下文切換消耗,也各有缺點:CAS 在資源競爭緊張的情況下鞍爱,會創(chuàng)建大量對象鹃觉,長時間自旋 CAS 也會增大 CPU 負(fù)載。ThreadLocal 的原理是在每個線程都提供一個副本睹逃,其實是以空間換時間盗扇,對內(nèi)存消耗大。

相關(guān)深入文章:
《面試官真是搞笑沉填!讓實現(xiàn)線程安全的單例疗隶,又不讓使用 synchronized!》
《Java 虛擬機(jī) | CAS 比較并交換》


如何破壞單例翼闹?

破壞單例其實就是在單例對象之外再創(chuàng)建另一個對象斑鼻,常規(guī)的對象創(chuàng)建是使用 new 關(guān)鍵字,此外還可以使用反射或反序列化創(chuàng)建對象猎荠,這些方法都可以破壞對象的單例性坚弱。需要注意的是,枚舉天然地可以防止反射和反序列化:使用反射創(chuàng)建枚舉對象時关摇,JDK 會判斷該類是否是一個枚舉類荒叶,如果是會拋出異常;在序列化和反序列化枚舉時拒垃,寫入和讀取的只是枚舉類型和枚舉對象的名字停撞,反序列化時直接使用 枚舉 valueOf(name) 直接查找枚舉對象,不會創(chuàng)建新的對象悼瓮。因此枚舉天然地可以防止破壞單例戈毒。


Kotlin 如何實現(xiàn)單例模式?

相關(guān)深入文章:《Kotlin 下的 5 種單例模式》


Java 創(chuàng)建對象有幾種方式横堡?

創(chuàng)建對象 是否調(diào)用構(gòu)造方法
new 關(guān)鍵字 調(diào)用構(gòu)造函數(shù)
反射(Class#newInstance() & Constructor#newInstance()) 調(diào)用構(gòu)造函數(shù)
Object#clone() 沒有調(diào)用構(gòu)造函數(shù)
反序列化 沒有調(diào)用構(gòu)造函數(shù)

Java 創(chuàng)建對象可以使用 new埋市、反射、clone() 和反序列化命贴,需要注意道宅,使用 clone() 和反序列化創(chuàng)建對象是不會調(diào)用構(gòu)造函數(shù)的。

new 關(guān)鍵字: 先在堆中創(chuàng)建對象胸蛛,并把對象的引用入棧污茵,隨后 invokespecial 調(diào)用 <init> 方法

Object obj = new Object();

new           // class java/lang/Object
dup
invokespecial // Method java/lang/Object.<init> :()V
astore_1
return

dup(完整單詞:duplicate 復(fù)制)會復(fù)制棧上最后一個元素,然后再次入棧葬项。因為 invokespecial 會消耗棧頂?shù)膶ο笠门⒌保匀绻覀兿M{(diào)用 invokespecial 后操作數(shù)棧頂還維持一個指向新建對象的引用,就必須先使用 dup 復(fù)制一份引用民珍。
astore_1 將操作數(shù)棧頂元素出棧并存儲在第 1 位局部變量表襟士。

反射(Class#newInstance() & Constructor#newInstance())

User user = User.class.newInstance();

ldc            // class com/User
invokevirtual  // Method java/lang/Class.newInstance: ()Ljava/lang/Object;
checkcast      // class com/User
astore_1
return

ldc:將常量池引用入棧
checkcast:檢查類型轉(zhuǎn)換合法

Object#clone(): 前提是類實現(xiàn) Cloneable 接口盗飒,復(fù)制對象時,首先創(chuàng)建一個和源對象相同大小的空間陋桂,然后進(jìn)行屬性復(fù)制逆趣,整個過程沒有經(jīng)過構(gòu)造方法。屬性復(fù)制是淺拷貝嗜历,基本類型的成員變量拷貝的是值宣渗,而引用類型的成員變量拷貝的是對象的內(nèi)存地址,不會創(chuàng)建新的對象秸脱。要實現(xiàn)深拷貝落包,需要每個成員變量的對象都實現(xiàn) Cloneabe 并重寫 clone() 方法,進(jìn)而實現(xiàn)對象的層層拷貝摊唇。深拷貝比淺拷貝性能損耗更大。

反序列化

相關(guān)深入文章:《盤點 Java 創(chuàng)建對象的 x 操作》


new 創(chuàng)建對象的過程

Java new 一個對象的過程涯鲁,基本上可以分為 5 個步驟:檢查加載 -> 分配內(nèi)存 -> 初始化零值 -> 設(shè)置對象頭 -> 執(zhí)行 <init> 構(gòu)造函數(shù):

  • 1巷查、檢查加載 & 類加載: 檢查類是否被類加載器加載,如果沒有需要先執(zhí)行類加載過程(加載 & 解析 & 初始化)抹腿;
  • 2岛请、分配內(nèi)存: Java 對象需要一塊連續(xù)的堆內(nèi)存空間,分配方式有 指針碰撞 & 空閑列表警绩。指針碰撞法要求 Java 堆是絕對規(guī)整的崇败,而空閑列表法不要求 Java 堆是絕對規(guī)整的。由于 Java 堆是線程共享的肩祥,所以需要考慮多線程并發(fā)分配內(nèi)存的問題后室,解決方法有 CAS 操作和 TLAB 分配緩沖。
  • 3混狠、初始化零值: 將實例數(shù)據(jù)的值初始化為零值岸霹;
  • 4、設(shè)置對象頭: 設(shè)置對象頭信息将饺,包括 Mark Work & 類型指針 & 數(shù)組長度贡避;
  • 5、執(zhí)行 <init> 構(gòu)造函數(shù): 執(zhí)行 <init> 構(gòu)造函數(shù)予弧,<init> 由編譯器生成刮吧,包括成員變量初始值、實例代碼塊和對象構(gòu)造函數(shù)掖蛤。

相關(guān)深入文章:Java 虛擬機(jī) | 拿放大鏡看對象


枚舉的實現(xiàn)原理杀捻?

序列化 & 反序列化的原理?

Kotlin lazy 的原理:


final坠七、finally 和 finalize() 有什么區(qū)別水醋?

final


反射

  • 反射可以修改 final 變量嗎旗笔?

5、集合

為什么 HashMap 是線程不安全的拄踪,體現(xiàn)在哪里蝇恶?

  • 數(shù)據(jù)覆蓋問題:如果兩個線程并發(fā)執(zhí)行 put 操作,并且兩個數(shù)據(jù)的 hash 值沖突惶桐,就可能出現(xiàn)數(shù)據(jù)覆蓋(線程 A 判斷 hash 值位置為 null撮弧,還未寫入數(shù)據(jù)時掛起,此時線程 B 正常插入數(shù)據(jù)姚糊。接著線程 A 獲得時間片贿衍,由于線程 A 不會重新判斷該位置是否為空,就會把剛才線程 B 寫入的數(shù)據(jù)覆蓋掉)救恨;
  • 環(huán)形鏈表問題: 如果兩個線程并發(fā)執(zhí)行 put 操作贸辈,并且觸發(fā)擴(kuò)容,就可能出現(xiàn)環(huán)形鏈表肠槽,此時獲取數(shù)據(jù)會死循環(huán)擎淤。這是因為 JDK 1.7 版本采用頭插法,在擴(kuò)容時會翻轉(zhuǎn)鏈表的順序秸仙,而 JDK 1.8 采用尾插法嘴拢,再擴(kuò)容時會保持鏈表原本的順序,就不會出現(xiàn)鏈表成環(huán)問題了寂纪。

相關(guān)深入文章:都說 HashMap 是線程不安全的席吴,到底體現(xiàn)在哪兒?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捞蛋,一起剝皮案震驚了整個濱河市孝冒,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌襟交,老刑警劉巖迈倍,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異捣域,居然都是意外死亡啼染,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門焕梅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迹鹅,“玉大人,你說我怎么就攤上這事贞言⌒迸铮” “怎么了?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長弟蚀。 經(jīng)常有香客問我蚤霞,道長,這世上最難降的妖魔是什么义钉? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任昧绣,我火速辦了婚禮,結(jié)果婚禮上捶闸,老公的妹妹穿的比我還像新娘夜畴。我一直安慰自己,他們只是感情好删壮,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布贪绘。 她就那樣靜靜地躺著,像睡著了一般央碟。 火紅的嫁衣襯著肌膚如雪税灌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天亿虽,我揣著相機(jī)與錄音垄琐,去河邊找鬼。 笑死经柴,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的墩朦。 我是一名探鬼主播坯认,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼氓涣!你這毒婦竟也來了牛哺?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤劳吠,失蹤者是張志新(化名)和其女友劉穎引润,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體痒玩,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡淳附,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蠢古。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片奴曙。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖草讶,靈堂內(nèi)的尸體忽然破棺而出洽糟,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布坤溃,位于F島的核電站拍霜,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏薪介。R本人自食惡果不足惜祠饺,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望昭灵。 院中可真熱鬧吠裆,春花似錦、人聲如沸烂完。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抠蚣。三九已至祝旷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嘶窄,已是汗流浹背怀跛。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留柄冲,地道東北人吻谋。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像现横,于是被迫代替她去往敵國和親漓拾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容

  • 今天感恩節(jié)哎戒祠,感謝一直在我身邊的親朋好友骇两。感恩相遇!感恩不離不棄姜盈。 中午開了第一次的黨會低千,身份的轉(zhuǎn)變要...
    迷月閃星情閱讀 10,559評論 0 11
  • 彩排完,天已黑
    劉凱書法閱讀 4,197評論 1 3
  • 表情是什么馏颂,我認(rèn)為表情就是表現(xiàn)出來的情緒示血。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了饱亮,難過就哭了矾芙。兩者是相互影響密不可...
    Persistenc_6aea閱讀 124,435評論 2 7