10個(gè)最難回答的Java面試題
這是我收集的10個(gè)較難回答的 Java 面試題子眶。這些問題主要來自 Java 核心部分 ,不涉及 Java EE 相關(guān)問題。這些問題都是容易在各種 Java 面試中被問到的枫绅。
1. 為什么 wait,notify 和 notifyAll 是在 Object 類中定義的而不是在 Thread 類中定義?
一個(gè)較難回答的 Java 問題避消, Java 編程語言又不是你設(shè)計(jì)的,你如何回答這個(gè)問題呢角虫? 需要對(duì) Java 編程的常識(shí)進(jìn)行深入了解才行沾谓。
這個(gè)問題的好在它能反映面試者是否對(duì) wait - notify 機(jī)制有沒有了解, 以及他相關(guān)知識(shí)的理解是否明確委造。就像為什么 Java 中不支持多繼承或者為什么 String 在 Java 中是 final 的問題一樣戳鹅,這個(gè)問題也可能有多個(gè)答案。
為什么在 Object 類中定義 wait 和 notify 方法昏兆,每個(gè)人都能說出一些理由枫虏。 從我的面試經(jīng)驗(yàn)來看, wait 和 nofity 仍然是大多數(shù)Java 程序員最困惑的,特別是2到3年的開發(fā)人員,如果他們要求使用 wait 和 notify, 他們會(huì)很困惑隶债。因此腾它,如果你去參加 Java 面試,請(qǐng)確保對(duì) wait 和 notify 機(jī)制有充分的了解死讹,并且可以輕松地使用 wait 來編寫代碼瞒滴,并通過“生產(chǎn)者-消費(fèi)者”問題或?qū)崿F(xiàn)阻塞隊(duì)列等了解通知的機(jī)制。
為什么等待和通知需要從同步塊或方法中調(diào)用, 以及 Java 中的 wait赞警,sleep 和 yield 方法之間的差異妓忍,如果你還沒有讀過相關(guān)知識(shí),一定要看看愧旦。為何 wait世剖,notify 和 notifyAll 屬于 Object 類? 為什么它們不應(yīng)該在 Thread 類中? 以下觀點(diǎn)我認(rèn)為是有道理的:
wait 和 notify 不僅僅是普通方法或同步工具,更重要的是它們是 Java 中兩個(gè)線程之間的通信機(jī)制笤虫。對(duì)語言設(shè)計(jì)者而言, 如果不能通過 Java 關(guān)鍵字(例如 synchronized)實(shí)現(xiàn)通信此機(jī)制旁瘫,同時(shí)又要確保這個(gè)機(jī)制對(duì)每個(gè)對(duì)象可用, 那么 Object 類則是的合理的聲明位置。記住同步和等待通知是兩個(gè)不同的領(lǐng)域琼蚯,不要把它們看成是相同的或相關(guān)的酬凳。同步是提供互斥并確保 Java 類的線程安全,而 wait 和 notify 是兩個(gè)線程之間的通信機(jī)制遭庶。
每個(gè)對(duì)象都可上鎖粱年,這是在 Object 類而不是 Thread 類中聲明 wait 和 notify 的另一個(gè)原因。
在 Java 中罚拟,為了進(jìn)入代碼的臨界區(qū)台诗,線程需要鎖定并等待鎖,他們不知道哪些線程持有鎖赐俗,而只是知道鎖被某個(gè)線程持有拉队, 并且需要等待以取得鎖, 而不是去了解哪個(gè)線程在同步塊內(nèi),并請(qǐng)求它們釋放鎖阻逮。
Java 是基于 Hoare 的監(jiān)視器的思想(http://en.wikipedia.org/wiki/...)粱快。在Java中,所有對(duì)象都有一個(gè)監(jiān)視器叔扼。
線程在監(jiān)視器上等待事哭,為執(zhí)行等待,我們需要2個(gè)參數(shù):
- 一個(gè)線程
- 一個(gè)監(jiān)視器(任何對(duì)象)
在 Java 設(shè)計(jì)中瓜富,線程不能被指定鳍咱,它總是運(yùn)行當(dāng)前代碼的線程。但是与柑,我們可以指定監(jiān)視器(這是我們稱之為等待的對(duì)象)谤辜。這是一個(gè)很好的設(shè)計(jì)蓄坏,因?yàn)槿绻覀兛梢宰屓魏纹渌€程在所需的監(jiān)視器上等待,這將導(dǎo)致“入侵”丑念,影響線程執(zhí)行順序涡戳,導(dǎo)致在設(shè)計(jì)并發(fā)程序時(shí)會(huì)遇到困難。請(qǐng)記住脯倚,在 Java 中渔彰,所有在另一個(gè)線程的執(zhí)行中造成入侵的操作都被棄用了(例如 Thread.stop 方法)。
2.為什么Java中不支持多重繼承推正?
我發(fā)現(xiàn)這個(gè) Java 核心問題很難回答胳岂,因?yàn)槟愕拇鸢缚赡懿粫?huì)讓面試官滿意,在大多數(shù)情況下舔稀,面試官正在尋找答案中的關(guān)鍵點(diǎn)乳丰,如果你提到這些關(guān)鍵點(diǎn),面試官會(huì)很高興内贮。在 Java 中回答這種棘手問題的關(guān)鍵是準(zhǔn)備好相關(guān)主題, 以應(yīng)對(duì)后續(xù)的各種可能的問題产园。
這是非常經(jīng)典的問題,與為什么 String 在 Java 中是不可變的很類似; 這兩個(gè)問題之間的相似之處在于它們主要是由 Java 創(chuàng)作者的設(shè)計(jì)決策使然夜郁。
為什么Java不支持多重繼承, 可以考慮以下兩點(diǎn):
- 第一個(gè)原因是圍繞鉆石??形繼承問題產(chǎn)生的歧義什燕,考慮一個(gè)類 A 有 foo() 方法, 然后 B 和 C 派生自 A, 并且有自己的 foo() 實(shí)現(xiàn),現(xiàn)在 D 類使用多個(gè)繼承派生自 B 和C竞端,如果我們只引用 foo(), 編譯器將無法決定它應(yīng)該調(diào)用哪個(gè) foo()屎即。這也稱為 Diamond 問題,因?yàn)檫@個(gè)繼承方案的結(jié)構(gòu)類似于菱形事富,見下圖:
A foo()
/ \
/ \
foo() B C foo()
\ /
\ /
D foo()
即使我們刪除鉆石的頂部 A 類并允許多重繼承技俐,我們也將看到這個(gè)問題含糊性的一面。如果你把這個(gè)理由告訴面試官统台,他會(huì)問為什么 C++ 可以支持多重繼承而 Java不行台妆。嗯尚辑,在這種情況下胞四,我會(huì)試著向他解釋我下面給出的第二個(gè)原因蜀细,它不是因?yàn)榧夹g(shù)難度, 而是更多的可維護(hù)和更清晰的設(shè)計(jì)是驅(qū)動(dòng)因素, 雖然這只能由 Java 言語設(shè)計(jì)師確認(rèn),我們只是推測(cè)贵扰。維基百科鏈接有一些很好的解釋仇穗,說明在使用多重繼承時(shí),由于鉆石問題戚绕,不同的語言地址問題是如何產(chǎn)生的纹坐。
- 對(duì)我來說第二個(gè)也是更有說服力的理由是,多重繼承確實(shí)使設(shè)計(jì)復(fù)雜化并在強(qiáng)制轉(zhuǎn)換列肢、構(gòu)造函數(shù)鏈接等過程中產(chǎn)生問題恰画。假設(shè)你需要多重繼承的情況并不多,簡(jiǎn)單起見瓷马,明智的決定是省略它拴还。此外,Java 可以通過使用接口支持單繼承來避免這種歧義欧聘。由于接口只有方法聲明而且沒有提供任何實(shí)現(xiàn)片林,因此只有一個(gè)特定方法的實(shí)現(xiàn),因此不會(huì)有任何歧義怀骤。
3.為什么Java不支持運(yùn)算符重載费封?
另一個(gè)類似的 Java 面試難題。為什么 C++ 支持運(yùn)算符重載而 Java 不支持? 有人可能會(huì)說 +
運(yùn)算符在 Java 中已被重載用于字符串連接蒋伦,不要被這些論據(jù)所欺騙弓摘。
與 C++ 不同,Java 不支持運(yùn)算符重載痕届。Java 不能為程序員提供自由的標(biāo)準(zhǔn)算術(shù)運(yùn)算符重載韧献,例如+
, -
研叫,*
和/
等锤窑。如果你以前用過 C++,那么 Java 與 C++ 相比少了很多功能嚷炉,例如 Java 不支持多重繼承渊啰,Java中沒有指針,Java中沒有地址引用傳遞申屹。另一個(gè)類似的問題是關(guān)于 Java 通過引用傳遞绘证,這主要表現(xiàn)為 Java 是通過值還是引用傳參。雖然我不知道背后的真正原因哗讥,但我認(rèn)為以下說法有些道理迈窟,為什么 Java 不支持運(yùn)算符重載。
簡(jiǎn)單性和清晰性忌栅。清晰性是Java設(shè)計(jì)者的目標(biāo)之一车酣。設(shè)計(jì)者不是只想復(fù)制語言,而是希望擁有一種清晰索绪,真正面向?qū)ο蟮恼Z言湖员。添加運(yùn)算符重載比沒有它肯定會(huì)使設(shè)計(jì)更復(fù)雜,并且它可能導(dǎo)致更復(fù)雜的編譯器, 或減慢 JVM瑞驱,因?yàn)樗枰鲱~外的工作來識(shí)別運(yùn)算符的實(shí)際含義娘摔,并減少優(yōu)化的機(jī)會(huì), 以保證 Java 中運(yùn)算符的行為。
避免編程錯(cuò)誤唤反。Java 不允許用戶定義的運(yùn)算符重載凳寺,因?yàn)槿绻试S程序員進(jìn)行運(yùn)算符重載鸭津,將為同一運(yùn)算符賦予多種含義,這將使任何開發(fā)人員的學(xué)習(xí)曲線變得陡峭肠缨,事情變得更加混亂逆趋。據(jù)觀察,當(dāng)語言支持運(yùn)算符重載時(shí)晒奕,編程錯(cuò)誤會(huì)增加闻书,從而增加了開發(fā)和交付時(shí)間。由于 Java 和 JVM 已經(jīng)承擔(dān)了大多數(shù)開發(fā)人員的責(zé)任脑慧,如在通過提供垃圾收集器進(jìn)行內(nèi)存管理時(shí)魄眉,因?yàn)檫@個(gè)功能增加污染代碼的機(jī)會(huì), 成為編程錯(cuò)誤之源, 因此沒有多大意義。
JVM復(fù)雜性闷袒。從JVM的角度來看坑律,支持運(yùn)算符重載使問題變得更加困難。通過更直觀囊骤,更干凈的方式使用方法重載也能實(shí)現(xiàn)同樣的事情脾歇,因此不支持 Java 中的運(yùn)算符重載是有意義的。與相對(duì)簡(jiǎn)單的 JVM 相比淘捡,復(fù)雜的 JVM 可能導(dǎo)致 JVM 更慢藕各,并為保證在 Java 中運(yùn)算符行為的確定性從而減少了優(yōu)化代碼的機(jī)會(huì)。
讓開發(fā)工具處理更容易焦除。這是在 Java 中不支持運(yùn)算符重載的另一個(gè)好處激况。省略運(yùn)算符重載后使語言更容易處理,如靜態(tài)分析等膘魄,這反過來又更容易開發(fā)處理語言的工具乌逐,例如 IDE 或重構(gòu)工具。Java 中的重構(gòu)工具遠(yuǎn)勝于 C++创葡。
4.為什么 String 在 Java 中是不可變的浙踢?
我最喜歡的 Java 面試問題,不好回答灿渴,但同時(shí)也非常有用洛波。一些面試者也常問這個(gè)問題,為什么 String 在 Java 中是 final
的骚露。
字符串在 Java 中是不可變的蹬挤,因?yàn)?String 對(duì)象緩存在 String 池中。由于緩存的字符串在多個(gè)客戶之間共享棘幸,因此始終存在風(fēng)險(xiǎn)焰扳,其中一個(gè)客戶的操作會(huì)影響所有其他客戶。例如,如果一段代碼將 String “Test” 的值更改為 “TEST”吨悍,則所有其他客戶也將看到該值扫茅。由于 String 對(duì)象的緩存是性能的重要保證,因此通過使 String 類不可變來避免這種風(fēng)險(xiǎn)育瓜。
同時(shí)葫隙,String 是 final 的,因此沒有人可以通過擴(kuò)展和覆蓋行為來破壞 String 類的不變性爆雹、緩存停蕉、散列值的計(jì)算等愕鼓。String 類不可變的另一個(gè)原因可能是由于 HashMap
钙态。
由于把字符串作為 HashMap 鍵很受歡迎。對(duì)于鍵值來說菇晃,不可變性是非常的重要册倒,以便用它們檢索存儲(chǔ)在 HashMap 中的值對(duì)象。由于 HashMap 的工作原理是散列磺送,因此需要具有相同的值才能正常運(yùn)行驻子。如果在插入后修改了 String 的內(nèi)容,可變的 String 將在插入和檢索時(shí)生成兩個(gè)不同的哈希碼估灿,可能會(huì)丟失 Map 中的值對(duì)象崇呵。
字符串是Java 非常特殊的類。我還沒有看到一個(gè)沒有使用 String 編寫的 Java 程序馅袁。這就是為什么對(duì) String 的充分理解對(duì)于 Java 開發(fā)人員來說非常重要域慷。
String 是數(shù)據(jù)類型,也傳輸對(duì)象和中間人汗销。 這種多重角色的重要性和流行性犹褒, 也使這個(gè)問題在 Java 面試中很常見。
為什么 String 在 Java 中是不可變的是 Java 中最常被問到的字符串訪問問題之一弛针,它首先討論了什么是 String叠骑,Java 中的 String 如何與 C 和 C++ 中的 String 不同,然后轉(zhuǎn)向在Java中什么是不可變對(duì)象削茁,不可變對(duì)象有什么好處宙枷,為什么要使用它們以及應(yīng)該使用哪些場(chǎng)景。這個(gè)問題有時(shí)也會(huì)問:“為什么 String 在 Java 中是 final 的”茧跋。在類似的說明中朦拖,如果你正在準(zhǔn)備Java 面試,我建議你看看Java編程面試公開書厌衔,這是高級(jí)和中級(jí)Java程序員的優(yōu)秀資源璧帝。它包含來自所有重要 Java 主題的問題,包括多線程富寿,集合睬隶,GC锣夹,JVM內(nèi)部以及 Spring和 Hibernate 框架等。
正如我所說苏潜,這個(gè)問題可能有很多可能的答案银萍,而 String 類的唯一設(shè)計(jì)者可以放心地回答它。我在 Joshua Bloch 的 Effective Java 書中期待一些線索恤左,但他也沒有提到它贴唇。我認(rèn)為以下幾點(diǎn)解釋了為什么 String 類在 Java 中是不可變的或 final 的:
- 想象字符串池沒有使字符串不可變,它根本不可能飞袋,因?yàn)樵谧址氐那闆r下戳气,一個(gè)字符串對(duì)象/文字,例如 “Test” 已被許多參考變量引用巧鸭,因此如果其中任何一個(gè)更改了值瓶您,其他參數(shù)將自動(dòng)受到影響,即假設(shè)
String A="Test";
String B="Test";
現(xiàn)在字符串 B 調(diào)用 "Test".toUpperCase()
, 將同一個(gè)對(duì)象改為“TEST”纲仍,所以 A 也是 “TEST”呀袱,這不是期望的結(jié)果。
下圖顯示了如何在堆內(nèi)存和字符串池中創(chuàng)建字符串郑叠。
字符串已被廣泛用作許多 Java 類的參數(shù)夜赵,例如,為了打開網(wǎng)絡(luò)連接乡革,你可以將主機(jī)名和端口號(hào)作為字符串傳遞寇僧,你可以將數(shù)據(jù)庫(kù) URL 作為字符串傳遞, 以打開數(shù)據(jù)庫(kù)連接,你可以通過將文件名作為參數(shù)傳遞給 File I/O 類來打開 Java 中的任何文件署拟。如果 String 不是不可變的婉宰,這將導(dǎo)致嚴(yán)重的安全威脅,我的意思是有人可以訪問他有權(quán)授權(quán)的任何文件推穷,然后可以故意或意外地更改文件名并獲得對(duì)該文件的訪問權(quán)限心包。由于不變性,你無需擔(dān)心這種威脅馒铃。這個(gè)原因也說明了蟹腾,為什么 String 在 Java 中是最終的,通過使
java.lang.String
final区宇,Java設(shè)計(jì)者確保沒有人覆蓋 String 類的任何行為娃殖。由于 String 是不可變的,它可以安全地共享許多線程议谷,這對(duì)于多線程編程非常重要. 并且避免了 Java 中的同步問題炉爆,不變性也使得String 實(shí)例在 Java 中是線程安全的,這意味著你不需要從外部同步 String 操作。關(guān)于 String 的另一個(gè)要點(diǎn)是由截取字符串 SubString 引起的內(nèi)存泄漏芬首,這不是與線程相關(guān)的問題赴捞,但也是需要注意的。
為什么 String 在 Java 中是不可變的另一個(gè)原因是允許 String 緩存其哈希碼郁稍,Java 中的不可變 String 緩存其哈希碼赦政,并且不會(huì)在每次調(diào)用 String 的 hashcode 方法時(shí)重新計(jì)算,這使得它在 Java 中的 HashMap 中使用的 HashMap 鍵非骋快恢着。簡(jiǎn)而言之,因?yàn)?String 是不可變的财破,所以沒有人可以在創(chuàng)建后更改其內(nèi)容掰派,這保證了 String 的 hashCode 在多次調(diào)用時(shí)是相同的。
String 不可變的絕對(duì)最重要的原因是它被類加載機(jī)制使用狈究,因此具有深刻和基本的安全考慮碗淌。如果 String 是可變的盏求,加載“java.io.Writer” 的請(qǐng)求可能已被更改為加載 “mil.vogoon.DiskErasingWriter”. 安全性和字符串池是使字符串不可變的主要原因抖锥。順便說一句,上面的理由很好回答另一個(gè)Java面試問題: “為什么String在Java中是最終的”碎罚。要想是不可變的磅废,你必須是最終的,這樣你的子類不會(huì)破壞不變性荆烈。你怎么看拯勉?
5. 為什么 char 數(shù)組比 Java 中的 String 更適合存儲(chǔ)密碼?
另一個(gè)基于 String 的棘手 Java 問題憔购,相信我只有很少的 Java 程序員可以正確回答這個(gè)問題宫峦。這是一個(gè)真正艱難的核心Java面試問題,并且需要對(duì) String 的扎實(shí)知識(shí)才能回答這個(gè)問題玫鸟。
這是最近在 Java 面試中向我的一位朋友詢問的問題导绷。他正在接受技術(shù)主管職位的面試,并且有超過6年的經(jīng)驗(yàn)屎飘。如果你還沒有遇到過這種情況妥曲,那么字符數(shù)組和字符串可以用來存儲(chǔ)文本數(shù)據(jù),但是選擇一個(gè)而不是另一個(gè)很難钦购。但正如我的朋友所說檐盟,任何與 String 相關(guān)的問題都必須對(duì)字符串的特殊屬性有一些線索,比如不變性押桃,他用它來說服訪提問的人葵萎。在這里,我們將探討為什么你應(yīng)該使用char[]
存儲(chǔ)密碼而不是String
的一些原因。
字符串:1)由于字符串在 Java 中是不可變的羡忘,如果你將密碼存儲(chǔ)為純文本锡足,它將在內(nèi)存中可用,直到垃圾收集器清除它. 并且為了可重用性壳坪,會(huì)存在 String 在字符串池中, 它很可能會(huì)保留在內(nèi)存中持續(xù)很長(zhǎng)時(shí)間舶得,從而構(gòu)成安全威脅。
由于任何有權(quán)訪問內(nèi)存轉(zhuǎn)儲(chǔ)的人都可以以明文形式找到密碼爽蝴,這是另一個(gè)原因沐批,你應(yīng)該始終使用加密密碼而不是純文本。由于字符串是不可變的蝎亚,所以不能更改字符串的內(nèi)容九孩,因?yàn)槿魏胃亩紩?huì)產(chǎn)生新的字符串,而如果你使用char[]
发框,你就可以將所有元素設(shè)置為空白或零躺彬。因此,在字符數(shù)組中存儲(chǔ)密碼可以明顯降低竊取密碼的安全風(fēng)險(xiǎn)梅惯。
2)Java 本身建議使用 JPasswordField
的 getPassword()
方法宪拥,該方法返回一個(gè) char[]
和不推薦使用的getTex()
方法,該方法以明文形式返回密碼铣减,由于安全原因她君。應(yīng)遵循 Java 團(tuán)隊(duì)的建議, 堅(jiān)持標(biāo)準(zhǔn)而不是反對(duì)它。
3)使用 String 時(shí)葫哗,總是存在在日志文件或控制臺(tái)中打印純文本的風(fēng)險(xiǎn)缔刹,但如果使用 Array,則不會(huì)打印數(shù)組的內(nèi)容而是打印其內(nèi)存位置劣针。雖然不是一個(gè)真正的原因校镐,但仍然有道理。
String strPassword =“Unknown”;
char [] charPassword = new char [] {'U'捺典,'n'鸟廓,'k','w'辣苏,'o'肝箱,'n'};
System.out.println(“字符密碼:”+ strPassword);
System.out.println(“字符密碼:”+ charPassword);
輸出
字符串密碼:Unknown
字符密碼:[C @110b053
我還建議使用散列或加密的密碼而不是純文本,并在驗(yàn)證完成后立即從內(nèi)存中清除它稀蟋。因此,在Java中,用字符數(shù)組用存儲(chǔ)密碼比字符串是更好的選擇煌张。雖然僅使用char[]
還不夠,還你需要擦除內(nèi)容才能更安全退客。
6. 如何使用雙重檢查鎖定在 Java 中創(chuàng)建線程安全的單例骏融?
艱難的核心 Java 面試問題.這個(gè) Java 問題也常被問: 什么是線程安全的單例链嘀,你怎么創(chuàng)建它。好吧档玻,在Java 5
之前的版本, 使用雙重檢查鎖定創(chuàng)建單例 Singleton
時(shí)怀泊,如果多個(gè)線程試圖同時(shí)創(chuàng)建 Singleton
實(shí)例,則可能有多個(gè) Singleton
實(shí)例被創(chuàng)建误趴。從 Java 5 開始霹琼,使用 Enum 創(chuàng)建線程安全的Singleton
很容易。但如果面試官堅(jiān)持雙重檢查鎖定凉当,那么你必須為他們編寫代碼枣申。記得使用volatile
變量。
為什么枚舉單例在 Java 中更好
枚舉單例是使用一個(gè)實(shí)例在 Java 中實(shí)現(xiàn)單例模式的新方法看杭。雖然Java中的單例模式存在很長(zhǎng)時(shí)間,但枚舉單例是相對(duì)較新的概念,在引入Enum作為關(guān)鍵字和功能之后,從Java5開始在實(shí)踐中忠藤。本文與之前關(guān)于 Singleton 的內(nèi)容有些相關(guān), 其中討論了有關(guān) Singleton
模式的面試中的常見問題, 以及 10 個(gè) Java 枚舉示例, 其中我們看到了如何通用枚舉可以。這篇文章是關(guān)于為什么我們應(yīng)該使用Eeame作為Java中的單例,它比傳統(tǒng)的單例方法相比有什么好處等等楼雹。
Java 枚舉和單例模式
Java 中的枚舉單例模式是使用枚舉在 Java 中實(shí)現(xiàn)單例模式模孩。單例模式在 Java 中早有應(yīng)用, 但使用枚舉類型創(chuàng)建單例模式時(shí)間卻不長(zhǎng). 如果感興趣, 你可以了解下構(gòu)建者設(shè)計(jì)模式和裝飾器設(shè)計(jì)模式。
- 枚舉單例易于書寫
這是迄今為止最大的優(yōu)勢(shì),如果你在Java 5之前一直在編寫單例, 你知道, 即使雙檢查鎖定, 你仍可以有多個(gè)實(shí)例贮缅。雖然這個(gè)問題通過 Java 內(nèi)存模型的改進(jìn)已經(jīng)解決了, 從 Java 5 開始的volatile
類型變量提供了保證, 但是對(duì)于許多初學(xué)者來說, 編寫起來仍然很棘手榨咐。與同步雙檢查鎖定相比,枚舉單例實(shí)在是太簡(jiǎn)單了。如果你不相信, 那就比較一下下面的傳統(tǒng)雙檢查鎖定單例和枚舉單例的代碼:
在 Java 中使用枚舉的單例
這是我們通常聲明枚舉的單例的方式,它可能包含實(shí)例變量和實(shí)例方法,但為了簡(jiǎn)單起見,我沒有使用任何實(shí)例方法,只是要注意,如果你使用的實(shí)例方法且該方法能改變對(duì)象的狀態(tài)的話, 則需要確保該方法的線程安全携悯。默認(rèn)情況下,創(chuàng)建枚舉實(shí)例是線程安全的,但 Enum 上的任何其他方法是否線程安全都是程序員的責(zé)任祭芦。
/**
* 使用 Java 枚舉的單例模式示例
*/
public enum EasySingleton{
INSTANCE;
}
你可以通過EasySingleton.INSTANCE
來處理它,這比在單例上調(diào)用getInstance()
方法容易得多筷笨。
具有雙檢查鎖定的單例示例
下面的代碼是單例模式中雙重檢查鎖定的示例,此處的 getInstance()
方法檢查兩次,以查看 INSTANCE 是否為空,這就是為什么它被稱為雙檢查鎖定模式,請(qǐng)記住,雙檢查鎖定是代理之前Java 5,但Java5內(nèi)存模型中易失變量的干擾,它應(yīng)該工作完美憔鬼。
/**
* 單例模式示例,雙重鎖定檢查
*/
public class DoubleCheckedLockingSingleton{
private volatile DoubleCheckedLockingSingleton INSTANCE;
private DoubleCheckedLockingSingleton(){}
public DoubleCheckedLockingSingleton getInstance(){
if(INSTANCE == null){
synchronized(DoubleCheckedLockingSingleton.class){
//double checking Singleton instance
if(INSTANCE == null){
INSTANCE = new DoubleCheckedLockingSingleton();
}
}
}
return INSTANCE;
}
}
你可以調(diào)用DoubleCheckedLockingSingleton.getInstance()
來獲取此單例類的訪問權(quán)限。
現(xiàn)在,只需查看創(chuàng)建延遲加載的線程安全的 Singleton 所需的代碼量胃夏。使用枚舉單例模式, 你可以在一行中具有該模式, 因?yàn)閯?chuàng)建枚舉實(shí)例是線程安全的, 并且由 JVM 進(jìn)行轴或。
人們可能會(huì)爭(zhēng)辯說,有更好的方法來編寫 Singleton 而不是雙檢查鎖定方法, 但每種方法都有自己的優(yōu)點(diǎn)和缺點(diǎn), 就像我最喜歡在類加載時(shí)創(chuàng)建的靜態(tài)字段 Singleton, 如下面所示, 但請(qǐng)記住, 這不是一個(gè)延遲加載單例:
單例模式用靜態(tài)工廠方法
這是我最喜歡的在 Java 中影響 Singleton 模式的方法之一,因?yàn)?Singleton 實(shí)例是靜態(tài)的,并且最后一個(gè)變量在類首次加載到內(nèi)存時(shí)初始化,因此實(shí)例的創(chuàng)建本質(zhì)上是線程安全的。
/**
* 單例模式示例與靜態(tài)工廠方法
*/
public class Singleton{
//initailzed during class loading
private static final Singleton INSTANCE = new Singleton();
//to prevent creating another instance of Singleton
private Singleton(){}
public static Singleton getSingleton(){
return INSTANCE;
}
}
你可以調(diào)用 Singleton.getSingleton()
來獲取此類的訪問權(quán)限仰禀。
- 枚舉單例自行處理序列化
傳統(tǒng)單例的另一個(gè)問題是,一旦實(shí)現(xiàn)可序列化接口,它們就不再是 Singleton, 因?yàn)?readObject() 方法總是返回一個(gè)新實(shí)例, 就像 Java 中的構(gòu)造函數(shù)一樣照雁。通過使用 readResolve() 方法, 通過在以下示例中替換 Singeton 來避免這種情況:
//readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
如果 Singleton 類保持內(nèi)部狀態(tài), 這將變得更加復(fù)雜, 因?yàn)槟阈枰獦?biāo)記為 transient(不被序列化),但使用枚舉單例, 序列化由 JVM 進(jìn)行。
- 創(chuàng)建枚舉實(shí)例是線程安全的
如第 1 點(diǎn)所述,因?yàn)?Enum 實(shí)例的創(chuàng)建在默認(rèn)情況下是線程安全的, 你無需擔(dān)心是否要做雙重檢查鎖定答恶。
總之, 在保證序列化和線程安全的情況下,使用兩行代碼枚舉單例模式是在 Java 5 以后的世界中創(chuàng)建 Singleton 的最佳方式饺蚊。你仍然可以使用其他流行的方法, 如你覺得更好, 歡迎討論。
7. 編寫 Java 程序時(shí), 如何在 Java 中創(chuàng)建死鎖并修復(fù)它悬嗓?
經(jīng)典但核心Java面試問題之一污呼。
如果你沒有參與過多線程并發(fā) Java 應(yīng)用程序的編碼,你可能會(huì)失敗包竹。
如何避免 Java 線程死鎖燕酷?
如何避免 Java 中的死鎖籍凝?是 Java 面試的熱門問題之一, 也是多線程的編程中的重口味之一, 主要在招高級(jí)程序員時(shí)容易被問到, 且有很多后續(xù)問題。盡管問題看起來非趁缢酰基本, 但大多數(shù) Java 開發(fā)人員一旦你開始深入, 就會(huì)陷入困境饵蒂。
面試問題總是以“什么是死鎖???”開始
當(dāng)兩個(gè)或多個(gè)線程在等待彼此釋放所需的資源(鎖定)并陷入無限等待即是死鎖酱讶。它僅在多任務(wù)或多線程的情況下發(fā)生退盯。
如何檢測(cè) Java 中的死鎖?
雖然這可以有很多答案, 但我的版本是首先我會(huì)看看代碼, 如果我看到一個(gè)嵌套的同步塊泻肯,或從一個(gè)同步的方法調(diào)用其他同步方法, 或試圖在不同的對(duì)象上獲取鎖, 如果開發(fā)人員不是非常小心得问,就很容易造成死鎖。
另一種方法是在運(yùn)行應(yīng)用程序時(shí)實(shí)際鎖定時(shí)找到它, 嘗試采取線程轉(zhuǎn)儲(chǔ),在 Linux 中,你可以通過kill -3
命令執(zhí)行此操作, 這將打印應(yīng)用程序日志文件中所有線程的狀態(tài), 并且你可以看到哪個(gè)線程被鎖定在哪個(gè)線程對(duì)象上软免。
你可以使用 fastthread.io 網(wǎng)站等工具分析該線程轉(zhuǎn)儲(chǔ), 這些工具允許你上載線程轉(zhuǎn)儲(chǔ)并對(duì)其進(jìn)行分析宫纬。
另一種方法是使用 jConsole 或 VisualVM, 它將顯示哪些線程被鎖定以及哪些對(duì)象被鎖定。
如果你有興趣了解故障排除工具和分析線程轉(zhuǎn)儲(chǔ)的過程, 我建議你看看 Uriah Levy 在多元視覺(PluraIsight)上《分析 Java 線程轉(zhuǎn)儲(chǔ)》課程膏萧。旨在詳細(xì)了解 Java 線程轉(zhuǎn)儲(chǔ), 并熟悉其他流行的高級(jí)故障排除工具漓骚。
編寫一個(gè)將導(dǎo)致死鎖的Java程序?
一旦你回答了前面的問題,他們可能會(huì)要求你編寫代碼,這將導(dǎo)致Java死鎖榛泛。
這是我的版本之一
/**
* Java 程序通過強(qiáng)制循環(huán)等待來創(chuàng)建死鎖蝌蹂。
*
*
*/
public class DeadLockDemo {
/*
* 此方法請(qǐng)求兩個(gè)鎖,第一個(gè)字符串,然后整數(shù)
*/
public void method1() {
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");
}
}
}
/*
* 此方法也請(qǐng)求相同的兩個(gè)鎖,但完全
* 相反的順序,即首先整數(shù),然后字符串。
* 如果一個(gè)線程持有字符串鎖,則這會(huì)產(chǎn)生潛在的死鎖
* 和其他持有整數(shù)鎖,他們等待對(duì)方,永遠(yuǎn)曹锨。
*/
public void method2() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
}
如果 method1() 和 method2() 都由兩個(gè)或多個(gè)線程調(diào)用,則存在死鎖的可能性, 因?yàn)槿绻€程 1 在執(zhí)行 method1() 時(shí)在 Sting 對(duì)象上獲取鎖, 線程 2 在執(zhí)行 method2() 時(shí)在 Integer 對(duì)象上獲取鎖, 等待彼此釋放 Integer 和 String 上的鎖以繼續(xù)進(jìn)行一步, 但這永遠(yuǎn)不會(huì)發(fā)生孤个。
此圖精確演示了我們的程序, 其中一個(gè)線程在一個(gè)對(duì)象上持有鎖, 并等待其他線程持有的其他對(duì)象鎖。
你可以看到, Thread1 需要 Thread2 持有的 Object2 上的鎖,而 Thread2 希望獲得 Thread1 持有的 Object1 上的鎖沛简。由于沒有線程愿意放棄, 因此存在死鎖, Java 程序被卡住齐鲤。
其理念是, 你應(yīng)該知道使用常見并發(fā)模式的正確方法, 如果你不熟悉這些模式,那么 Jose Paumard 《應(yīng)用于并發(fā)和多線程的常見 Java 模式》是學(xué)習(xí)的好起點(diǎn)。
如何避免Java中的死鎖椒楣?
現(xiàn)在面試官來到最后一部分, 在我看來, 最重要的部分之一; 如何修復(fù)代碼中的死鎖给郊?或如何避免Java中的死鎖?
如果你仔細(xì)查看了上面的代碼,那么你可能已經(jīng)發(fā)現(xiàn)死鎖的真正原因不是多個(gè)線程, 而是它們請(qǐng)求鎖的方式, 如果你提供有序訪問, 則問題將得到解決捧灰。
下面是我的修復(fù)版本,它通過避免循環(huán)等待淆九,而避免死鎖, 而不需要搶占, 這是需要死鎖的四個(gè)條件之一。
public class DeadLockFixed {
/**
* 兩種方法現(xiàn)在都以相同的順序請(qǐng)求鎖,首先采用整數(shù),然后是 String毛俏。
* 你也可以做反向,例如,第一個(gè)字符串,然后整數(shù),
* 只要兩種方法都請(qǐng)求鎖定,兩者都能解決問題
* 順序一致炭庙。
*/
public void method1() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
public void method2() {
synchronized (Integer.class) {
System.out.println("Aquired lock on Integer.class object");
synchronized (String.class) {
System.out.println("Aquired lock on String.class object");
}
}
}
}
現(xiàn)在沒有任何死鎖,因?yàn)閮煞N方法都按相同的順序訪問 Integer 和 String 類文本上的鎖煌寇。因此,如果線程 A 在 Integer 對(duì)象上獲取鎖, 則線程 B 不會(huì)繼續(xù), 直到線程 A 釋放 Integer 鎖, 即使線程 B 持有 String 鎖, 線程 A 也不會(huì)被阻止, 因?yàn)楝F(xiàn)在線程 B 不會(huì)期望線程 A 釋放 Integer 鎖以繼續(xù)焕蹄。
8. 如果你的Serializable類包含一個(gè)不可序列化的成員,會(huì)發(fā)生什么擦盾?你是如何解決的嘲驾?
任何序列化該類的嘗試都會(huì)因NotSerializableException
而失敗,但這可以通過在 Java中 為 static 設(shè)置瞬態(tài)(trancient)變量來輕松解決迹卢。
Java 序列化相關(guān)的常見問題
Java 序列化是一個(gè)重要概念, 但它很少用作持久性解決方案, 開發(fā)人員大多忽略了 Java 序列化 API辽故。根據(jù)我的經(jīng)驗(yàn), Java 序列化在任何 Java核心內(nèi)容面試中都是一個(gè)相當(dāng)重要的話題, 在幾乎所有的網(wǎng)面試中, 我都遇到過一兩個(gè) Java 序列化問題, 我看過一次面試, 在問幾個(gè)關(guān)于序列化的問題之后候選人開始感到不自在, 因?yàn)槿狈@方面的經(jīng)驗(yàn)。 他們不知道如何在 Java 中序列化對(duì)象, 或者他們不熟悉任何 Java 示例來解釋序列化, 忘記了諸如序列化在 Java 中如何工作, 什么是標(biāo)記接口, 標(biāo)記接口的目的是什么, 瞬態(tài)變量和可變變量之間的差異, 可序列化接口具有多少種方法, 在 Java 中,Serializable
和 Externalizable
有什么區(qū)別, 或者在引入注解之后, 為什么不用 @Serializable
注解或替換 Serializalbe
接口腐碱。
在本文中,我們將從初學(xué)者和高級(jí)別進(jìn)行提問, 這對(duì)新手和具有多年 Java 開發(fā)經(jīng)驗(yàn)的高級(jí)開發(fā)人員同樣有益誊垢。
關(guān)于Java序列化的10個(gè)面試問題
大多數(shù)商業(yè)項(xiàng)目使用數(shù)據(jù)庫(kù)或內(nèi)存映射文件或只是普通文件, 來滿足持久性要求症见, 只有很少的項(xiàng)目依賴于 Java 中的序列化過程喂走。無論如何,這篇文章不是 Java 序列化教程或如何序列化在 Java 的對(duì)象, 但有關(guān)序列化機(jī)制和序列化 API 的面試問題, 這是值得去任何 Java 面試前先看看以免讓一些未知的內(nèi)容驚到自己谋作。
對(duì)于那些不熟悉 Java 序列化的人, Java 序列化是用來通過將對(duì)象的狀態(tài)存儲(chǔ)到帶有.ser
擴(kuò)展名的文件來序列化 Java 中的對(duì)象的過程, 并且可以通過這個(gè)文件恢復(fù)重建 Java對(duì)象狀態(tài), 這個(gè)逆過程稱為 deserialization芋肠。
什么是 Java 序列化
序列化是把對(duì)象改成可以存到磁盤或通過網(wǎng)絡(luò)發(fā)送到其他運(yùn)行中的 Java 虛擬機(jī)的二進(jìn)制格式的過程, 并可以通過反序列化恢復(fù)對(duì)象狀態(tài). Java 序列化API給開發(fā)人員提供了一個(gè)標(biāo)準(zhǔn)機(jī)制, 通過 java.io.Serializable
和 java.io.Externalizable
接口, ObjectInputStream
及ObjectOutputStream
處理對(duì)象序列化. Java 程序員可自由選擇基于類結(jié)構(gòu)的標(biāo)準(zhǔn)序列化或是他們自定義的二進(jìn)制格式, 通常認(rèn)為后者才是最佳實(shí)踐, 因?yàn)樾蛄谢亩M(jìn)制文件格式成為類輸出 API的一部分, 可能破壞 Java 中私有和包可見的屬性的封裝.
如何序列化
讓 Java 中的類可以序列化很簡(jiǎn)單. 你的 Java 類只需要實(shí)現(xiàn) java.io.Serializable
接口, JVM 就會(huì)把 Object 對(duì)象按默認(rèn)格式序列化。 讓一個(gè)類是可序列化的需要有意為之遵蚜。 類可序列會(huì)可能為是一個(gè)長(zhǎng)期代價(jià)帖池, 可能會(huì)因此而限制你修改或改變其實(shí)現(xiàn). 當(dāng)你通過實(shí)現(xiàn)添加接口來更改類的結(jié)構(gòu)時(shí), 添加或刪除任何字段可能會(huì)破壞默認(rèn)序列化吭净, 這可以通過自定義二進(jìn)制格式使不兼容的可能性最小化, 但仍需要大量的努力來確保向后兼容性睡汹。序列化如何限制你更改類的能力的一個(gè)示例是 SerialVersionUID。如果不顯式聲明 SerialVersionUID, 則 JVM 會(huì)根據(jù)類結(jié)構(gòu)生成其結(jié)構(gòu)寂殉, 該結(jié)構(gòu)依賴于類實(shí)現(xiàn)接口和可能更改的其他幾個(gè)因素囚巴。 假設(shè)你新版本的類文件實(shí)現(xiàn)的另一個(gè)接口, JVM 將生成一個(gè)不同的 SerialVersionUID 的, 當(dāng)你嘗試加載舊版本的程序序列化的舊對(duì)象時(shí), 你將獲得無效類異常 InvalidClassException。
問題 1) Java 中的可序列化接口和可外部接口之間的區(qū)別是什么友扰?
這是 Java 序列化訪談中最常問的問題彤叉。下面是我的版本 Externalizable 給我們提供 writeExternal() 和 readExternal() 方法, 這讓我們靈活地控制 Java 序列化機(jī)制, 而不是依賴于 Java 的默認(rèn)序列化。 正確實(shí)現(xiàn) Externalizable 接口可以顯著提高應(yīng)用程序的性能焕檬。
問題 2) 可序列化的方法有多少姆坚?如果沒有方法,那么可序列化接口的用途是什么?
可序列化 Serializalbe 接口存在于java.io
包中,構(gòu)成了 Java 序列化機(jī)制的核心实愚。它沒有任何方法, 在 Java 中也稱為標(biāo)記接口。 當(dāng)類實(shí)現(xiàn) java.io.Serializable
接口時(shí), 它將在 Java 中變得可序列化, 并指示編譯器使用 Java 序列化機(jī)制序列化此對(duì)象兔辅。
問題 3) 什么是 serialVersionUID 腊敲?如果你不定義這個(gè), 會(huì)發(fā)生什么?
我最喜歡的關(guān)于Java序列化的問題面試問題之一维苔。serialVersionUID 是一個(gè) private static final long
型 ID, 當(dāng)它被印在對(duì)象上時(shí), 它通常是對(duì)象的哈希碼,你可以使用 serialver
這個(gè) JDK 工具來查看序列化對(duì)象的 serialVersionUID碰辅。SerialVerionUID 用于對(duì)象的版本控制。 也可以在類文件中指定 serialVersionUID介时。不指定 serialVersionUID的后果是,當(dāng)你添加或修改類中的任何字段時(shí), 則已序列化類將無法恢復(fù), 因?yàn)闉樾骂惡团f序列化對(duì)象生成的 serialVersionUID 將有所不同没宾。Java 序列化過程依賴于正確的序列化對(duì)象恢復(fù)狀態(tài)的, ,并在序列化對(duì)象序列版本不匹配的情況下引發(fā) java.io.InvalidClassException
無效類異常,了解有關(guān) serialVersionUID 詳細(xì)信息,請(qǐng)參閱這篇文章, 需要 FQ凌彬。
問題 4) 序列化時(shí),你希望某些成員不要序列化?你如何實(shí)現(xiàn)它循衰?
另一個(gè)經(jīng)常被問到的序列化面試問題铲敛。這也是一些時(shí)候也問, 如什么是瞬態(tài) transient 變量会钝, 瞬態(tài)和靜態(tài)變量會(huì)不會(huì)得到序列化等伐蒋,所以,如果你不希望任何字段是對(duì)象的狀態(tài)的一部分, 然后聲明它靜態(tài)或瞬態(tài)根據(jù)你的需要, 這樣就不會(huì)是在 Java 序列化過程中被包含在內(nèi)。
問題 5) 如果類中的一個(gè)成員未實(shí)現(xiàn)可序列化接口迁酸,會(huì)發(fā)生什么情況先鱼?
關(guān)于Java 序列化過程的一個(gè)簡(jiǎn)單問題。如果嘗試序列化實(shí)現(xiàn)了可序列化接口的類的對(duì)象奸鬓,但該對(duì)象包含對(duì)不可序列化類的引用焙畔,則在運(yùn)行時(shí)將引發(fā)不可序列化異常 NotSerializableException
, 這就是為什么我始終將一個(gè)可序列化警報(bào)(在我的代碼注釋部分中)串远,作為代碼注釋最佳實(shí)踐之一闹蒜, 提示開發(fā)人員記住這一事實(shí), 在可序列化類中添加新字段時(shí)要注意抑淫。
問題 6) 如果類是可序列化的, 但其超類不是, 則反序列化后從超級(jí)類繼承的實(shí)例變量的狀態(tài)如何绷落?
Java 序列化過程僅在對(duì)象層級(jí)都是可序列化的類中繼續(xù), 即:實(shí)現(xiàn)了可序列化接口始苇, 如果從超級(jí)類沒有實(shí)現(xiàn)可序列化接口砌烁,則超級(jí)類繼承的實(shí)例變量的值將通過調(diào)用構(gòu)造函數(shù)初始化。且一旦構(gòu)造函數(shù)鏈啟動(dòng), 就不可能停止催式, 因此函喉, 即使層次結(jié)構(gòu)中更高的類成員變量實(shí)現(xiàn)了可序列化接口, 也將通過執(zhí)行構(gòu)造函數(shù)創(chuàng)建荣月,而不再是反序列化得到管呵。如你所見, 這個(gè)序列化面試問題看起來非常不易回答哺窄, 但如果你熟悉關(guān)鍵概念捐下, 則并不難。
問題 7) 是否可以自定義序列化過程, 或者是否可以覆蓋 Java 中的默認(rèn)序列化過程萌业?
答案是肯定的, 你可以坷襟。我們都知道,對(duì)于序列化一個(gè)對(duì)象需調(diào)用 ObjectOutputStream.writeObject(saveThisObject)
, 并用 ObjectInputStream.readObject()
讀取對(duì)象, 但 Java 虛擬機(jī)為你提供的還有一件事, 是定義這兩個(gè)方法。 如果在類中定義這兩種方法, 則 JVM 將調(diào)用這兩種方法, 而不是應(yīng)用默認(rèn)序列化機(jī)制生年。 你可以在此處通過執(zhí)行任何類型的預(yù)處理或后處理任務(wù)來自定義對(duì)象序列化和反序列化的行為婴程。需要注意的重要一點(diǎn)是要聲明這些方法為私有方法, 以避免被繼承、重寫或重載抱婉。 由于只有 Java 虛擬機(jī)可以調(diào)用類的私有方法, 你的類的完整性會(huì)得到保留, 并且 Java 序列化將正常工作档叔。在我看來, 這是在任何 Java 序列化面試中可以問的最好問題之一, 一個(gè)很好的后續(xù)問題是, 為什么要為你的對(duì)象提供自定義序列化表單桌粉?
問題 8) 假設(shè)新類的超級(jí)類實(shí)現(xiàn)可序列化接口, 如何避免新類被序列化?
這是在 Java 序列化中不好回答的問題衙四。如果類的 Super 類已經(jīng)在 Java 中實(shí)現(xiàn)了可序列化接口铃肯, 那么它在 Java 中已經(jīng)可以序列化, 因?yàn)槟悴荒苋∠涌诮旄椋豢赡苷嬲顾鼰o法序列化類, 但是有一種方法可以避免新類序列化缘薛。為了避免 Java 序列化,你需要在類中實(shí)現(xiàn) writeObject()
和 readObject()
方法, 并且需要從該方法引發(fā)不序列化異常NotSerializableException
。 這是自定義 Java 序列化過程的另一個(gè)好處, 如上述序列化面試問題中所述, 并且通常隨著面試進(jìn)度, 它作為后續(xù)問題提出卡睦。
問題 9) 在 Java 中的序列化和反序列化過程中使用哪些方法宴胧?
這是很常見的面試問題, 在序列化基本上面試官試圖知道: 你是否熟悉 readObject()
的用法、writeObject()
表锻、readExternal()
和 writeExternal()
恕齐。 Java 序列化由java.io.ObjectOutputStream
類完成。 該類是一個(gè)篩選器流, 它封裝在較低級(jí)別的字節(jié)流中, 以處理序列化機(jī)制瞬逊。要通過序列化機(jī)制存儲(chǔ)任何對(duì)象, 我們調(diào)用 ObjectOutputStream.writeObject(savethisobject)
, 并反序列化該對(duì)象, 我們稱之為 ObjectInputStream.readObject()
方法显歧。調(diào)用以 writeObject()
方法在 java 中觸發(fā)序列化過程。關(guān)于 readObject()
方法, 需要注意的一點(diǎn)很重要一點(diǎn)是, 它用于從持久性讀取字節(jié), 并從這些字節(jié)創(chuàng)建對(duì)象, 并返回一個(gè)對(duì)象, 該對(duì)象需要類型強(qiáng)制轉(zhuǎn)換為正確的類型确镊。
問題 10) 假設(shè)你有一個(gè)類,它序列化并存儲(chǔ)在持久性中士骤, 然后修改了該類以添加新字段。如果對(duì)已序列化的對(duì)象進(jìn)行反序列化, 會(huì)發(fā)生什么情況蕾域?
這取決于類是否具有其自己的 serialVersionUID拷肌。正如我們從上面的問題知道, 如果我們不提供 serialVersionUID, 則 Java 編譯器將生成它, 通常它等于對(duì)象的哈希代碼。通過添加任何新字段, 有可能為該類新版本生成的新 serialVersionUID 與已序列化的對(duì)象不同, 在這種情況下, Java 序列化 API 將引發(fā) java.io.InvalidClassException
, 因此建議在代碼中擁有自己的 serialVersionUID, 并確保在單個(gè)類中始終保持不變旨巷。
- Java序列化機(jī)制中的兼容更改和不兼容更改是什么巨缘?
真正的挑戰(zhàn)在于通過添加任何字段、方法或刪除任何字段或方法來更改類結(jié)構(gòu), 方法是使用已序列化的對(duì)象采呐。根據(jù) Java 序列化規(guī)范, 添加任何字段或方法都面臨兼容的更改和更改類層次結(jié)構(gòu)或取消實(shí)現(xiàn)的可序列化接口, 有些接口在非兼容更改下若锁。對(duì)于兼容和非兼容更改的完整列表, 我建議閱讀 Java 序列化規(guī)范。
12) 我們可以通過網(wǎng)絡(luò)傳輸一個(gè)序列化的對(duì)象嗎斧吐?
是的 ,你可以通過網(wǎng)絡(luò)傳輸序列化對(duì)象, 因?yàn)?Java 序列化對(duì)象仍以字節(jié)的形式保留, 字節(jié)可以通過網(wǎng)絡(luò)發(fā)送又固。你還可以將序列化對(duì)象存儲(chǔ)在磁盤或數(shù)據(jù)庫(kù)中作為 Blob。
13) 在 Java 序列化期間,哪些變量未序列化会通?
這個(gè)問題問得不同, 但目的還是一樣的, Java開發(fā)人員是否知道靜態(tài)和瞬態(tài)變量的細(xì)節(jié)口予。由于靜態(tài)變量屬于類, 而不是對(duì)象, 因此它們不是對(duì)象狀態(tài)的一部分, 因此在 Java 序列化過程中不會(huì)保存它們。由于 Java 序列化僅保留對(duì)象的狀態(tài),而不是對(duì)象本身涕侈。瞬態(tài)變量也不包含在 Java 序列化過程中, 并且不是對(duì)象的序列化狀態(tài)的一部分。在提出這個(gè)問題之后,面試官會(huì)詢問后續(xù)內(nèi)容, 如果你不存儲(chǔ)這些變量的值, 那么一旦對(duì)這些對(duì)象進(jìn)行反序列化并重新創(chuàng)建這些變量, 這些變量的值是多少煤辨?這是你們要考慮的裳涛。
9. 為什么Java中 wait 方法需要在 synchronized 的方法中調(diào)用端三?
另一個(gè)有難度的 Java 問題团赁,wait 和 notify怀挠。它們是在有 synchronized 標(biāo)記的方法或 synchronized 塊中調(diào)用的吞滞,因?yàn)?wait 和 nodify 需要監(jiān)視對(duì)其調(diào)用的 Object凸舵。
大多數(shù)Java開發(fā)人員都知道對(duì)象類的 wait()菇夸,notify() 和 notifyAll() 方法必須在 Java 中的 synchronized 方法或 synchronized 塊中調(diào)用, 但是我們想過多少次, 為什么在 Java 中 wait, notify 和 notifyAll 來自 synchronized 塊或方法?
最近這個(gè)問題在Java面試中被問到我的一位朋友械蹋,他思索了一下,并回答說: 如果我們不從同步上下文中調(diào)用 wait() 或 notify() 方法全度,我們將在 Java 中收到 IllegalMonitorStateException。
他的回答從實(shí)際效果上是正確的将鸵,但面試官對(duì)這樣的答案不會(huì)完全滿意勉盅,并希望向他解釋這個(gè)問題。面試結(jié)束后他和我討論了這個(gè)問題顶掉,我認(rèn)為他應(yīng)該告訴面試官關(guān)于 Java 中 wait()和 notify()之間的競(jìng)態(tài)條件草娜,如果我們不在同步方法或塊中調(diào)用它們就可能存在。
讓我們看看競(jìng)態(tài)條件如何在 Java 程序中產(chǎn)生痒筒。它也是流行的線程面試問題之一宰闰。因此,如果你正在準(zhǔn)備Java面試簿透,那么你應(yīng)該準(zhǔn)備這樣的問題移袍,并且可以真正幫助你的一本書是《Java程序員面試公開書》的。這是一本罕見的書老充,涵蓋了Java訪談的幾乎所有重要主題葡盗,例如核心Java,多線程啡浊,IO 和 NIO 以及 Spring 和 Hibernate 等框架觅够。
為什么要等待來自 Java中的 synchronized 方法的 wait方法為什么必須從 Java 中的 synchronized 塊或方法調(diào)用 ? 我們主要使用 wait()巷嚣,notify() 或 notifyAll() 方法用于 Java 中的線程間通信喘先。一個(gè)線程在檢查條件后正在等待,例如廷粒,在經(jīng)典的生產(chǎn)者 - 消費(fèi)者問題中窘拯,如果緩沖區(qū)已滿红且,則生產(chǎn)者線程等待喇勋,并且消費(fèi)者線程通過使用元素在緩沖區(qū)中創(chuàng)建空間后通知生產(chǎn)者線程骏庸。調(diào)用notify() 或 notifyAll() 方法向單個(gè)或多個(gè)線程發(fā)出一個(gè)條件已更改的通知,并且一旦通知線程離開 synchronized 塊,正在等待的所有線程開始獲取正在等待的對(duì)象鎖定砂轻,幸運(yùn)的線程在重新獲取鎖之后從 wait() 方法返回并繼續(xù)進(jìn)行。
讓我們將整個(gè)操作分成幾步斤吐,以查看Java 中 wait() 和 notify() 方法之間的競(jìng)爭(zhēng)條件的可能性搔涝,我們將使用Produce Consumer 線程示例更好地理解方案:
- Producer 線程測(cè)試條件(緩沖區(qū)是是否已滿)并確認(rèn)是否需要等待(如果發(fā)現(xiàn)緩沖區(qū)已滿則需要等待)。
- Consumer 線程在使用緩沖區(qū)中的元素后和措,設(shè)置條件庄呈。
- Consumer 線程調(diào)用 notify() 方法; 這是不會(huì)被聽到的,因?yàn)?Producer 線程還沒有等待派阱。
- Producer 線程調(diào)用 wait() 方法并進(jìn)入等待狀態(tài)诬留。
因此,由于競(jìng)態(tài)條件贫母,我們可能會(huì)丟失通知文兑,如果我們使用緩沖區(qū)或只使用一個(gè)元素,生產(chǎn)線程將永遠(yuǎn)等待腺劣,你的程序?qū)炱?/strong>绿贞。“在Java 同步中等待 notify 和 notifyAll 現(xiàn)在讓我們考慮如何解決這個(gè)潛在的競(jìng)態(tài)條件橘原?這個(gè)競(jìng)態(tài)條件通過使用 Java 提供的 synchronized 關(guān)鍵字和鎖定來解決籍铁。為了調(diào)用 wait(),notify() 或 notifyAll()趾断,必須獲得對(duì)我們調(diào)用方法的對(duì)象的鎖定拒名。由于 Java 中的 wait() 方法在等待之前釋放鎖定并在從 wait() 返回之前重新獲取鎖定方法,我們必須使用這個(gè)鎖來確保檢查條件(緩沖區(qū)是否已滿) 和設(shè)置條件 (從緩沖區(qū)獲取元素) 是原子的芋酌,這可以通過使用 synchronized 方法或塊來實(shí)現(xiàn)增显。
我不確定這是否是面試官實(shí)際期待的,但這個(gè)我認(rèn)為至少有意義隔嫡,請(qǐng)糾正我如果我錯(cuò)了甸怕,請(qǐng)告訴我們是否還有其他令人信服的理由調(diào)用 wait(),notify() 或 Java 中的 notifyAll() 方法腮恩。
總結(jié)一下梢杭,我們用 Java 中的 synchronized 方法或 synchronized 塊調(diào)用 Java 中的 wait(),notify() 或 notifyAll() 方法來避免:
- Java 會(huì)拋出
IllegalMonitorStateException
秸滴,如果我們不調(diào)用來自同步上下文的wait()武契,notify()或者notifyAll()方法。 - Javac 中 wait 和 notify 方法之間的任何潛在競(jìng)爭(zhēng)條件。
10.你能用Java覆蓋靜態(tài)方法嗎咒唆?如果我在子類中創(chuàng)建相同的方法是編譯時(shí)錯(cuò)誤届垫?
不,你不能在Java中覆蓋靜態(tài)方法全释,但在子類中聲明一個(gè)完全相同的方法不是編譯時(shí)錯(cuò)誤装处,這稱為隱藏在Java中的方法。
你不能覆蓋Java中的靜態(tài)方法浸船,因?yàn)榉椒ǜ采w基于運(yùn)行時(shí)的動(dòng)態(tài)綁定妄迁,靜態(tài)方法在編譯時(shí)使用靜態(tài)綁定進(jìn)行綁定。雖然可以在子類中聲明一個(gè)具有相同名稱和方法簽名的方法李命,看起來可以在Java中覆蓋靜態(tài)方法登淘,但實(shí)際上這是方法隱藏。Java不會(huì)在運(yùn)行時(shí)解析方法調(diào)用封字,并且根據(jù)用于調(diào)用靜態(tài)方法的 Object 類型黔州,將調(diào)用相應(yīng)的方法。這意味著如果你使用父類的類型來調(diào)用靜態(tài)方法阔籽,那么原始靜態(tài)將從父類中調(diào)用流妻,另一方面如果你使用子類的類型來調(diào)用靜態(tài)方法,則會(huì)調(diào)用來自子類的方法仿耽。簡(jiǎn)而言之合冀,你無法在Java中覆蓋靜態(tài)方法。如果你使用像Eclipse或Netbeans這樣的Java IDE项贺,它們將顯示警告靜態(tài)方法應(yīng)該使用類名而不是使用對(duì)象來調(diào)用君躺,因?yàn)殪o態(tài)方法不能在Java中重寫。
/**
*
* Java program which demonstrate that we can not override static method in Java.
* Had Static method can be overridden, with Super class type and sub class object
* static method from sub class would be called in our example, which is not the case.
*/
public class CanWeOverrideStaticMethod {
public static void main(String args[]) {
Screen scrn = new ColorScreen();
//if we can override static , this should call method from Child class
scrn.show(); //IDE will show warning, static method should be called from classname
}
}
class Screen{
/*
* public static method which can not be overridden in Java
*/
public static void show(){
System.out.printf("Static method from parent class");
}
}
class ColorScreen extends Screen{
/*
* static method of same name and method signature as existed in super
* class, this is not method overriding instead this is called
* method hiding in Java
*/
public static void show(){
System.err.println("Overridden static method in Child Class in Java");
}
}
輸出:
Static method from parent class
此輸出確認(rèn)你無法覆蓋 Java 中的靜態(tài)方法开缎,并且靜態(tài)方法基于類型信息而不是基于 Object 進(jìn)行綁定棕叫。如果要覆蓋靜態(tài)方法,則會(huì)調(diào)用子類或 ColorScreen 中的方法奕删。這一切都在討論中我們可以覆蓋 Java 中的靜態(tài)方法俺泣。我們已經(jīng)確認(rèn)沒有,我們不能覆蓋靜態(tài)方法完残,我們只能在Java中隱藏靜態(tài)方法伏钠。創(chuàng)建具有相同名稱和方法簽名的靜態(tài)方法稱為Java 隱藏方法。IDE 將顯示警告:"靜態(tài)方法應(yīng)該使用類名而不是使用對(duì)象來調(diào)用", 因?yàn)殪o態(tài)方法不能在 Java 中重寫谨设。
這些是我的核心 Java 面試問題和答案的清單熟掂。對(duì)于有經(jīng)驗(yàn)的程序員來說,一些 Java 問題看起來并不那么難扎拣,但對(duì)于Java中的中級(jí)和初學(xué)者來說赴肚,它們真的很難回答素跺。順便說一句,如果你在面試中遇到任何棘手的Java問題誉券,請(qǐng)與我們分享指厌。
Read more: http://www.java67.com/2012/09...
看完三件事??
如果你覺得這篇內(nèi)容對(duì)你還蠻有幫助,我想邀請(qǐng)你幫我三個(gè)小忙:
點(diǎn)贊踊跟,轉(zhuǎn)發(fā)踩验,有你們的 『點(diǎn)贊和評(píng)論』,才是我創(chuàng)造的動(dòng)力琴锭。
關(guān)注公眾號(hào) 『 java爛豬皮 』晰甚,不定期分享原創(chuàng)知識(shí)。
同時(shí)可以期待后續(xù)文章ing??
4.需要以上面試題內(nèi)容的關(guān)注作者簡(jiǎn)信私信【666】