史上最难10道Java面试题!
點(diǎn)擊上方“朱小廝的博客”,選擇“設(shè)為星標(biāo)”
回復(fù)”1024“獲取獨(dú)家整理的學(xué)習(xí)資料
這是我收集的10個(gè)最棘手的Java面試問題列表。這些問題主要來自 Java 核心部分 ,不涉及 Java EE 相關(guān)問題。你可能知道這些棘手的 Java 問題的答案,或者覺得這些不足以挑戰(zhàn)你的 Java 知識(shí),但這些問題都是容易在各種 Java 面試中被問到的,而且包括我的朋友和同事在內(nèi)的許多程序員都覺得很難回答。
1.為什么等待和通知是在 Object 類而不是 Thread 中聲明的?
一個(gè)棘手的 Java 問題,如果 Java編程語言不是你設(shè)計(jì)的,你怎么能回答這個(gè)問題呢。Java編程的常識(shí)和深入了解有助于回答這種棘手的 Java 核心方面的面試問題。為什么 wait,notify 和 notifyAll 是在 Object 類中定義的而不是在 Thread 類中定義這是有名的 Java 面試問題,招2~4年經(jīng)驗(yàn)的到高級(jí) Java 開發(fā)人員面試都可能碰到。這個(gè)問題的好在它能反映了面試者對(duì)等待通知機(jī)制的了解, 以及他對(duì)此主題的理解是否明確。就像為什么 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 方法之間的差異,如果你還沒有讀過,你會(huì)覺得有趣。為何 wait,notify 和 notifyAll 屬于 Object 類? 為什么它們不應(yīng)該在 Thread 類中? 以下是我認(rèn)為有意義的一些想法:1) 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ī)制。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ì)象)
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):1)第一個(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)生的。2)對(duì)我來說第二個(gè)也是更有說服力的理由是,多重繼承確實(shí)使設(shè)計(jì)復(fù)雜化并在轉(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)算符重載。1)簡(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)算符的行為。2)避免編程錯(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ò)誤之源, 因此沒有多大意義。3)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ì)。4)讓開發(fā)工具處理更容易。這是在 Java 中不支持運(yùn)算符重載的另一個(gè)好處。省略運(yùn)算符重載使語言更容易處理,這反過來又更容易開發(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ì)象。如果你是印度板球迷,你可能能夠與我的下一句話聯(lián)系起來。字符串是Java的 VVS Laxman,即非常特殊的類。我還沒有看到一個(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程序員面試寶典(第4版) 》,這是高級(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 的:1)想象字符串池沒有使字符串不可變,它根本不可能,因?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)建字符串。2)字符串已被廣泛用作許多 Java 類的參數(shù),例如,為了打開網(wǎng)絡(luò)連接,你可以將主機(jī)名和端口號(hào)作為字符串傳遞,你可以將數(shù)據(jù)庫 URL 作為字符串傳遞, 以打開數(shù)據(jù)庫連接,你可以通過將文件名作為參數(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 類的任何行為。3)由于 String 是不可變的,它可以安全地共享許多線程,這對(duì)于多線程編程非常重要. 并且避免了 Java 中的同步問題,不變性也使得String 實(shí)例在 Java 中是線程安全的,這意味著你不需要從外部同步 String 操作。關(guān)于 String 的另一個(gè)要點(diǎn)是由截取字符串 SubString 引起的內(nèi)存泄漏,這不是與線程相關(guān)的問題,但也是需要注意的。4)為什么 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í)是相同的。5)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ù)很長時(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)建線程安全的單例?
這個(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中的單例模式存在很長時(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í)間卻不長. 如果感興趣, 你可以了解下構(gòu)建者設(shè)計(jì)模式和裝飾器設(shè)計(jì)模式。1) 枚舉單例易于書寫這是迄今為止最大的優(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)限。2) 枚舉單例自行處理序列化傳統(tǒng)單例的另一個(gè)問題是,一旦實(shí)現(xiàn)可序列化接口,它們就不再是 Singleton, 因?yàn)?readObject() 方法總是返回一個(gè)新實(shí)例, 就像 Java 中的構(gòu)造函數(shù)一樣。通過使用 readResolve() 方法, 通過在以下示例中替換 Singeton 來避免這種情況://readResolve?to?prevent?another?instance?of?Singletonprivate?Object?readResolve(){????return?INSTANCE;}如果 Singleton 類保持內(nèi)部狀態(tài), 這將變得更加復(fù)雜, 因?yàn)槟阈枰獦?biāo)記為 transient(不被序列化),但使用枚舉單例, 序列化由 JVM 進(jìn)行。3) 創(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è)條件之一。現(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ù)庫或內(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è)長期代價(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 無效類異常。問題 4) 序列化時(shí),你希望某些成員不要序列化?你如何實(shí)現(xiàn)它?另一個(gè)經(jīng)常被問到的序列化面試問題。這也是一些時(shí)候也問, 如什么是瞬態(tài) trasient 變量, 瞬態(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ì)象層次都是可序列化結(jié)構(gòu)中繼續(xù), 即實(shí)現(xiàn) Java 中的可序列化接口, 并且從超級(jí)類繼承的實(shí)例變量的值將通過調(diào)用構(gòu)造函數(shù)初始化, 在反序列化過程中不可序列化的超級(jí)類。一旦構(gòu)造函數(shù)鏈接將啟動(dòng), 就不可能停止, 因此, 即使層次結(jié)構(gòu)中較高的類實(shí)現(xiàn)可序列化接口, 也將執(zhí)行構(gòu)造函數(shù)。正如你從陳述中看到的, 這個(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 序列化中一個(gè)棘手的面試問題。如果類的 Super 類已經(jīng)在 Java 中實(shí)現(xiàn)了可序列化接口, 那么它在 Java 中已經(jīng)可以序列化, 因?yàn)槟悴荒苋∠涌? 它不可能真正使它無法序列化類, 但是有一種方法可以避免新類序列化。為了避免 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è)類中始終保持不變。11) 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ù)庫中作為 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)建這些變量, 這些變量的價(jià)值是多少?這是你們要考慮的。9. 為什么Java中 wait 方法需要在 synchronized 的方法中調(diào)用?
另一個(gè)棘手的核心 Java 問題,wait 和 notify。它們是在有 synchronized 標(biāo)記的方法或 synchronized 塊中調(diào)用的,因?yàn)?wait 和 modify 需要監(jiān)視對(duì)其上調(diào)用 wait 或 notify-get 的 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é)束后 他和我討論了同樣的問題,我認(rèn)為他應(yīng)該告訴面試官關(guān)于 Java 中 wait()和 notify()之間的競(jìng)態(tài)條件,如果我們不在同步方法或塊中調(diào)用它們就可能存在。讓我們看看競(jìng)態(tài)條件如何在Java程序中發(fā)生。它也是流行的線程面試問題之一,并經(jīng)常在電話和面對(duì)面的Java開發(fā)人員面試中出現(xià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)必須等待(找到緩沖區(qū)已滿)。
- Consumer 線程在使用緩沖區(qū)中的元素后設(shè)置條件。
- Consumer 線程調(diào)用 notify() 方法; 這是不會(huì)被聽到的,因?yàn)?Producer 線程還沒有等待。
- Producer 線程調(diào)用 wait() 方法并進(jìn)入等待狀態(tài)。
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中重寫。輸出:Static method from parent class此輸出確認(rèn)你無法覆蓋Java中的靜態(tài)方法,并且靜態(tài)方法基于類型信息而不是基于Object進(jìn)行綁定。如果要覆蓋靜態(tài)mehtod,則會(huì)調(diào)用子類或 ColorScreen 中的方法。這一切都在討論中我們可以覆蓋Java中的靜態(tài)方法。我們已經(jīng)確認(rèn)沒有,我們不能覆蓋靜態(tài)方法,我們只能在Java中隱藏靜態(tài)方法。創(chuàng)建具有相同名稱和mehtod簽名的靜態(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)與我們分享。譯者:Yujiaao來源:segmentfault.com/a/1190000019962661原文:http://t.cn/AiH7NCW1想知道更多?掃描下面的二維碼關(guān)注我
相關(guān)推薦:
《科普 | 明星公司之Netflix》
《看我如何作死 | 將CPU、IO打爆》
《看我如何作死 | 網(wǎng)絡(luò)延遲、丟包、中斷一個(gè)都沒落下》
《7102-2019年技術(shù)文全套整理,建議收藏》
《看我如何假死!》
《總結(jié)緩存使用過程中的幾種策略以及優(yōu)缺點(diǎn)組合分析》
加技術(shù)群入口(備注:技術(shù)):>>>Learn More<<
免費(fèi)資料入口(備注:1024):>>>Learn More<<
免費(fèi)星球入口:>>>Free<<<
點(diǎn)個(gè)"在看"唄^_^
總結(jié)
以上是生活随笔為你收集整理的史上最难10道Java面试题!的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker 容器基础技术
- 下一篇: Java程序员面试必备的一些流程图