一、Java物件導向面試題

趙廣陸發表於2020-11-17

1. 物件導向都有哪些特性以及你對這些特性的理解:

1)繼承:

  繼承是從已有類得到繼承資訊建立新類的過程。提供繼承資訊的類被稱為父類(超類、基類);得到繼承資訊的類被稱為子類(派生類)。繼承讓變化中的軟體系統有了一定的延續性,同時繼承也是封裝程式中可變因素的重要手段。

2) 封裝:

  通常認為封裝是把資料和運算元據的方法繫結起來,對資料的訪問只能通過已定義的介面。物件導向的本質就是將現實世界描繪成一系列完全自治、封閉的物件。我們在類中編寫的方法就是對實現細節的一種封裝;我們編寫一個類就是對資料和資料操作的封裝。可以說,封裝就是隱藏一切可隱藏的東西,只向外界提供最簡單的程式設計介面。

3) 多型性:

  多型性是指允許不同子型別的物件對同一訊息作出不同的響應。簡單的說就是用同樣的物件引用呼叫同樣的方法但是做了不同的事情。多型性分為編譯時的多型性和執行時的多型性。如果將物件的方法視為物件向外界提供的服務,那麼執行時的多型性可以解釋為:當 A 系統訪問 B 系統提供的服務時,B 系統有多種提供服務的方式,但一切對 A 系統來說都是透明的。方法過載(overload)實現的是編譯時的多型性(也稱為前繫結),而方法重寫(override)實現的是執行時的多型性(也稱為後繫結)。執行時的多型是物件導向最精髓的東西,要實現多型需要做兩件事:

1. 方法重寫(子類繼承父類並重寫父類中已有的或抽象的方法)

2. 物件造型(用父型別引用引用子型別物件,這樣同樣的引用呼叫同樣的方法就會根據子類物件的不同而表現出不同的行為)。

如果將物件的方法視為物件向外界提供的服務,那麼執行時的多型性可以解釋為:當 A 系統訪問 B 系統提供的服務時,B 系統有多種提供服務的方式,但一切對 A 系統來說都是透明的。這句話筆者看著較為困難,意思是什麼我訪問你的方法的時候,你有多個方法可以讓我選擇使用,而且都是可見的方法,我看下面介紹說方法的重寫是執行時的多型,也就是說父類訪問子類雖然方法相同但是會有不同的反饋,同時這些方法都是透明的.我是這樣理解的.這裡的B應該是子類,A應該是父類,父類訪問子類方法全是透明的,但是吧不知道原話作者的例子想表達什麼,既然是執行時的多型性,也就是說程式執行才可以實現,那麼就好理解,也就是說執行中匹配實際型別物件的方法的過程.

4)抽象:

抽象是將一類物件的共同特徵總結出來構造類的過程,包括資料抽象和行為抽象兩方面。抽象只關注物件有哪些屬性和行為,並不關注這些行為的細節是什麼。

注意:預設情況下物件導向有 3 大特性,封裝、繼承、多型,如果面試官問讓說出 4 大特性,那麼我們就把抽象加上去。

1 訪問許可權修飾符 public、private、protected, 以及不寫(預設)時的區別

該題目比較簡單,不同的許可權修飾符的區別見下表。

2 如何理解 clone 物件

2.1 為什麼要用 clone

  在實際程式設計過程中,我們常常要遇到這種情況:有一個物件 A,在某一時刻 A 中已經包含了一些有效值,此時可能會需要一個和 A 完全相同新物件 B,並且此後對 B 任何改動都不會影響到 A 中的值,也就是說,A 與 B 是兩個獨立的物件,但 B 的初始值是由 A 物件確定的。在 Java 語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實現 clone()方法是其中最簡單,也是最高效的手段。

2.2 new 一個物件的過程和 clone 一個物件的過程區別

  new 操作符的本意是分配記憶體。程式執行到 new 操作符時,首先去看 new 操作符後面的型別,因為知道了型別,才能知道要分配多大的記憶體空間。分配完記憶體之後,再呼叫建構函式,填充物件的各個域,這一步叫做物件的初始化構造方法返回後,一個物件建立完畢,可以把他的引用(地址)釋出到外部,在外部就可以使用這個引用操縱這個物件。

  clone 在第一步是和 new 相似的,都是分配記憶體,呼叫 clone 方法時,分配的記憶體和原物件(即呼叫 clone 方法的物件)相同,然後再使用原物件中對應的各個域,填充新物件的域,填充完成之後,clone 方法返回,一個新的相同的物件被建立,同樣可以把這個新物件的引用釋出到外部。

2.3 clone 物件的使用

2.3.1 複製物件和複製引用的區別

1. Person p = new Person(23, "zhang"); 
2. Person p1 = p;  
3. System.out.println(p); 
4. System.out.println(p1);

