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