更新時(shí)間:2020-02-06 10:25:45 來(lái)源:動(dòng)力節(jié)點(diǎn) 瀏覽2787次
為什么等待和通知是在Object類而不是Thread中聲明的?
一個(gè)棘手的Java問(wèn)題,如果Java編程語(yǔ)言不是你設(shè)計(jì)的,你怎么能回答這個(gè)問(wèn)題呢。Java編程的常識(shí)和深入了解有助于回答這種棘手的Java核心方面的面試問(wèn)題。
為什么wait,notify和notifyAll是在Object類中定義的而不是在Thread類中定義
這是有名的Java面試問(wèn)題,招2~4年經(jīng)驗(yàn)的到高級(jí)Java開(kāi)發(fā)人員面試都可能碰到。
這個(gè)問(wèn)題的好在它能反映了面試者對(duì)等待通知機(jī)制的了解,以及他對(duì)此主題的理解是否明確。就像為什么Java中不支持多繼承或者為什么String在Java中是final的問(wèn)題一樣,這個(gè)問(wèn)題也可能有多個(gè)答案。
為什么在Object類中定義wait和notify方法,每個(gè)人都能說(shuō)出一些理由。從我的面試經(jīng)驗(yàn)來(lái)看,wait和nofity仍然是大多數(shù)Java程序員最困惑的,特別是2到3年的開(kāi)發(fā)人員,如果他們要求使用wait和notify,他們會(huì)很困惑。因此,如果你去參加Java面試,請(qǐng)確保對(duì)wait和notify機(jī)制有充分的了解,并且可以輕松地使用wait來(lái)編寫(xiě)代碼,并通過(guò)生產(chǎn)者-消費(fèi)者問(wèn)題或?qū)崿F(xiàn)阻塞隊(duì)列等了解通知的機(jī)制。
為什么等待和通知需要從同步塊或方法中調(diào)用,以及Java中的wait,sleep和yield方法之間的差異,如果你還沒(méi)有讀過(guò),你會(huì)覺(jué)得有趣。為何wait,notify和notifyAll屬于Object類?為什么它們不應(yīng)該在Thread類中?以下是我認(rèn)為有意義的一些想法:
1)wait和notify不僅僅是普通方法或同步工具,更重要的是它們是Java中兩個(gè)線程之間的通信機(jī)制。對(duì)語(yǔ)言設(shè)計(jì)者而言,如果不能通過(guò)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ī)制。
2)每個(gè)對(duì)象都可上鎖,這是在Object類而不是Thread類中聲明wait和notify的另一個(gè)原因。
3)在Java中為了進(jìn)入代碼的臨界區(qū),線程需要鎖定并等待鎖定,他們不知道哪些線程持有鎖,而只是知道鎖被某個(gè)線程持有,并且他們應(yīng)該等待取得鎖,而不是去了解哪個(gè)線程在同步塊內(nèi),并請(qǐng)求它們釋放鎖定。
4)Java是基于Hoare的監(jiān)視器的思想。在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)致“入侵”,導(dǎo)致在設(shè)計(jì)并發(fā)程序時(shí)會(huì)遇到困難。請(qǐng)記住,在Java中,所有在另一個(gè)線程的執(zhí)行中侵入的操作都被棄用了(例如stop方法)。
如果你的Serializable類包含一個(gè)不可序列化的成員,會(huì)發(fā)生什么?你是如何解決的?
任何序列化該類的嘗試都會(huì)因NotSerializableException而失敗,但這可以通過(guò)在Java中為static設(shè)置瞬態(tài)(trancient)變量來(lái)輕松解決。
Java序列化相關(guān)的常見(jiàn)問(wèn)題
Java序列化是一個(gè)重要概念,但它很少用作持久性解決方案,開(kāi)發(fā)人員大多忽略了Java序列化API。根據(jù)我的經(jīng)驗(yàn),Java序列化在任何Java核心內(nèi)容面試中都是一個(gè)相當(dāng)重要的話題,在幾乎所有的網(wǎng)面試中,我都遇到過(guò)一兩個(gè)Java序列化問(wèn)題,我看過(guò)一次面試,在問(wèn)幾個(gè)關(guān)于序列化的問(wèn)題之后候選人開(kāi)始感到不自在,因?yàn)槿狈@方面的經(jīng)驗(yàn)。
他們不知道如何在Java中序列化對(duì)象,或者他們不熟悉任何Java示例來(lái)解釋序列化,忘記了諸如序列化在Java中如何工作,什么是標(biāo)記接口,標(biāo)記接口的目的是什么,瞬態(tài)變量和可變變量之間的差異,可序列化接口具有多少種方法,在Java中,Serializable和Externalizable有什么區(qū)別,或者在引入注解之后,為什么不用@Serializable注解或替換Serializalbe接口。
在本文中,我們將從初學(xué)者和高級(jí)別進(jìn)行提問(wèn),這對(duì)新手和具有多年Java開(kāi)發(fā)經(jīng)驗(yàn)的高級(jí)開(kāi)發(fā)人員同樣有益。
關(guān)于Java序列化的10個(gè)面試問(wèn)題
大多數(shù)商業(yè)項(xiàng)目使用數(shù)據(jù)庫(kù)或內(nèi)存映射文件或只是普通文件,來(lái)滿足持久性要求,只有很少的項(xiàng)目依賴于Java中的序列化過(guò)程。無(wú)論如何,這篇文章不是Java序列化教程或如何序列化在Java的對(duì)象,但有關(guān)序列化機(jī)制和序列化API的面試問(wèn)題,這是值得去任何Java面試前先看看以免讓一些未知的內(nèi)容驚到自己。
對(duì)于那些不熟悉Java序列化的人,Java序列化是用來(lái)通過(guò)將對(duì)象的狀態(tài)存儲(chǔ)到帶有.ser擴(kuò)展名的文件來(lái)序列化Java中的對(duì)象的過(guò)程,并且可以通過(guò)這個(gè)文件恢復(fù)重建Java對(duì)象狀態(tài),這個(gè)逆過(guò)程稱為deserialization。
什么是Java序列化
序列化是把對(duì)象改成可以存到磁盤(pán)或通過(guò)網(wǎng)絡(luò)發(fā)送到其他運(yùn)行中的Java虛擬機(jī)的二進(jìn)制格式的過(guò)程,并可以通過(guò)反序列化恢復(fù)對(duì)象狀態(tài).Java序列化API給開(kāi)發(fā)人員提供了一個(gè)標(biāo)準(zhǔn)機(jī)制,通過(guò)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中私有和包可見(jiàn)的屬性的封裝.
如何序列化
讓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)你通過(guò)實(shí)現(xiàn)添加接口來(lái)更改類的結(jié)構(gòu)時(shí),添加或刪除任何字段可能會(huì)破壞默認(rèn)序列化,這可以通過(guò)自定義二進(jìn)制格式使不兼容的可能性最小化,但仍需要大量的努力來(lái)確保向后兼容性。序列化如何限制你更改類的能力的一個(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í),你將獲得無(wú)效類異常InvalidClassException。
問(wèn)題1)Java中的可序列化接口和可外部接口之間的區(qū)別是什么?
這是Java序列化訪談中最常問(wèn)的問(wèn)題。下面是我的版本Externalizable給我們提供writeExternal()和readExternal()方法,這讓我們靈活地控制Java序列化機(jī)制,而不是依賴于Java的默認(rèn)序列化。正確實(shí)現(xiàn)Externalizable接口可以顯著提高應(yīng)用程序的性能。
問(wèn)題2)可序列化的方法有多少?如果沒(méi)有方法,那么可序列化接口的用途是什么?
可序列化Serializalbe接口存在于java.io包中,構(gòu)成了Java序列化機(jī)制的核心。它沒(méi)有任何方法,在Java中也稱為標(biāo)記接口。當(dāng)類實(shí)現(xiàn)java.io.Serializable接口時(shí),它將在Java中變得可序列化,并指示編譯器使用Java序列化機(jī)制序列化此對(duì)象。
問(wèn)題3)什么是serialVersionUID?如果你不定義這個(gè),會(huì)發(fā)生什么?
我最喜歡的關(guān)于Java序列化的問(wèn)題面試問(wèn)題之一。serialVersionUID是一個(gè)privatestaticfinallong型ID,當(dāng)它被印在對(duì)象上時(shí),它通常是對(duì)象的哈希碼,你可以使用serialver這個(gè)JDK工具來(lái)查看序列化對(duì)象的serialVersionUID。SerialVerionUID用于對(duì)象的版本控制。也可以在類文件中指定serialVersionUID。不指定serialVersionUID的后果是,當(dāng)你添加或修改類中的任何字段時(shí),則已序列化類將無(wú)法恢復(fù),因?yàn)闉樾骂惡团f序列化對(duì)象生成的serialVersionUID將有所不同。Java序列化過(guò)程依賴于正確的序列化對(duì)象恢復(fù)狀態(tài)的,,并在序列化對(duì)象序列版本不匹配的情況下引發(fā)java.io.InvalidClassException無(wú)效類異常,了解有關(guān)serialVersionUID詳細(xì)信息,請(qǐng)參閱這篇文章,需要FQ。
問(wèn)題4)序列化時(shí),你希望某些成員不要序列化?你如何實(shí)現(xiàn)它?
另一個(gè)經(jīng)常被問(wèn)到的序列化面試問(wèn)題。這也是一些時(shí)候也問(wèn),如什么是瞬態(tài)trasient變量,瞬態(tài)和靜態(tài)變量會(huì)不會(huì)得到序列化等,所以,如果你不希望任何字段是對(duì)象的狀態(tài)的一部分,然后聲明它靜態(tài)或瞬態(tài)根據(jù)你的需要,這樣就不會(huì)是在Java序列化過(guò)程中被包含在內(nèi)。
問(wèn)題5)如果類中的一個(gè)成員未實(shí)現(xiàn)可序列化接口,會(huì)發(fā)生什么情況?
關(guān)于Java序列化過(guò)程的一個(gè)簡(jiǎn)單問(wèn)題。如果嘗試序列化實(shí)現(xiàn)可序列化的類的對(duì)象,但該對(duì)象包含對(duì)不可序列化類的引用,則在運(yùn)行時(shí)將引發(fā)不可序列化異常NotSerializableException,這就是為什么我始終將一個(gè)可序列化警報(bào)(在我的代碼注釋部分中),代碼注釋最佳實(shí)踐之一,指示開(kāi)發(fā)人員記住這一事實(shí),在可序列化類中添加新字段時(shí)要注意。
問(wèn)題6)如果類是可序列化的,但其超類不是,則反序列化后從超級(jí)類繼承的實(shí)例變量的狀態(tài)如何?
Java序列化過(guò)程僅在對(duì)象層次都是可序列化結(jié)構(gòu)中繼續(xù),即實(shí)現(xiàn)Java中的可序列化接口,并且從超級(jí)類繼承的實(shí)例變量的值將通過(guò)調(diào)用構(gòu)造函數(shù)初始化,在反序列化過(guò)程中不可序列化的超級(jí)類。一旦構(gòu)造函數(shù)鏈接將啟動(dòng),就不可能停止,因此,即使層次結(jié)構(gòu)中較高的類實(shí)現(xiàn)可序列化接口,也將執(zhí)行構(gòu)造函數(shù)。正如你從陳述中看到的,這個(gè)序列化面試問(wèn)題看起來(lái)非常棘手和有難度,但如果你熟悉關(guān)鍵概念,則并不難。
問(wèn)題7)是否可以自定義序列化過(guò)程,或者是否可以覆蓋Java中的默認(rèn)序列化過(guò)程?
答案是肯定的,你可以。我們都知道,對(duì)于序列化一個(gè)對(duì)象需調(diào)用ObjectOutputStream.writeObject(saveThisObject),并用ObjectInputStream.readObject()讀取對(duì)象,但Java虛擬機(jī)為你提供的還有一件事,是定義這兩個(gè)方法。如果在類中定義這兩種方法,則JVM將調(diào)用這兩種方法,而不是應(yīng)用默認(rèn)序列化機(jī)制。你可以在此處通過(guò)執(zhí)行任何類型的預(yù)處理或后處理任務(wù)來(lái)自定義對(duì)象序列化和反序列化的行為。
需要注意的重要一點(diǎn)是要聲明這些方法為私有方法,以避免被繼承、重寫(xiě)或重載。由于只有Java虛擬機(jī)可以調(diào)用類的私有方法,你的類的完整性會(huì)得到保留,并且Java序列化將正常工作。在我看來(lái),這是在任何Java序列化面試中可以問(wèn)的最好問(wèn)題之一,一個(gè)很好的后續(xù)問(wèn)題是,為什么要為你的對(duì)象提供自定義序列化表單?
問(wèn)題8)假設(shè)新類的超級(jí)類實(shí)現(xiàn)可序列化接口,如何避免新類被序列化?
在Java序列化中一個(gè)棘手的面試問(wèn)題。如果類的Super類已經(jīng)在Java中實(shí)現(xiàn)了可序列化接口,那么它在Java中已經(jīng)可以序列化,因?yàn)槟悴荒苋∠涌?它不可能真正使它無(wú)法序列化類,但是有一種方法可以避免新類序列化。為了避免Java序列化,你需要在類中實(shí)現(xiàn)writeObject()和readObject()方法,并且需要從該方法引發(fā)不序列化異常NotSerializableException。這是自定義Java序列化過(guò)程的另一個(gè)好處,如上述序列化面試問(wèn)題中所述,并且通常隨著面試進(jìn)度,它作為后續(xù)問(wèn)題提出。
問(wèn)題9)在Java中的序列化和反序列化過(guò)程中使用哪些方法?
這是很常見(jiàn)的面試問(wèn)題,在序列化基本上面試官試圖知道:你是否熟悉readObject()的用法、writeObject()、readExternal()和writeExternal()。Java序列化由java.io.ObjectOutputStream類完成。該類是一個(gè)篩選器流,它封裝在較低級(jí)別的字節(jié)流中,以處理序列化機(jī)制。要通過(guò)序列化機(jī)制存儲(chǔ)任何對(duì)象,我們調(diào)用ObjectOutputStream.writeObject(savethisobject),并反序列化該對(duì)象,我們稱之為ObjectInputStream.readObject()方法。調(diào)用以writeObject()方法在java中觸發(fā)序列化過(guò)程。關(guān)于readObject()方法,需要注意的一點(diǎn)很重要一點(diǎn)是,它用于從持久性讀取字節(jié),并從這些字節(jié)創(chuàng)建對(duì)象,并返回一個(gè)對(duì)象,該對(duì)象需要類型強(qiáng)制轉(zhuǎn)換為正確的類型。
問(wèn)題10)假設(shè)你有一個(gè)類,它序列化并存儲(chǔ)在持久性中,然后修改了該類以添加新字段。如果對(duì)已序列化的對(duì)象進(jìn)行反序列化,會(huì)發(fā)生什么情況?
這取決于類是否具有其自己的serialVersionUID。正如我們從上面的問(wèn)題知道,如果我們不提供serialVersionUID,則Java編譯器將生成它,通常它等于對(duì)象的哈希代碼。通過(guò)添加任何新字段,有可能為該類新版本生成的新serialVersionUID與已序列化的對(duì)象不同,在這種情況下,Java序列化API將引發(fā)java.io.InvalidClassException,因此建議在代碼中擁有自己的serialVersionUID,并確保在單個(gè)類中始終保持不變。
11)Java序列化機(jī)制中的兼容更改和不兼容更改是什么?
真正的挑戰(zhàn)在于通過(guò)添加任何字段、方法或刪除任何字段或方法來(lái)更改類結(jié)構(gòu),方法是使用已序列化的對(duì)象。根據(jù)Java序列化規(guī)范,添加任何字段或方法都面臨兼容的更改和更改類層次結(jié)構(gòu)或取消實(shí)現(xiàn)的可序列化接口,有些接口在非兼容更改下。對(duì)于兼容和非兼容更改的完整列表,我建議閱讀Java序列化規(guī)范。
12)我們可以通過(guò)網(wǎng)絡(luò)傳輸一個(gè)序列化的對(duì)象嗎?
是的,你可以通過(guò)網(wǎng)絡(luò)傳輸序列化對(duì)象,因?yàn)镴ava序列化對(duì)象仍以字節(jié)的形式保留,字節(jié)可以通過(guò)網(wǎng)絡(luò)發(fā)送。你還可以將序列化對(duì)象存儲(chǔ)在磁盤(pán)或數(shù)據(jù)庫(kù)中作為Blob。
13)在Java序列化期間,哪些變量未序列化?
這個(gè)問(wèn)題問(wèn)得不同,但目的還是一樣的,Java開(kāi)發(fā)人員是否知道靜態(tài)和瞬態(tài)變量的細(xì)節(jié)。由于靜態(tài)變量屬于類,而不是對(duì)象,因此它們不是對(duì)象狀態(tài)的一部分,因此在Java序列化過(guò)程中不會(huì)保存它們。由于Java序列化僅保留對(duì)象的狀態(tài),而不是對(duì)象本身。瞬態(tài)變量也不包含在Java序列化過(guò)程中,并且不是對(duì)象的序列化狀態(tài)的一部分。在提出這個(gè)問(wèn)題之后,面試官會(huì)詢問(wèn)后續(xù)內(nèi)容,如果你不存儲(chǔ)這些變量的值,那么一旦對(duì)這些對(duì)象進(jìn)行反序列化并重新創(chuàng)建這些變量,這些變量的價(jià)值是多少?這是你們要考慮的。
以上就是動(dòng)力節(jié)點(diǎn)Java培訓(xùn)機(jī)構(gòu)小編介紹的“最新java面試題及答案整理”的內(nèi)容,希望對(duì)大家有幫助,如有疑問(wèn),請(qǐng)?jiān)诰€咨詢,有專業(yè)老師隨時(shí)為你服務(wù)。
相關(guān)推薦
相關(guān)閱讀
0基礎(chǔ) 0學(xué)費(fèi) 15天面授
有基礎(chǔ) 直達(dá)就業(yè)
業(yè)余時(shí)間 高薪轉(zhuǎn)行
工作1~3年,加薪神器
工作3~5年,晉升架構(gòu)
提交申請(qǐng)后,顧問(wèn)老師會(huì)電話與您溝通安排學(xué)習(xí)
初級(jí) 202925
初級(jí) 203221
初級(jí) 202629
初級(jí) 203743