1. 程式人生 > >2015攜程JAVA工程師筆試題(基礎卻又沒多少人做對的面向物件面試題)

2015攜程JAVA工程師筆試題(基礎卻又沒多少人做對的面向物件面試題)

最近真的發現自己越來越懶了,雖然現在有點晚了,可是內疚完之後,還是得更新每個星期一篇的面試題,找了很多面試題,發現有些很基礎的就沒必要分享出來,最後找了這麼一篇經典的面試題,一開始我也還真做錯了,話不多說。來看下 2015 攜程的 JAVA 工程師的面試題

一、題目


public class Base
{
    private String baseName = "base";
    public Base()
    {
        callName();
    }

    public void callName()
    {
        System. out. println(baseName);
    }

    static
class Sub extends Base { private String baseName = "sub"; public void callName() { System. out. println (baseName) ; } } public static void main(String[] args) { Base b = new Sub(); } }

求這段程式的輸出值?

二、解題

因為本身一開始我也做錯了這道題,因此不好寫一開始的思考思路,我們就用最直接的方法來看下答案是什麼?

直接把程式執行,看輸出的結果:

直接執行輸出結果為null.png

可以看到,結果為 null 。為什麼呢?

我們在仔細的觀察一下題目,可以知道,這道題無非就是考察我們三個知識點,第一,類的載入機制以及類的初始化過程;第二,繼承的相關知識,其中這裡涉及到子類繼承父類的時候,同名的屬性不會覆蓋父類,只是會將父類的同名屬性隱藏;第三,多型性,多型性就是讓實現與介面進行分離,在這道題目中,在父類的構造方法中呼叫了虛擬函式造成多型

竟然我們上面就提到這個題目就是考察我們三個知識點,那麼我們就根據題目對這三個知識點進行逐一擊破

1.類載入的機制和程式執行的順序

我們通過 Debug 能很好的瞭解程式的執行順序,因為 new 了一個 Sub 物件,且 Sub 類中沒有重寫建構函式,因此會呼叫父類的建構函式,父類 Base 的建構函式中呼叫了 callName 方法,因此就在父類的 callName 方法中的輸出語句打一個斷點,最後因為子類的 Sub 重寫了 callName 方法, 因此也在子類中重寫的 callName 方法中打一個斷點。最後通過 debug 我們可以看出程式的執行順序

Debug檢視執行的順序.png

知道了程式的執行順序之後,我們還需知道一個知識點,那就是類的例項變數的初始化過程,也就是題目中成員變數 baseName 的初始化過程。

我們都知道,一個類一旦被載入連線初始化,它就可以隨時被使用了,程式可以訪問它的靜態欄位,呼叫靜態方法,或者建立它的例項。在 JAVA 程式中類可以被明確或者隱含地例項化有四種途徑:(1)明確使用 new 操作符;(2)呼叫 Class 或者 Constructor 物件的 newInstance() 方法;(3)呼叫任何現有物件的 clone() 方法;(4)或者通過 objectInputStream 類的 getObject() 方法反序列化。虛擬機器建立一個新的例項時,都需要在堆中為儲存物件的例項分配記憶體。所有在物件的類中和它的父類中宣告的變數(包括隱藏的例項變數)都要分配記憶體。一旦虛擬機器為新的物件準備好堆記憶體,它立即把例項變數初始化為預設的初始值。

2.繼承

題目中 Sub 類繼承了 Base 類,關於繼承,一個基本所有人都知道的知識點,不過這裡還是貼出來

Java保證了一個物件被初始化前其父類也必須被初始化。有下面機制來保證:Java強制要求任何類的建構函式中的第一句必須是呼叫父類建構函式或者是類中定義的其他建構函式。如果沒有建構函式,系統新增預設的無參建構函式,如果我們的建構函式中沒有顯示的呼叫父類的建構函式,那麼編譯器自動生成一個父類的無參建構函式

3.多型

父類中的建構函式呼叫了 callName 方法,在題目中是通過 new Sub() 物件,因此呼叫的是子類 Sub 類中的 callName 方法,因此當前的 this 是指 Sub 類中的。

好了,最後我們根據執行順序分析整個過程
1.Base b = new Sub();

在 main 方法中宣告父類變數b對子類的引用,JAVA類載入器將Base,Sub類載入到JVM;也就是完成了 Base 類和 Sub 類的初始化

2.JVM 為 Base,Sub 的的成員開闢記憶體空間且值均為 null

在初始化 Sub 物件前,首先 JAVA 虛擬機器就在堆區開闢記憶體並將子類 Sub 中的 baseName 和父類 Base 中的 baseName(已被隱藏)均賦為 null ,至於為什麼 Base 類中的 baseName 為什麼會被隱藏,上面的知識點也已經說明,就是子類繼承父類的時候,同名的屬性不會覆蓋父類,只是會將父類的同名屬性隱藏

3.呼叫父類的無參構造

呼叫 Sub 的建構函式,因為子類沒有重寫建構函式,預設呼叫無參的建構函式,呼叫了 super() 。

4.callName 在子類中被重寫,因此呼叫子類的 callName();

呼叫了父類的建構函式,父類的建構函式中呼叫了 callName 方法,此時父類中的 baseName 的值為 base,可是子類重寫了 callName 方法,且 呼叫父類 Base 中的 callName 是在子類 Sub 中呼叫的,因此當前的 this 指向的是子類,也就是說是實現子類的 callName 方法

5.呼叫子類的callName,列印baseName

實際上在new Sub()時,實際執行過程為:


public Sub(){
    super();
    baseName = "sub"; 
}

可見,在 baseName = “sub” 執行前,子類的 callName() 已經執行,所以子類的 baseName 為預設值狀態 null 。

時間也很晚了(2016年12月27日01:35:54),最後給出一個知識點就睡了

構造器的初始化順序大概是:父類靜態塊 ->子類靜態塊 ->父類初始化語句 ->父類構造函器 ->子類初始化語句 子類構造器。