當 Person p1 = p;執行之後, 是建立了一個新的物件嗎? 首先看列印結果:

1.com.itheima.Person@2f9ee1ac 
2.com.itheima.Person@2f9ee1ac 

可以看出,列印的地址值是相同的,既然地址都是相同的,那麼肯定是同一個物件。p 和 p1 只是引用而已,他們

都指向了一個相同的物件 Person(23, “zhang”) 。 可以把這種現象叫做引用的複製。上面程式碼執行完成之後, 內

存中的情景如下圖所示:

而下面的程式碼是真真正正的克隆了一個物件。 

1.Person p = new Person(23, "zhang");   
2.Person p1 = (Person) p.clone();    
3.System.out.println(p);   
4.System.out.println(p1); 

從列印結果可以看出,兩個物件的地址是不同的,也就是說建立了新的物件, 而不是把原物件的地址賦給了一個
新的引用變數:

1. com.itheima.Person@2f9ee1ac 
2. com.itheima.Person@67f1fba0 

以上程式碼執行完成後, 記憶體中的情景如下圖所示

2.3.2 深拷貝和淺拷貝 

上面的示例程式碼中,Person 中有兩個成員變數,分別是 name 和 age, name 是 String 型別, age 是 int 類

型。程式碼非常簡單,如下所示: 

1.public class Person implements Cloneable{   
2.privatint age ;   
3.    private String name;   
4.    public Person(int age, String name) {   
5.        this.age = age;   
6.        this.name = name;   
7.    }    
8.    public Person() {}   
9.    public int getAge() {   
10.        return age;   
11.    }    
12.    public String getName() { 
13.        return name;   
14.    }    
15.    @Override  
16.    protected Object clone() throws CloneNotSupportedException {   
17.        return (Person)super.clone();   
18.    }   
19.}

由於age是基本資料型別, 那麼對它的拷貝沒有什麼疑議,直接將一個4位元組的整數值拷貝過來就行。但是name 是 String 型別的, 它只是一個引用, 指向一個真正的 String 物件,那麼對它的拷貝有兩種方式: 直接將原物件中 的 name 的引用值拷貝給新物件的 name 欄位, 或者是根據原 Person 物件中的 name 指向的字串物件建立一個 新的相同的字串物件,將這個新字串物件的引用賦給新拷貝的 Person 物件的 name 欄位。這兩種拷貝方式分別 叫做淺拷貝和深拷貝。深拷貝和淺拷貝的原理如下圖所示: 

下面通過程式碼進行驗證。如果兩個 Person 物件的 name 的地址值相同, 說明兩個物件的 name 都指向同一個 String 物件,也就是淺拷貝, 而如果兩個物件的 name 的地址值不同, 那麼就說明指向不同的 String 物件, 也就 是在拷貝Person物件的時候, 同時拷貝了name引用的String物件, 也就是深拷貝。驗證程式碼如下: 

1. Person p = new Person(23, "zhang"); 
2. Person p1 = (Person) p.clone(); 
3. String result = p.getName() == p1.getName() 
4.         ? "clone 是淺拷貝的" : "clone 是深拷貝的"; 
5. System.out.println(result);

列印結果為: 

6. clone 是淺拷貝的 

所以,clone方法執行的是淺拷貝, 在編寫程式時要注意這個細節。 
如何進行深拷貝: 
由上一節的內容可以得出如下結論:如果想要深拷貝一個物件,這個物件必須要實現Cloneable介面,實現clone
方法,並且在 clone 方法內部,把該物件引用的其他物件也要 clone 一份,這就要求這個被引用的物件必須也要實現
Cloneable介面並且實現clone方法。那麼,按照上面的結論,實現以下程式碼 Body類組合了Head類,要想深拷貝
Body類,必須在Body類的clone方法中將Head類也要拷貝一份。程式碼如下: 

1.static class Body implements Cloneable{   
2.    public Head head;   
3.    public Body() {}   
4.    public Body(Head head) {this.head = head;}   
5.    @Override  
6.    protected Object clone() throws CloneNotSupportedException {   
7.        Body newBody =  (Body) super.clone();   
8.        newBody.head = (Head) head.clone();   
9.        return newBody;   
10.    }   
11.}   
12.static class Head implements Cloneable{   13.    public  Face face;    14.    public Head() {}   
15.    @Override  
16.    protected Object clone() throws CloneNotSupportedException {   
17.        return super.clone();   
18.    }  }      
19.public static void main(String[] args) throws CloneNotSupportedException {    
20.    Body body = new Body(new Head(new Face()));   
21.    Body body1 = (Body) body.clone();   
22.    System.out.println("body == body1 : " + (body == body1) );   
23.    System.out.println("body.head == body1.head : " +  (body.head == body1.head));   24.}

列印結果為:

1. body == body1 : false 
2. body.head == body1.head : false 

 

相關文章