更新時間:2019-08-05 16:36:55 來源:動力節點 瀏覽5394次
這是我收集的幾個最棘手的Java面試問題列表。這些問題主要來自Java核心部分,不涉及JavaEE相關問題。你可能知道這些棘手的Java問題的答案,或者覺得這些不足以挑戰你的Java知識,但這些問題都是容易在各種Java面試中被問到的,而且包括我的朋友和同事在內的許多程序員都覺得很難回答。
為什么等待和通知是在Object類而不是Thread中聲明的?
一個棘手的Java問題,如果Java編程語言不是你設計的,你怎么能回答這個問題呢。Java編程的常識和深入了解有助于回答這種棘手的Java核心方面的面試問題。
為什么wait,notify和notifyAll是在Object類中定義的而不是在Thread類中定義
這是有名的Java面試問題,招2~4年經驗的到高級Java開發人員面試都可能碰到。
這個問題的好在它能反映了面試者對等待通知機制的了解,以及他對此主題的理解是否明確。就像為什么Java中不支持多繼承或者為什么String在Java中是final的問題一樣,這個問題也可能有多個答案。
為什么在Object類中定義wait和notify方法,每個人都能說出一些理由。從我的面試經驗來看,wait和nofity仍然是大多數Java程序員最困惑的,特別是2到3年的開發人員,如果他們要求使用wait和notify,他們會很困惑。因此,如果你去參加Java面試,請確保對wait和notify機制有充分的了解,并且可以輕松地使用wait來編寫代碼,并通過生產者-消費者問題或實現阻塞隊列等了解通知的機制。
為什么等待和通知需要從同步塊或方法中調用,以及Java中的wait,sleep和yield方法之間的差異,如果你還沒有讀過,你會覺得有趣。為何wait,notify和notifyAll屬于Object類?為什么它們不應該在Thread類中?以下是我認為有意義的一些想法:
1)wait和notify不僅僅是普通方法或同步工具,更重要的是它們是Java中兩個線程之間的通信機制。對語言設計者而言,如果不能通過Java關鍵字(例如synchronized)實現通信此機制,同時又要確保這個機制對每個對象可用,那么Object類則是的正確聲明位置。記住同步和等待通知是兩個不同的領域,不要把它們看成是相同的或相關的。同步是提供互斥并確保Java類的線程安全,而wait和notify是兩個線程之間的通信機制。
2)每個對象都可上鎖,這是在Object類而不是Thread類中聲明wait和notify的另一個原因。
3)在Java中為了進入代碼的臨界區,線程需要鎖定并等待鎖定,他們不知道哪些線程持有鎖,而只是知道鎖被某個線程持有,并且他們應該等待取得鎖,而不是去了解哪個線程在同步塊內,并請求它們釋放鎖定。
4)Java是基于Hoare的監視器的思想(http://en.wikipedia.org/wiki/...。在Java中,所有對象都有一個監視器。
線程在監視器上等待,為執行等待,我們需要2個參數:
一個線程
一個監視器(任何對象)
在Java設計中,線程不能被指定,它總是運行當前代碼的線程。但是,我們可以指定監視器(這是我們稱之為等待的對象)。這是一個很好的設計,因為如果我們可以讓任何其他線程在所需的監視器上等待,這將導致“入侵”,導致在設計并發程序時會遇到困難。請記住,在Java中,所有在另一個線程的執行中侵入的操作都被棄用了(例如stop方法)。
為什么Java中不支持多重繼承?
我發現這個Java核心問題很難回答,因為你的答案可能不會讓面試官滿意,在大多數情況下,面試官正在尋找答案中的關鍵點,如果你提到這些關鍵點,面試官會很高興。在Java中回答這種棘手問題的關鍵是準備好相關主題,以應對后續的各種可能的問題。
這是非常經典的問題,與為什么String在Java中是不可變的很類似;這兩個問題之間的相似之處在于它們主要是由Java創作者的設計決策使然。
為什么Java不支持多重繼承,可以考慮以下兩點:
1)第一個原因是圍繞鉆石:gem:形繼承問題產生的歧義,考慮一個類A有foo()方法,然后B和C派生自A,并且有自己的foo()實現,現在D類使用多個繼承派生自B和C,如果我們只引用foo(),編譯器將無法決定它應該調用哪個foo()。這也稱為Diamond問題,因為這個繼承方案的結構類似于菱形,見下圖:
即使我們刪除鉆石的頂部A類并允許多重繼承,我們也將看到這個問題含糊性的一面。如果你把這個理由告訴面試官,他會問為什么C++可以支持多重繼承而Java不行。嗯,在這種情況下,我會試著向他解釋我下面給出的第二個原因,它不是因為技術難度,而是更多的可維護和更清晰的設計是驅動因素,雖然這只能由Java言語設計師確認,我們只是推測。維基百科鏈接有一些很好的解釋,說明在使用多重繼承時,由于鉆石問題,不同的語言地址問題是如何產生的。
2)對我來說第二個也是更有說服力的理由是,多重繼承確實使設計復雜化并在轉換、構造函數鏈接等過程中產生問題。假設你需要多重繼承的情況并不多,簡單起見,明智的決定是省略它。此外,Java可以通過使用接口支持單繼承來避免這種歧義。由于接口只有方法聲明而且沒有提供任何實現,因此只有一個特定方法的實現,因此不會有任何歧義。
為什么Java不支持運算符重載?
另一個類似棘手的Java問題。為什么C++支持運算符重載而Java不支持?有人可能會說+運算符在Java中已被重載用于字符串連接,不要被這些論據所欺騙。
與C++不同,Java不支持運算符重載。Java不能為程序員提供自由的標準算術運算符重載,例如+,-,*和/等。如果你以前用過C++,那么Java與C++相比少了很多功能,例如Java不支持多重繼承,Java中沒有指針,Java中沒有引用傳遞。另一個類似的問題是關于Java通過引用傳遞,這主要表現為Java是通過值還是引用傳參。雖然我不知道背后的真正原因,但我認為以下說法有些道理,為什么Java不支持運算符重載。
1)簡單性和清晰性。清晰性是Java設計者的目標之一。設計者不是只想復制語言,而是希望擁有一種清晰,真正面向對象的語言。添加運算符重載比沒有它肯定會使設計更復雜,并且它可能導致更復雜的編譯器,或減慢JVM,因為它需要做額外的工作來識別運算符的實際含義,并減少優化的機會,以保證Java中運算符的行為。
2)避免編程錯誤。Java不允許用戶定義的運算符重載,因為如果允許程序員進行運算符重載,將為同一運算符賦予多種含義,這將使任何開發人員的學習曲線變得陡峭,事情變得更加混亂。據觀察,當語言支持運算符重載時,編程錯誤會增加,從而增加了開發和交付時間。由于Java和JVM已經承擔了大多數開發人員的責任,如在通過提供垃圾收集器進行內存管理時,因為這個功能增加污染代碼的機會,成為編程錯誤之源,因此沒有多大意義。
3)JVM復雜性。從JVM的角度來看,支持運算符重載使問題變得更加困難。通過更直觀,更干凈的方式使用方法重載也能實現同樣的事情,因此不支持Java中的運算符重載是有意義的。與相對簡單的JVM相比,復雜的JVM可能導致JVM更慢,并為保證在Java中運算符行為的確定性從而減少了優化代碼的機會。
4)讓開發工具處理更容易。這是在Java中不支持運算符重載的另一個好處。省略運算符重載使語言更容易處理,這反過來又更容易開發處理語言的工具,例如IDE或重構工具。Java中的重構工具遠勝于C++。
為什么String在Java中是不可變的?
我最喜歡的Java面試問題,很棘手,但同時也非常有用。一些面試者也常問這個問題,為什么String在Java中是final的。
字符串在Java中是不可變的,因為String對象緩存在String池中。由于緩存的字符串在多個客戶之間共享,因此始終存在風險,其中一個客戶的操作會影響所有其他客戶。例如,如果一段代碼將String“Test”的值更改為“TEST”,則所有其他客戶也將看到該值。由于String對象的緩存性能是很重要的一方面,因此通過使String類不可變來避免這種風險。
同時,String是final的,因此沒有人可以通過擴展和覆蓋行為來破壞String類的不變性、緩存、散列值的計算等。String類不可變的另一個原因可能是由于HashMap。
由于把字符串作為HashMap鍵很受歡迎。對于鍵值來說,重要的是它們是不可變的,以便用它們檢索存儲在HashMap中的值對象。由于HashMap的工作原理是散列,因此需要具有相同的值才能正常運行。如果在插入后修改了String的內容,可變的String將在插入和檢索時生成兩個不同的哈希碼,可能會丟失Map中的值對象。
如果你是印度板球迷,你可能能夠與我的下一句話聯系起來。字符串是Java的VVSLaxman,即非常特殊的類。我還沒有看到一個沒有使用String編寫的Java程序。這就是為什么對String的充分理解對于Java開發人員來說非常重要。
String作為數據類型,傳輸對象和中間人角色的重要性和流行性也使這個問題在Java面試中很常見。
為什么String在Java中是不可變的是Java中最常被問到的字符串訪問問題之一,它首先討論了什么是String,Java中的String如何與C和C++中的String不同,然后轉向在Java中什么是不可變對象,不可變對象有什么好處,為什么要使用它們以及應該使用哪些場景。這個問題有時也會問:“為什么String在Java中是final的”。在類似的說明中,如果你正在準備Java面試,我建議你看看Java編程面試公開書,這是高級和中級Java程序員的優秀資源。它包含來自所有重要Java主題的問題,包括多線程,集合,GC,JVM內部以及Spring和Hibernate框架等。
正如我所說,這個問題可能有很多可能的答案,而String類的唯一設計者可以放心地回答它。我在JoshuaBloch的EffectiveJava書中期待一些線索,但他也沒有提到它。我認為以下幾點解釋了為什么String類在Java中是不可變的或final的:
1)想象字符串池沒有使字符串不可變,它根本不可能,因為在字符串池的情況下,一個字符串對象/文字,例如“Test”已被許多參考變量引用,因此如果其中任何一個更改了值,其他參數將自動受到影響,即假設
現在字符串B調用"Test".toUpperCase(),將同一個對象改為“TEST”,所以A也是“TEST”,這不是期望的結果。
下圖顯示了如何在堆內存和字符串池中創建字符串。
2)字符串已被廣泛用作許多Java類的參數,例如,為了打開網絡連接,你可以將主機名和端口號作為字符串傳遞,你可以將數據庫URL作為字符串傳遞,以打開數據庫連接,你可以通過將文件名作為參數傳遞給FileI/O類來打開Java中的任何文件。如果String不是不可變的,這將導致嚴重的安全威脅,我的意思是有人可以訪問他有權授權的任何文件,然后可以故意或意外地更改文件名并獲得對該文件的訪問權限。由于不變性,你無需擔心這種威脅。這個原因也說明了,為什么String在Java中是最終的,通過使java.lang.Stringfinal,Java設計者確保沒有人覆蓋String類的任何行為。
3)由于String是不可變的,它可以安全地共享許多線程,這對于多線程編程非常重要.并且避免了Java中的同步問題,不變性也使得String實例在Java中是線程安全的,這意味著你不需要從外部同步String操作。關于String的另一個要點是由截取字符串SubString引起的內存泄漏,這不是與線程相關的問題,但也是需要注意的。
4)為什么String在Java中是不可變的另一個原因是允許String緩存其哈希碼,Java中的不可變String緩存其哈希碼,并且不會在每次調用String的hashcode方法時重新計算,這使得它在Java中的HashMap中使用的HashMap鍵非常快。簡而言之,因為String是不可變的,所以沒有人可以在創建后更改其內容,這保證了String的hashCode在多次調用時是相同的。
5)String不可變的絕對最重要的原因是它被類加載機制使用,因此具有深刻和基本的安全考慮。如果String是可變的,加載“java.io.Writer”的請求可能已被更改為加載“mil.vogoon.DiskErasingWriter”.安全性和字符串池是使字符串不可變的主要原因。順便說一句,上面的理由很好回答另一個Java面試問題:“為什么String在Java中是最終的”。要想是不可變的,你必須是最終的,這樣你的子類不會破壞不變性。你怎么看?
為什么char數組比Java中的String更適合存儲密碼?
另一個基于String的棘手Java問題,相信我只有很少的Java程序員可以正確回答這個問題。這是一個真正艱難的核心Java面試問題,并且需要對String的扎實知識才能回答這個問題。
這是最近在Java面試中向我的一位朋友詢問的問題。他正在接受技術主管職位的面試,并且有超過6年的經驗。如果你還沒有遇到過這種情況,那么字符數組和字符串可以用來存儲文本數據,但是選擇一個而不是另一個很難。但正如我的朋友所說,任何與String相關的問題都必須對字符串的特殊屬性有一些線索,比如不變性,他用它來說服訪提問的人。在這里,我們將探討為什么你應該使用char[]存儲密碼而不是String的一些原因。
字符串:1)由于字符串在Java中是不可變的,如果你將密碼存儲為純文本,它將在內存中可用,直到垃圾收集器清除它.并且為了可重用性,會存在String在字符串池中,它很可能會保留在內存中持續很長時間,從而構成安全威脅。
由于任何有權訪問內存轉儲的人都可以以明文形式找到密碼,這是另一個原因,你應該始終使用加密密碼而不是純文本。由于字符串是不可變的,所以不能更改字符串的內容,因為任何更改都會產生新的字符串,而如果你使用char[],你就可以將所有元素設置為空白或零。因此,在字符數組中存儲密碼可以明顯降低竊取密碼的安全風險。
2)Java本身建議使用JPasswordField的getPassword()方法,該方法返回一個char[]和不推薦使用的getTex()方法,該方法以明文形式返回密碼,由于安全原因。應遵循Java團隊的建議,堅持標準而不是反對它。
3)使用String時,總是存在在日志文件或控制臺中打印純文本的風險,但如果使用Array,則不會打印數組的內容而是打印其內存位置。雖然不是一個真正的原因,但仍然有道理。
輸出
字符串密碼:Unknown
我還建議使用散列或加密的密碼而不是純文本,并在驗證完成后立即從內存中清除它。因此,在Java中,用字符數組用存儲密碼比字符串是更好的選擇。雖然僅使用char[]還不夠,還你需要擦除內容才能更安全。
如何使用雙重檢查鎖定在Java中創建線程安全的單例?
艱難的核心Java面試問題.這個Java問題也常被問:什么是線程安全的單例,你怎么創建它。好吧,在Java5之前的版本,使用雙重檢查鎖定創建單例Singleton時,如果多個線程試圖同時創建Singleton實例,則可能有多個Singleton實例被創建。從Java5開始,使用Enum創建線程安全的Singleton很容易。但如果面試官堅持雙重檢查鎖定,那么你必須為他們編寫代碼。記得使用volatile變量。
為什么枚舉單例在Java中更好
枚舉單例是使用一個實例在Java中實現單例模式的新方法。雖然Java中的單例模式存在很長時間,但枚舉單例是相對較新的概念,在引入Enum作為關鍵字和功能之后,從Java5開始在實踐中。本文與之前關于Singleton的內容有些相關,其中討論了有關Singleton模式的面試中的常見問題,以及10個Java枚舉示例,其中我們看到了如何通用枚舉可以。這篇文章是關于為什么我們應該使用Eeame作為Java中的單例,它比傳統的單例方法相比有什么好處等等。
Java枚舉和單例模式
Java中的枚舉單例模式是使用枚舉在Java中實現單例模式。單例模式在Java中早有應用,但使用枚舉類型創建單例模式時間卻不長.如果感興趣,你可以了解下構建者設計模式和裝飾器設計模式。
1)枚舉單例易于書寫
這是迄今為止最大的優勢,如果你在Java5之前一直在編寫單例,你知道,即使雙檢查鎖定,你仍可以有多個實例。雖然這個問題通過Java內存模型的改進已經解決了,從Java5開始的volatile類型變量提供了保證,但是對于許多初學者來說,編寫起來仍然很棘手。與同步雙檢查鎖定相比,枚舉單例實在是太簡單了。如果你不相信,那就比較一下下面的傳統雙檢查鎖定單例和枚舉單例的代碼:
在Java中使用枚舉的單例
這是我們通常聲明枚舉的單例的方式,它可能包含實例變量和實例方法,但為了簡單起見,我沒有使用任何實例方法,只是要注意,如果你使用的實例方法且該方法能改變對象的狀態的話,則需要確保該方法的線程安全。默認情況下,創建枚舉實例是線程安全的,但Enum上的任何其他方法是否線程安全都是程序員的責任。
你可以通過EasySingleton.INSTANCE來處理它,這比在單例上調用getInstance()方法容易得多。
具有雙檢查鎖定的單例示例
下面的代碼是單例模式中雙重檢查鎖定的示例,此處的getInstance()方法檢查兩次,以查看INSTANCE是否為空,這就是為什么它被稱為雙檢查鎖定模式,請記住,雙檢查鎖定是代理之前Java5,但Java5內存模型中易失變量的干擾,它應該工作完美。
你可以調用DoubleCheckedLockingSingleton.getInstance()來獲取此單例類的訪問權限。
現在,只需查看創建延遲加載的線程安全的Singleton所需的代碼量。使用枚舉單例模式,你可以在一行中具有該模式,因為創建枚舉實例是線程安全的,并且由JVM進行。
人們可能會爭辯說,有更好的方法來編寫Singleton而不是雙檢查鎖定方法,但每種方法都有自己的優點和缺點,就像我最喜歡在類加載時創建的靜態字段Singleton,如下面所示,但請記住,這不是一個延遲加載單例:
單例模式用靜態工廠方法
這是我最喜歡的在Java中影響Singleton模式的方法之一,因為Singleton實例是靜態的,并且最后一個變量在類首次加載到內存時初始化,因此實例的創建本質上是線程安全的。
你可以調用Singleton.getSingleton()來獲取此類的訪問權限。
2)枚舉單例自行處理序列化
傳統單例的另一個問題是,一旦實現可序列化接口,它們就不再是Singleton,因為readObject()方法總是返回一個新實例,就像Java中的構造函數一樣。通過使用readResolve()方法,通過在以下示例中替換Singeton來避免這種情況:
如果Singleton類保持內部狀態,這將變得更加復雜,因為你需要標記為transient(不被序列化),但使用枚舉單例,序列化由JVM進行。
3)創建枚舉實例是線程安全的
如第1點所述,因為Enum實例的創建在默認情況下是線程安全的,你無需擔心是否要做雙重檢查鎖定。
總之,在保證序列化和線程安全的情況下,使用兩行代碼枚舉單例模式是在Java5以后的世界中創建Singleton的最佳方式。你仍然可以使用其他流行的方法,如你覺得更好,歡迎討論。
編寫Java程序時,如何在Java中創建死鎖并修復它?
經典但核心Java面試問題之一。
如果你沒有參與過多線程并發Java應用程序的編碼,你可能會失敗。
如何避免Java線程死鎖?
如何避免Java中的死鎖?是Java面試的熱門問題之一,也是多線程的編程中的重口味之一,主要在招高級程序員時容易被問到,且有很多后續問題。盡管問題看起來非常基本,但大多數Java開發人員一旦你開始深入,就會陷入困境。
面試問題總是以“什么是死鎖:lock:?”開始
當兩個或多個線程在等待彼此釋放所需的資源(鎖定)并陷入無限等待即是死鎖。它僅在多任務或多線程的情況下發生。
如何檢測Java中的死鎖?
雖然這可以有很多答案,但我的版本是首先我會看看代碼,如果我看到一個嵌套的同步塊,或從一個同步的方法調用其他同步方法,或試圖在不同的對象上獲取鎖,如果開發人員不是非常小心,就很容易造成死鎖。
另一種方法是在運行應用程序時實際鎖定時找到它,嘗試采取線程轉儲,在Linux中,你可以通過kill-3命令執行此操作,這將打印應用程序日志文件中所有線程的狀態,并且你可以看到哪個線程被鎖定在哪個線程對象上。
你可以使用fastthread.io網站等工具分析該線程轉儲,這些工具允許你上載線程轉儲并對其進行分析。
另一種方法是使用jConsole或VisualVM,它將顯示哪些線程被鎖定以及哪些對象被鎖定。
如果你有興趣了解故障排除工具和分析線程轉儲的過程,我建議你看看UriahLevy在多元視覺(PluraIsight)上《分析Java線程轉儲》課程。旨在詳細了解Java線程轉儲,并熟悉其他流行的高級故障排除工具。
編寫一個將導致死鎖的Java程序?
一旦你回答了前面的問題,他們可能會要求你編寫代碼,這將導致Java死鎖。
這是我的版本之一
如果method1()和method2()都由兩個或多個線程調用,則存在死鎖的可能性,因為如果線程1在執行method1()時在Sting對象上獲取鎖,線程2在執行method2()時在Integer對象上獲取鎖,等待彼此釋放Integer和String上的鎖以繼續進行一步,但這永遠不會發生。
此圖精確演示了我們的程序,其中一個線程在一個對象上持有鎖,并等待其他線程持有的其他對象鎖。
你可以看到,Thread1需要Thread2持有的Object2上的鎖,而Thread2希望獲得Thread1持有的Object1上的鎖。由于沒有線程愿意放棄,因此存在死鎖,Java程序被卡住。
其理念是,你應該知道使用常見并發模式的正確方法,如果你不熟悉這些模式,那么JosePaumard《應用于并發和多線程的常見Java模式》是學習的好起點。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習