10道虐心的Java面试题,被面试官虐哭了,同事一题都没答对
10道虐心的Java面試題,被面試官虐哭了,同事一題都沒答對
Java高級架構(gòu)
有一天,小王告訴我,他去一家公司面試 Java 崗,結(jié)果被面試官虐哭了。整整 10 道 Java 面試題,小王一道也沒答正確。
他沮喪地給我說,“哥,說點我的情況,你愿意聽嗎?我和一個女孩相處,女孩大我兩歲,我非科班。本來打算國慶換一家薪水高點的,好確認(rèn)關(guān)系。我經(jīng)驗不多,技術(shù)一般般,之前在一家外包公司,有一個甲方內(nèi)推,我就魯莽地把外包的工作辭了,結(jié)果沒想到面試被虐了,我擔(dān)心女朋友會不會因為我沒有工作和我分手。”
聽他這么一說,確實挺虐心的。后來我就安慰他,要他端正心態(tài),先把這些面試題整明白,然后繼續(xù)找工作,不要想太多。
借這個機(jī)會,我就把小王遇到的這 10 道面試題分享出來,希望能對其他小伙伴一些幫助。
第一題,下面這串代碼打印的結(jié)果是什么
public class Test {public static void main(String[] args) {System.out.println(Math.min(Double.MIN_VALUE, 0.0d));} }小王之所以沒答對這道題,是因為他覺得 Double.MIN_VALUE 和 Integer.MIN_VALUE 一樣,是個負(fù)數(shù),應(yīng)該小于 0.0d。
但事實上,Double. MIN_VALUE 和 Double. MAX_VALUE 一樣,都是正數(shù),Double. MIN_VALUE 的值是2^(-1074),直接打印 Double. MIN_VALUE 的話,輸出結(jié)果為?4.9E-324。
因此這道題的正確答案是輸出?0.0。
第二題,在 try 塊或者 catch 語句中執(zhí)行 return 語句或者?System.exit()?會發(fā)生什么,finally 語句還會執(zhí)行嗎?
小王之所以沒答對這道題,是因為在他的刻板印象中,finally 語句是無論如何都會執(zhí)行的。
但事實上,在 try 塊或者 catch 語句中執(zhí)行 return 語句時,finally 語句會執(zhí)行;在 try 塊或者 catch 語句中執(zhí)行?System.exit()?時,finally 語句不會執(zhí)行。
public class Test1 {public static void main(String[] args) {returnTryExec();returnCatchExec();exitTryExec();exitCatchExec();}public static int returnTryExec() {try {return 0;} catch (Exception e) {} finally {System.out.println("finally returnTryExec");return -1;}}public static int returnCatchExec() {try { } catch (Exception e) {return 0;} finally {System.out.println("finally returnCatchExec");return -1;}}public static void exitTryExec() {try {System.exit(0);} catch (Exception e) {} finally {System.out.println("finally exitTryExec");}}public static void exitCatchExec() {try { } catch (Exception e) {System.exit(0);} finally {System.out.println("finally exitCatchExec");}} }程序執(zhí)行結(jié)果如下所示:
finally returnTryExec finally returnCatchExec第三題,私有方法或者靜態(tài)方法能被重寫(override)嗎?
小王之所以沒答對這道題,是因為他不確定私有方法或者靜態(tài)方法與重寫之間的關(guān)系。
重寫的兩個方法名相同,方法參數(shù)的個數(shù)也相同;不過一個方法在父類中,另外一個在子類中。
class LaoWang{public void write() {System.out.println("老王寫了一本《基督山伯爵》");} } class XiaoWang extends LaoWang {@Overridepublic void write() {System.out.println("小王寫了一本《茶花女》");} } public class OverridingTest {public static void main(String[] args) {LaoWang wang = new XiaoWang();wang.write();} }父類 LaoWang 有一個?write()?方法(無參),方法體是寫一本《基督山伯爵》;子類 XiaoWang 重寫了父類的write()?方法(無參),但方法體是寫一本《茶花女》。
在 main 方法中,我們聲明了一個類型為 LaoWang 的變量 wang。在編譯期間,編譯器會檢查 LaoWang 類是否包含了?write()?方法,發(fā)現(xiàn) LaoWang 類有,于是編譯通過。在運行期間,new 了一個 XiaoWang 對象,并將其賦值給 wang,此時 Java 虛擬機(jī)知道 wang 引用的是 XiaoWang 對象,所以調(diào)用的是子類 XiaoWang 中的write()?方法而不是父類 LaoWang 中的?write()?方法,因此輸出結(jié)果為“小王寫了一本《茶花女》”。
而私有方法對子類是不可見的,它僅在當(dāng)前聲明的類中可見,private 關(guān)鍵字滿足了封裝的最高級別要求。另外,Java 中的私有方法是通過編譯期的靜態(tài)綁定的方式綁定的,不依賴于特定引用變量所持有的對象類型。
方法重寫適用于動態(tài)綁定,因此私有方法無法被重寫。
class LaoWang{public LaoWang() {write();read();}public void write() {System.out.println("老王寫了一本《基督山伯爵》");}private void read() {System.out.println("老王在讀《哈姆雷特》");} } class XiaoWang extends LaoWang {@Overridepublic void write() {System.out.println("小王寫了一本《茶花女》");}private void read() {System.out.println("小王在讀《威尼斯商人》");} } public class PrivateOrrideTest {public static void main(String[] args) {LaoWang wang = new XiaoWang();} }程序輸出結(jié)果如下所示:
小王寫了一本《茶花女》 老王在讀《哈姆雷特》在父類的構(gòu)造方法中,分別調(diào)用了?write()?和?read()?方法,write()?方法是 public 的,可以被重寫,因此執(zhí)行了子類的?write()?方法,read()?方法是私有的,無法被重寫,因此執(zhí)行的仍然是父類的?read()?方法。
和私有方法類似,靜態(tài)方法在編譯期也是通過靜態(tài)綁定的方式綁定的,不依賴于特定引用變量所持有的對象類型。方法重寫適用于動態(tài)綁定,因此靜態(tài)方法無法被重寫。
public class StaticOrrideTest {public static void main(String[] args) {Laozi zi = new Xiaozi();zi.write();} } class Laozi{public static void write() {System.out.println("老子寫了一本《基督山伯爵》");} } class Xiaozi extends Laozi {public static void write() {System.out.println("小子寫了一本《茶花女》");} }程序輸出結(jié)果如下所示:
老子寫了一本《基督山伯爵》引用變量 zi 的類型為 Laozi,所以?zi.write()?執(zhí)行的是父類中的?write()?方法。
靜態(tài)方法也叫類方法,直接通過類名就可以調(diào)用,通過對象調(diào)用的時候,IDE 會發(fā)出警告。
?
第四題,1.0/0.0?得到的結(jié)果是什么?會拋出異常嗎,還是會出現(xiàn)編譯錯誤?
小王之所以沒答對這道題,是因為他沒有深入研究過 double 類型和 int 類型的除法運算。
數(shù)字在 Java 中可以分為兩種,一種是整形,一種是浮點型。不太清楚的小伙伴先去研究一下數(shù)據(jù)類型。
當(dāng)浮點數(shù)除以 0 的時候,結(jié)果為 Infinity 或者 NaN。
System.out.println(1.0 / 0.0); // Infinity System.out.println(0.0 / 0.0); // NaNInfinity 的中文意思是無窮大,NaN 的中文意思是這不是一個數(shù)字(Not a Number)。
當(dāng)整數(shù)除以 0 的時候(10 / 0),會拋出異常:
Exception in thread "main" java.lang.ArithmeticException: / by zeroat com.itwanger.eleven.ArithmeticOperator.main(ArithmeticOperator.java:32)通常,我們在進(jìn)行整數(shù)的除法運算時,需要先判斷除數(shù)是否為 0,以免程序拋出異常。
第五題,Java 支持多重繼承嗎?
小王之所以沒答對這道題,是因為他知道,通過接口可以達(dá)到多重繼承的目的。
來定義兩個接口,Fly 會飛,Run 會跑。
public interface Fly {void fly(); } public interface Run {void run(); }然后讓一個類同時實現(xiàn)這兩個接口。
public class Pig implements Fly,Run{@Overridepublic void fly() {System.out.println("會飛的豬");}@Overridepublic void run() {System.out.println("會跑的豬");} }但說到多重繼承,討論的關(guān)鍵字是 extends,而非 implements。
Java 只支持單一繼承,是因為涉及到菱形問題。如果有兩個類共同繼承一個有特定方法的父類,那么該方法可能會被兩個子類重寫。然后,如果你決定同時繼承這兩個子類,那么在你調(diào)用該重寫方法時,編譯器不能識別你要調(diào)用哪個子類的方法。
?
類 C 同時繼承了類 A 各類 B,類 C 的對象在調(diào)用類 A 各類 B 中重寫的方法時,就不知道該調(diào)用類 A 的方法,還是類 B 的方法。
第六題,當(dāng)在 HashMap 中放入一個已經(jīng)存在的 key 時,會發(fā)生什么?
小王之所以沒答對這道題,是因為他沒有深入研究過 HashMap 的工作原理。
Hash,一般譯作“散列”,也有直接音譯為“哈希”的,這玩意什么意思呢?就是把任意長度的數(shù)據(jù)通過一種算法映射到固定長度的域上(散列值)。
再直觀一點,就是對一串?dāng)?shù)據(jù) wang 進(jìn)行雜糅,輸出另外一段固定長度的數(shù)據(jù) er——作為數(shù)據(jù) wang 的特征。我們通常用一串指紋來映射某一個人,別小瞧手指頭那么大點的指紋,在你所處的范圍內(nèi)很難找出第二個和你相同的(人的散列算法也好厲害,有沒有)。
對于任意兩個不同的數(shù)據(jù)塊,其散列值相同的可能性極小,也就是說,對于一個給定的數(shù)據(jù)塊,找到和它散列值相同的數(shù)據(jù)塊極為困難。再者,對于一個數(shù)據(jù)塊,哪怕只改動它的一個比特位,其散列值的改動也會非常的大——這正是 Hash 存在的價值!
大家應(yīng)該知道,HashMap 的底層數(shù)據(jù)結(jié)構(gòu)是一個數(shù)組,通過?hash()?方法來確定下標(biāo)。
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }當(dāng)我們放入一個鍵值對的時候,會先調(diào)用?hash()?方法對 key 進(jìn)行哈希算法,如果 key 是相同的,那么哈希后的結(jié)果也是相同的,意味著數(shù)組中的下標(biāo)是相同的,新放入的值就會覆蓋原來的值。
第七題,下面這段代碼將會打印出什么?
public class Test {public static void main(String[] args) {char[] chars = new char[]{'\u0097'};String str = new String(chars);byte[] bytes = str.getBytes();System.out.println(Arrays.toString(bytes));} }小王之所以沒答對這道題,是因為他沒有深入研究過字符編碼方面的一些知識。
在這段程序中,我們通過一個字符數(shù)組創(chuàng)建了一個字符串對象,然后調(diào)用 String 類的?getByte()?方法得到字節(jié)數(shù)組并將其打印到控制臺。
這道面試題考察的核心并不是最終的打印結(jié)果(結(jié)果是不確定的),而是字符編碼。通常情況下,我們在調(diào)用getBytes()?方法時,要指定編碼,比如說?str.getBytes(StandardCharsets.UTF_8)。
當(dāng)我們沒有指定編碼的時候,JDK 會調(diào)用平臺默認(rèn)的字符編碼,而不同的操作系統(tǒng),編碼不盡相同的,bytes 的結(jié)果也就會不同。
當(dāng)使用 UTF_8 時,結(jié)果為?-62, -105,當(dāng)使用 GB2312 時,結(jié)果為?63。
第八題,當(dāng)方法在父類中拋出?NullPointerException?時,是否可以使用拋出?RuntimeException?的方法來重寫它?
小王之所以沒答對這道題,是因為他被重寫(overriding)和重載(overloading)的概念搞混了。
方法重寫和方法重載時,方法名可以完全相同,但根本的不同在于方法重寫時發(fā)生在運行時,方法重載時發(fā)生在編譯時。
另外,方法重寫和方法重載時的規(guī)則也不盡相同。在 Java 中,不能重寫 private、static 和 final 方法,但可以重載它們。
我們來重點看一下方法重寫時的規(guī)則:
1)方法簽名必須相同,包括返回類型、參數(shù)的數(shù)量、參數(shù)的類型和參數(shù)的順序。
2)重寫后的方法不能拋出比父類中更高級別的異常。舉例來說,如果父類中的方法拋出的是 IOException,那么子類中重寫的方法不能拋出 Exception,可以是 IOException 的子類或者不拋出任何異常。這條規(guī)則只適用于可檢查的異常。
可檢查(checked)異常必須在源代碼中顯式地進(jìn)行捕獲處理,不檢查(unchecked)異常就是所謂的運行時異常,比如說 NullPointerException、
ArrayIndexOutOfBoundsException 之類的,不會在編譯器強(qiáng)制要求。
3)重寫后的方法訪問權(quán)限不能比父類中的方法低,比如說父類中的方法是 public,重寫后的方法就不能是 protected。
public class ExceptionDemo {public static void main(String[] args) {Super s = new Child();s.write();} } class Super{public void write() throws NullPointerException { } }class Child extends Super {@Overridepublic void write() throws RuntimeException { } }RuntimeException 和 NullPointerException 屬于不檢查異常,所以本題的答案是可以的。如果是可檢查異常的話,IDE 就會發(fā)出警告。
?
第九題,下面這段代碼使用了?compareTo()?方法,有問題嗎?
class Employee implements Comparable {private int id;@Overridepublic int compareTo(Object o) {Employee emp = (Employee) o;return this.id - emp.id;} }小王之所以沒答對這道題,是因為他想當(dāng)然地認(rèn)為 id 的都是正整數(shù)。
當(dāng)我們需要按照一定的規(guī)則進(jìn)行排序的時候,通常要實現(xiàn) Comparable 接口,并實現(xiàn) compareTo 方法,規(guī)則如下:
1)如果當(dāng)前對象小于另外一個對象,則 compareTo 方法必須返回負(fù)數(shù);如果當(dāng)前對象大于另外一個對象,則必須返回正數(shù);如果兩個對象相等,則返回零。
2)通常來說,compareTo 方法必須和 equals 方法一致,如果兩個對象通過 equals 方法判斷的結(jié)果為 true,那么 compareTo 必須返回零。
不過,JDK 中有一個反例,就是 BigDecimal。
BigDecimal bd1 = new BigDecimal("2.0"); BigDecimal bd2 = new BigDecimal("2.00");System.out.println("equals: " + bd1.equals(bd2)); System.out.println("compareTo: " + bd1.compareTo(bd2));輸出結(jié)果如下所示:
equals: false compareTo: 0這是因為 JDK 認(rèn)為 2.0 和 2.00 的精度不一樣,所以不能 equals,但值確實是相等的。
3)不能使用減法來比較整數(shù)值,因為減法的結(jié)果可能溢出。應(yīng)該使用?Integer.compareTo()?來進(jìn)行比較。如果你想通過減法操作來提高性能,必須得確保兩個操作數(shù)是正整數(shù),或者確保兩者相差的值小于 Integer.MAX_VALUE。
public class CompareDemo {public static void main(String[] args) {List<Employee> list = new ArrayList<>();list.add(new Employee(1));list.add(new Employee(Integer.MIN_VALUE));list.add(new Employee(Integer.MAX_VALUE));Collections.sort(list);System.out.println(list);} }class Employee implements Comparable {private int id;public Employee(int id) {this.id = id;}@Overridepublic int compareTo(Object o) {Employee emp = (Employee) o;return this.id - emp.id;}@Overridepublic String toString() {return "Employee{" +"id=" + id +'}';} }程序的輸出結(jié)果如下所示:
[Employee{id=1}, Employee{id=2147483647}, Employee{id=-2147483648}]排序就亂了。因為?Integer.MIN_VALUE - 1?變成了正數(shù)?2147483647。
第十題,StringBuffer 和 StringBuilder 之間有什么區(qū)別?
小王之所以沒答對這道題,是因為他覺得這道題太簡單了,結(jié)果說反了,大意了啊。
StringBuilder 是 JDK 1.5 之后引入的,它和 StringBuffer 最大的區(qū)別就在于它的一系列方法都是非同步的。
?
好了,以上就是小王這次面試遇到的 10 道虐心的面試題,本來最后一道是送分題,結(jié)果大意說反了,讓小王更加懊惱。年后是跳槽的高峰期,有打算的小伙伴要提前準(zhǔn)備了,希望大家都能夠順利面上心儀的崗位。
總結(jié)
以上是生活随笔為你收集整理的10道虐心的Java面试题,被面试官虐哭了,同事一题都没答对的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: POST /product/:id 获取
- 下一篇: 开始尝试淘宝直通车推广