天天看點

幾道有挑戰性的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 的螢幕的思想。在Java中,所有對象都有一個螢幕。



線程在螢幕上等待,為執行等待,我們需要2個參數:      一個線程 一個螢幕(任何對象)在 Java 設計中,線程不能被指定,它總是運作目前代碼的線程。但是,我們可以指定螢幕(這是我們稱之為等待的對象)。這是一個很好的設計,因為如果我們可以讓任何其他線程在所需的螢幕上等待,這将導緻“入侵”,導緻在設計并發程式時會遇到困難。請記住,在 Java 中,所有在另一個線程的執行中侵入的操作都被棄用了(例如 stop 方法)。
           

2.為什麼Java中不支援多重繼承?我發現這個 Java 核心問題很難回答,因為你的答案可能不會讓面試官滿意,在大多數情況下,面試官正在尋找答案中的關鍵點,如果你提到這些關鍵點,面試官會很高興。在 Java 中回答這種棘手問題的關鍵是準備好相關主題, 以應對後續的各種可能的問題。

這是非常經典的問題,與為什麼 String 在 Java 中是不可變的很類似; 這兩個問題之間的相似之處在于它們主要是由 Java 創作者的設計決策使然。



為什麼Java不支援多重繼承, 可以考慮以下兩點:



1、第一個原因是圍繞鑽石?形繼承問題産生的歧義,考慮一個類 A 有 foo() 方法, 然後 B 和 C 派生自 A, 并且有自己的 foo() 實作,現在 D 類使用多個繼承派生自 B 和C,如果我們隻引用 foo(), 編譯器将無法決定它應該調用哪個 foo()。這也稱為 Diamond 問題,因為這個繼承方案的結構類似于菱形,見下圖: 



             A foo()    

           / \    

         /     \    
           

foo() B C foo()

\     /    

           \ /    

           D  foo()
           

即使我們删除鑽石的頂部 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的 VVS Laxman,即非常特殊的類。我還沒有看到一個沒有使用 String 編寫的 Java 程式。這就是為什麼對 String 的充分了解對于 Java 開發人員來說非常重要。



String 作為資料類型,傳輸對象和中間人角色的重要性和流行性也使這個問題在 Java 面試中很常見。



為什麼 String 在 Java 中是不可變的是 Java 中最常被問到的字元串通路問題之一,它首先讨論了什麼是 String,Java 中的 String 如何與 C 和 C++ 中的 String 不同,然後轉向在Java中什麼是不可變對象,不可變對象有什麼好處,為什麼要使用它們以及應該使用哪些場景。這個問題有時也會問:“為什麼 String 在 Java 中是 final 的”。



正如我所說,這個問題可能有很多可能的答案,而 String 類的唯一設計者可以放心地回答它。我在 Joshua Bloch 的 Effective Java 書中期待一些線索,但他也沒有提到它。我認為以下幾點解釋了為什麼 String 類在 Java 中是不可變的或 final 的:



1、想象字元串池沒有使字元串不可變,它根本不可能,因為在字元串池的情況下,一個字元串對象/文字,例如 “Test” 已被許多參考變量引用,是以如果其中任何一個更改了值,其他參數将自動受到影響,即假設
           

String A=“Test”;

String B=“Test”;

現在字元串 B 調用 “Test”.toUpperCase(), 将同一個對象改為“TEST”,是以 A 也是 “TEST”,這不是期望的結果。

2、字元串已被廣泛用作許多 Java 類的參數,例如,為了打開網絡連接配接,你可以将主機名和端口号作為字元串傳遞,你可以将資料庫 URL 作為字元串傳遞, 以打開資料庫連接配接,你可以通過将檔案名作為參數傳遞給 File I/O 類來打開 Java 中的任何檔案。如果 String 不是不可變的,這将導緻嚴重的安全威脅,我的意思是有人可以通路他有權授權的任何檔案,然後可以故意或意外地更改檔案名并獲得對該檔案的通路權限。由于不變性,你無需擔心這種威脅。這個原因也說明了,為什麼 String 在 Java 中是最終的,通過使 java.lang.String final,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,則不會列印數組的内容而是列印其記憶體位置。雖然不是一個真正的原因,但仍然有道理。 



 String strPassword =“Unknown”; 

char [] charPassword = new char [] {'U','n','k','w','o','n'}; 

System.out.println(“字元密碼:”+ strPassword);

System.out.println(“字元密碼:”+ charPassword);
           

輸出