- 相關推薦
來自投資銀行的20個Java面試題
下面這篇來自投資銀行的20個Java面試題是CN人才網(wǎng)小編驚險為您推薦,歡迎參考閱讀。
問題一:在多線程環(huán)境中使用HashMap會有什么問題?在什么情況下使用get()方法會產(chǎn)生無限循環(huán)?
HashMap本身沒有什么問題,有沒有問題取決于你是如何使用它的。比如,你在一個線程里初始化了一個HashMap然后在多個其他線程里對其進行讀取,這肯定沒有任何問題。有個例子就是使用HashMap來存儲系統(tǒng)配置項。當有多于一個線程對HashMap進行修改操作的時候才會真正產(chǎn)生問題,比如增加、刪除、更新鍵值對的時候。因為put()操作可以造成重新分配存儲大小(re-sizeing)的動作,因此有可能造成無限循環(huán)的發(fā)生,所以這時需要使用Hashtable或者ConcurrentHashMap,而后者更優(yōu)。
問題二:不重寫B(tài)ean的hashCode()方法是否會對性能帶來影響?
這個問題非常好,每個人可能都會有自己的體會。按照我掌握的知識來說,如果一個計算hash的方法寫得不好,直接的影響是,當向HashMap中添加元素的時候會更頻繁地造成沖突,因此最終增加了耗時。但是自從Java 8開始,這種影響不再像前幾個版本那樣顯著了,因為當沖突的發(fā)生超出了一定的限度之后,鏈表類的實現(xiàn)將會被替換成二叉樹(binary tree)實現(xiàn),這時你仍可以得到O(logN)的開銷,優(yōu)于鏈表類的O(n)。
問題三:對于一個不可修改的類,它的每個對象是不是都必須聲明成final的?
不盡然,因為你可以通過將成員聲明成非final且private,并且不要在除了構(gòu)造函數(shù)的其他地方來修改它。不要為它們提供setter方法,同時不會通過任何函數(shù)泄露出對此成員的引用。需要記住的是,把對象聲明成final僅僅保證了它不會被重新賦上另外一個值,你仍然可以通過此引用來修改引用對象的屬性。這一點是關鍵,面試官通常喜歡聽到你強調(diào)這一點。
問題四:String的substring()方法內(nèi)部是如何實現(xiàn)的?
又一個Java面試的好問題,你應該答出“substring方法通過原字符串創(chuàng)建了一個新的對象”,否則你的回答肯定是不能令人滿意的。這個問題也經(jīng)常被拿來測試應聘者對于substring()可能帶來的內(nèi)存泄漏風險是否有所了解。直到Java 1.7版本之前,substring會保存一份原字符串的字符數(shù)組的引用,這意味著,如果你從1GB大小的字符串里截取了5個字符,而這5個字符也會阻止那1GB內(nèi)存被回收,因為這個引用是強引用。
到了Java 1.7,這個問題被解決了,原字符串的字符數(shù)組已經(jīng)不再被引用,但是這個改變也使得substring()創(chuàng)建字符串的操作更加耗時,以前的開銷是O(1),現(xiàn)在最壞情況是O(n)。
substring
問題五:能否寫一個單例模式,并且保證實例的唯一性?
這算是Java一個比較核心的問題了,面試官期望你能知道在寫單例模式時應該對實例的初始化與否進行雙重檢查。記住對實例的聲明使用Volatile關鍵字,以保證單例模式是線程安全的。下面是一段示例,展示了如何用一種線程安全的方式實現(xiàn)了單例模式:
public class Singleton {
private static volatile Singleton _instance;
/**
* Double checked locking code on Singleton
* @return Singelton instance
*/
public static Singleton getInstance() {
if (_instance == null) {
synchronized (Singleton.class) {
if (_instance == null) {
_instance = new Singleton();
}
}
}
return _instance;
}
}
問題六:你在寫存儲過程或者在Java里調(diào)用存儲過程的時候如何來處理錯誤情況?
這是個很棘手的Java面試題,答案也并不固定。我的答案是,寫存儲過程的時候一旦有操作失敗,則一定要返回錯誤碼。但是在調(diào)用存儲過程的時候出錯的話捕捉SQLException卻是唯一能做的。
問題七:Executor.submit()和Executor.execute()這兩個方法有什么區(qū)別?
此問題來自另外一篇文章,《15個最流行的java多線程面試問題》,現(xiàn)在對熟練掌握并發(fā)技能的開發(fā)者的需求越來越大,因此這個問題也越來越引起大家的重視。答案是:前者返回一個Future對象,可以通過這個對象來獲得工作線程執(zhí)行的結(jié)果。
當我們考察異常處理的時候,又會發(fā)現(xiàn)另外一個不同。當你使用execute提交的任務拋出異常時,此異常將會交由未捕捉異常處理過程來處理(uncaught exception handler),當你沒有顯式指定一個異常處理器的話,默認情況下僅僅會通過System.err打印出錯誤堆棧。當你用submit來提交一個任務的時候,這個任務一旦拋出異常(無論是否是運行時異常),那這個異常是任務返回對象的一部分。對這樣一種情形,當你調(diào)用Future.get()方法的時候,這個方法會重新拋出這個異常,并且會使用ExecutionException進行包裝。
問題八:工廠模式和抽象工廠模式有何不同?
抽象工廠模式提供了多一級的抽象。不同的工廠類都繼承了同一個抽象工廠方法,但是卻根據(jù)工廠的類別創(chuàng)建不同的對象。例如,AutomobileFactory, UserFactory, RoleFactory都繼承了AbstractFactory,但是每個工廠類創(chuàng)建自己對應類型的對象。下面是工廠模式和抽象工廠模式對應的UML圖。
Factory
問題九:什么是單例模式?創(chuàng)建單例對象的時候是將整個方法都標記為synchronized好還是僅僅把創(chuàng)建的的語句標記為synchronized好?
在Java中,單例類是指那些在整個Java程序中只存在一份實例的類,例如java.lang.Runtime就是一個單例類。在Java 4版本及以前創(chuàng)建單例會有些麻煩,但是自從Java 5引入了Enum類型之后,事情就變得簡單了。可以去看看我的關于如何使用Enum來創(chuàng)建單例類的文章,同時再看看問題五來看看如何在創(chuàng)建單例類的時候進行雙重檢查。
問題十:能否寫一段用Java 4或5來遍歷一個HashMap的代碼?
事實上,用Java可以有四種方式來遍歷任何一個Map,一種是使用keySet()方法獲取所有的鍵,然后遍歷這些鍵,再依次通過get()方法來獲取對應的值。第二種方法可以使用entrySet()來獲取鍵值對的集合,然后使用for each語句來遍歷這個集合,遍歷的時候獲得的每個鍵值對已經(jīng)包含了鍵和值。這種算是一種更優(yōu)的方式,因為每輪遍歷的時候同時獲得了key和value,無需再調(diào)用get()方法,get()方法在那種如果bucket位置有一個巨大的鏈表的時候的性能開銷是O(n)。第三種方法是獲取entrySet之后用iterator依次獲取每個鍵值對。第四種方法是獲得key set之后用iterator依次獲取每個key,然后再根據(jù)key來調(diào)用get方法。
問題十一:你在什么時候會重寫hashCode()和equals()方法?
當你需要根據(jù)業(yè)務邏輯來進行相等性判斷、而不是根據(jù)對象相等性來判斷的時候你就需要重寫這兩個函數(shù)了。例如,兩個Employee對象相等的依據(jù)是它們擁有相同的emp_id,盡管它們有可能是兩個不同的Object對象,并且分別在不同的地方被創(chuàng)建。同時,如果你準備把它們當作HashMap中的key來使用的話,你也必須重寫這兩個方法。現(xiàn)在,作為Java中equals-hashcode的一個約定,當你重寫equals的時候必須也重寫hashcode,否則你會打破諸如Set, Map等集合賴以正常工作的約定。你可以看看我的另外一篇博文來理解這兩個方法之間的微妙區(qū)別與聯(lián)系。
問題十二:如果不重寫hashCode方法會有什么問題?
如果不重寫equals方法的話,equals和hashCode之間的約定就會被打破:當通過equals方法返回相等的兩個對象,他們的hashCode也必須一樣。如果不重寫hashCode方法的話,即使是使用equals方法返回值為true的兩個對象,當它們插入同一個map的時候,因為hashCode返回不同所以仍然會被插入到兩個不同的位置。這樣就打破了HashMap的本來目的,因為Map本身不允許存進去兩個key相同的值。當使用put方法插入一個的時候,HashMap會先計算對象的hashcode,然后根據(jù)它來找到存儲位置(bucket),然后遍歷此存儲位置上所有的Map.Entry對象來查看是否與待插入對象相同。如果沒有提供hashCode的話,這些就都做不到了。
問題十三:我們要同步整個getInstance()方法,還是只同步getInstance()方法中的關鍵部分?
答案是:僅僅同步關鍵部分(Critical Section)。這是因為,如果我們同步整個方法的話,每次有線程調(diào)用getInstance()方法的時候都會等待其他線程調(diào)用完成才行,即使在此方法中并沒有執(zhí)行對象的創(chuàng)建操作。換句話說,我們只需要同步那些創(chuàng)建對象的代碼,而創(chuàng)建對象的代碼只會執(zhí)行一次。一旦對象創(chuàng)建完成之后,根本沒有必要再對方法進行同步保護了。事實上,從性能上來說,對方法進行同步保護這種編碼方法非常要命,因為它會使性能降低10到20倍。下面是單例模式的UML圖。
getInstance
再補充一下,創(chuàng)建線程安全的單例對象有多種方法,你也可以順便提一下。
問題十四:HashMap,在調(diào)用get()方法的時候equals()和hashCode()方法都起了什么樣的作用?
這個問題算是對問題十二的補充,應聘者應該知道的是,一旦你提到了hashCode()方法,人們很可能要問HashMap是如何使用這個函數(shù)的。當你向HashMap插入一個key的時候,首先,這個對象的hashCode()方法會被調(diào)用,調(diào)用結(jié)果用來計算將要存儲的位置(bucket)。
因為某個位置上可能以鏈表的方式已經(jīng)包含了多個Map.Entry對象,所以HashMap會使用equals()方法來將此對象與所有這些Map.Entry所包含的key進行對比,以確定此key對象是否已經(jīng)存在。
問題十五:在Java中如何避免死鎖?
你可以通過打破互相等待的局面來避免死鎖。為了達到這一點,你需要在代碼中合理地安排獲取和釋放鎖的順序。如果獲得鎖的順序是固定的,并且獲得的順序和釋放的順序剛好相反的話,就不會產(chǎn)生出現(xiàn)死鎖的條件了。
問題十六:創(chuàng)建字符串對象的時候,使用字面值和使用new String()構(gòu)造器這兩種方式有什么不同?
當我們使用new String構(gòu)造器來創(chuàng)建字符串的時候,字符串的值會在堆中創(chuàng)建,而不會加入JVM的字符串池中。相反,使用字面值創(chuàng)建的String對象會被放入堆的PermGen段中。例如:
String str=new String(“Test”);
這句代碼創(chuàng)建的對象str不會放入字符串池中,我們需要顯式調(diào)用String.intern()方法來將它放入字符串池中。僅僅當你使用字面值創(chuàng)建字符串時,Java才會自動將它放入字符串池中,比如:String s=”Test”。順便提一下,這里有個容易被忽視的地方,當我們將參數(shù)“Test”傳入構(gòu)造器的時候,這個參數(shù)是個字面值,因此它也會在字符串池中保存另外一份。想了解更多關于字面值字符串和字符串對象之間的差別,請看這篇文章。
下圖很好地解釋了這種差異。
newString
問題十七:什么是不可修改對象(Immutable Object)?你能否寫一個例子?
不可修改對象是那些一旦被創(chuàng)建就不能修改的對象。對這種對象的任何改動的后果都是會創(chuàng)建一個新的對象,而不是在原對象本身做修改。例如Java中的String類就是不可修改的。大多數(shù)這樣的類通常都是final類型的,因為這樣可以避免自己被繼承繼而被覆蓋方法,在覆蓋的方法里,不可修改的特性就難以得到保證了。你通常也可以通過將類的成員設置成private但是非final的來獲得同樣的效果。
另外,你同樣要保證你的類不要通過任何方法暴露成員,特別是那些可修改類型的成員。同樣地,當你的方法接收客戶類傳入的可修改對象的話,你應該使用一個復制的對象來防止客戶代碼來修改這個剛傳入的可修改類。比如,傳入java.util.Date對象的話,你應該自己使用clone()方法來獲得一個副本。
當你通過類函數(shù)返回一個可修改對象的時候,你也要采取類似的防護措施,返回一個類成功的副本,防止客戶代碼通過此引用修改了成員對象的屬性。千萬不要直接把你的可修改成員直接返回給客戶代碼。
問題十八:如何在不使用任何分析工具的情況下用最簡單的方式計算某個方法的執(zhí)行所花費的時間?
在執(zhí)行此方法之前和之后獲取一個系統(tǒng)時間,取這兩個時間的差值,即可得到此方法所花費的時間。
需要注意的是,如果執(zhí)行此方法花費的時間非常短,那么得到的時間值有可能是0ms。這時你可以在一個計算量比較大的方法上試一下效果。
long start=System.currentTimeMillis();
method();
long end=System.currentTimeMillis();
System.out.println("Time taken for execution is "+(end-start));
問題十九:當你要把某個類作為HashMap的key使用的話,你需要重寫這個類的哪兩個方法?
為了使類可以在HashMap或Hashtable中作為key使用,必須要實現(xiàn)這個類自己的equals()和hashCode()方法。具體請參考問題十四。
問題二十:你如何阻止客戶代碼直接初始化你的類的構(gòu)造方法?例如,你有一個名為Cache的接口和兩個具體的實現(xiàn)類MemoryCache和DiskCache,你如何保證這兩個類禁止客戶代碼用new關鍵字來獲取它們的實例?
我把這最后一個問題留給你做練習吧,你可以在我給出答案之前好好思索一下。我確信你能夠找到正確的方法的,因為這是將類的實現(xiàn)掌控在自己手中的一個重要的方法,同時也能為以后的維護提供巨大的好處。
【來自投資銀行的20個Java面試題】相關文章:
Java經(jīng)典面試題12-29
Java面試題01-22
java學習:Java面試題和答案07-17
Java框架面試題07-16
瞬聯(lián)java面試題03-26
java線程面試題匯總02-22
2016高薪Java面試題02-20
java工程師面試題09-25
阿里巴巴java面試題07-31
java開發(fā)面試題型與技巧07-18