1. 程式人生 > >Java面試題——繼承,多型

Java面試題——繼承,多型

一、面向物件的思想 Java是一門純粹的面向物件的語言。面向物件這種程式設計模式它將現實世界中的一切事物都看作是物件,例如,一個人是一個物件,汽車、飛機、小鳥等等,都是物件;它強調從物件出發,以物件為中心用人類的思維方式來認識和思考問題。每個物件都具有各自的狀態特徵(也可以稱為屬性)及行為特徵(方法),java就是通過物件之間行為的互動來解決問題的。 類是面向物件中一個重要的概念。類是具有相同屬性和行為特徵的物件的抽象,類是物件的概念模型,物件是類的一個例項,通過類來建立物件,同一類的所有物件具有相同的屬性和行為特徵。類具有三個基本特徵:封裝、繼承、多型。 封裝就是將物件的屬性和行為特徵包裝到一個程式單元(即類)中,把實現細節隱藏起來,通過公用的方法來展現類對外提供的功能,提高了類的內聚性,降低了物件之間的耦合性。 繼承是對原有類的拓展,舉例說明:我現在有一個Person類,但是我想要一個學生物件,他擁有Person類的所有屬性和方法,此外他還有學號屬性,及上課、寫作業等一些方法,我可以建立一個Student類,但是我不想重複寫Person類中已經有了的屬性和方法,那麼,此時我就可以用Student類繼承Person類,Student類就擁有了Person類裡的屬性和方法了,我只需要在Student類裡新增另外的新的屬性和方法就可以了。Person類就成為父類,Student類就稱為子類。父類和子類之間是一般和特殊的關係,子類是一種特殊的父類。此外,子類還可以通過重寫來改變父類中的方法,重寫可以改變方法的返回型別和訪問許可權,不能改變方法名稱。

多型是建立在繼承的基礎上的,是指子類型別的物件可以賦值給父類型別的引用變數,但執行時仍表現子類的行為特徵。也就是說,同一種類型的物件執行同一個方法時可以表現出不同的行為特徵。

java中的訪問修飾符: (1)public: 用public修飾的類、類屬變數及方法,包內及包外的任何類(包括子類和普通類)均可以訪問;   (2)protected: 用protected修飾的類、類屬變數及方法,包內的任何類及包外那些繼承了該類的子類才能訪問(此處稍後解釋),protected重點突出繼承;   (3)default: 如果一個類、類屬變數及方法沒有用任何修飾符(即沒有用public、protected及private中任何一種修飾),則其訪問許可權為default(預設訪問許可權)。預設訪問許可權的類、類屬變數及方法,包內的任何類(包括繼承了此類的子類)都可以訪問它,而對於包外的任何類都不能訪問它(包括包外繼承了此類的子類)。default重點突出包;   (4)private: 用private修飾的類、類屬變數及方法,只有本類可以訪問,而包內包外的任何類均不能訪問它。

1. public、private和protected對我們來說沒有任何異議。
  • 2. 頂層類只能用public訪問修飾符和default(預設)訪問修飾符修飾,其中用預設修飾符修飾的類(及沒有任何修飾符的類,如class B{})不能被其他包中的類繼承,這也說明了default(預設)訪問修飾符突出的是包許可權

 3. protected:本人做了一次實驗,發現在不同包的子類中,new一個父類物件,並用該父類物件去訪問父類中的用protected修飾的類屬變數和方法時不能訪問,而new一個子類物件時,子類物件可以訪問(說明protected修飾的類可以被其他包中的類繼承)。也可以在子類重寫父類的方法中使用super關鍵字呼叫。這豈不是和上面表格中的總結(紅色對勾)衝突了?本人也是百思不得其解。最後在網上找到了一個相對比較認可的解釋,如下: protected修飾符的修飾的成員變數和方法也稱為受保護的成員變數和方法, 受保護的成員變數和方法可以在本類或同一個包中的其它類(包括子類)中通過類的例項進行訪問,也可以被同一個包中的類或不同包中的類繼承,但是不能在不同包中的其它類(包括子類)中通過類的例項進行訪問。   4. 如果一個類使用public修飾,那該類的類名必須與他所在的原始檔名相同。一個.java原始檔中有且只有一個public類,頂層類只能用public和預設修飾符(即無修飾符)修飾;   5. final修飾的類不能被繼承,沒有子類。   6. abstract修飾的類不能被例項化,必須被子類繼承。類只要有一個抽象方法就必定是抽象類,但抽象類不一定要有抽象方法。 最終總結,就一句話:protected修飾符所修飾的類(這句話中指父類)屬成員變數和方法,只可以被子類訪問,而不管子類是不是和父類位於同一個包中。default修飾符所修飾的類屬成員變數和方法,只可被同一個包中的其他類訪問,而不管其他類是不是該類的子類。protected屬於子類限制修飾符,而default屬於包限制修飾符。

二、繼承,多型,向上轉型 由於Cat是繼承自它的父類Animal,所以Animal型別的引用是可以指向Cat型別的物件的。這就是“向上轉型”。

因為子類是對父類的一個改進和擴充,所以一般子類在功能上較父類更強大,屬性較父類更獨特, 定義一個父類型別的引用指向一個子類的物件既可以使用子類強大的功能,又可以抽取父類的共性。 所以,父類型別的引用可以呼叫父類中定義的所有屬性和方法,而對於子類中定義而父類中沒有的方法,父類引用是無法呼叫的;

那什麼是動態連結呢?當父類中的一個方法只有在父類中定義而在子類中沒有重寫的情況下,才可以被父類型別的引用呼叫; 對於父類中定義的方法,如果子類中重寫了該方法,那麼父類型別的引用將會呼叫子類中的這個方法,這就是動態連線。

/**
 *
 * 當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,
 * 但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法,
 * 但是它仍然要根據繼承鏈中方法呼叫的優先順序來確認方法
 *
 * 其實在繼承鏈中物件方法的呼叫存在一個優先順序:this.show(O)、super.show(O)、this.show((super)O)、super.show((super)O)。
 * @author Administrator
 *
 */
public class Polymorphic {
            public static void main(String[] args) {
                        A a1 = new A();
                        A a2 = new B();
                        B b = new B();
                        C c = new C();
                        D d = new D();
                        System.out.println("1--" + a1.show(b));//B繼承了A,相當於傳進去的是A,  A and A  第三級。。。
                        System.out.println("2--" + a1.show(c));//C繼承了B。B繼承了A      A and A   第三級。。
                        System.out.println("3--" + a1.show(d)); ///  A and D   第一級。。

                        System.out.println("4--" + a2.show(b));//引用是A,所以this代表A物件。  B and A  
                        System.out.println("5--" + a2.show(c));//  B and A   在第三級時,確定了要呼叫A中的show(A obj)的方法,但是,由於動態連線的問題,最終卻呼叫了子類重寫的方法
                        System.out.println("6--" + a2.show(d));// A and D

                        System.out.println("7--" + b.show(b)); //B and B
                        System.out.println("8--" + b.show(c)); //  B and B
                        System.out.println("9--" + b.show(d)); // A and D
            }
}
class A {
            public String show(D obj) {
                        return ("A and D");
            }
            public String show(A obj) {
                        return ("A and A");
            }
}
class B extends A {
            public String show(B obj) {
                        return ("B and B");
            }
            public String show(A obj) {
                        return ("B and A");
            }

/*         public String show(D obj) {
                        return ("B and D");
            }*/
}
class C extends B {
}
class D extends B {
}

我們分析5,a2.show(c),a2是A型別的引用變數,所以this就代表了A,a2.show(c),它在A類中找發現沒有找到,於是到A的超類中找(super),由於A沒有超類(Object除外),所以跳到第三級,也就是this.show((super)O),C的超類有B、A,所以(super)O為B、A,this同樣是A,這裡在A中找到了show(A obj),同時由於a2是B類的一個引用且B類重寫了show(A obj),因此最終會呼叫子類B類的show(A obj)方法,結果也就是B and A。

方法已經找到了但是我們這裡還是存在一點疑問,我們還是來看這句話:當超類物件引用變數引用子類物件時,被引用物件的型別而不是引用變數的型別決定了呼叫誰的成員方法,但是這個被呼叫的方法必須是在超類中定義過的,也就是說被子類覆蓋的方法。這我們用一個例子來說明這句話所代表的含義:a2.show(b);

對於多型,可以總結以下幾點:

一、使用父類型別的引用指向子類的物件; 二、該引用只能呼叫父類中定義的方法和變數; 三、如果子類中重寫了父類中的一個方法,那麼在呼叫這個方法的時候,將會呼叫子類中的這個方法;(動態連線、動態呼叫) 四、變數不能被重寫(覆蓋),”重寫“的概念只針對方法,如果在子類中”重寫“了父類中的變數,那麼在編譯時會報錯

1、抽象類: (1)、抽象類不可以被例項化; (2)、抽象類中可以沒有抽象方法;可以擁有非抽象方法或者屬性; (3)、介面與抽象類中的抽象方法不能具體實現; (4)、抽象類可以有建構函式; (5)、抽象類中的成員變數可以被不同的修飾符來修飾; (6)、 2、介面: (1)、介面中不能有普通資料成員,只能夠有靜態的不能被修改的資料成員,final表示全域性,static 表示不可修改,可以不用static final 修飾,會隱式的宣告為static和final ; (2)、介面中的方法一定是抽象方法,所以不用abstract修飾; (3)、介面不能且它裡面的方法只是一個宣告必須用public來修飾沒有具體實現的方法。

三、java中靜態屬性和和靜態方法的繼承問題 以及多型的實質 首先結論是:java中靜態屬性和和靜態方法可以被繼承,但是沒有被重寫(overwrite)而是被隱藏。

靜態方法和屬性是屬於類的,呼叫的時候直接通過類名.方法名完成的,不需繼承機制就可以呼叫如果子類裡面定義了靜態方法和屬性,那麼這時候父類的靜態方法 或屬性稱之為“隱藏”,你如果想要呼叫父類的靜態方法和屬性,直接通過父類名.方法名或變數名完成,至於是否繼承一說,子類是有繼承靜態方法和屬性,但是 跟例項方法和屬性不太一樣,存在“隱藏”的這種情況。

多型之所以能夠實現是依賴於 繼承 介面和 重寫 、過載(繼承和重寫最為關鍵)。有了繼承和重寫就可以 實現父類的引用可以指向不同子類的物件。重寫的功能是:“重寫”後子類的優先順序要高於父類的優先順序,但是“隱藏”是沒有這個優先順序之分的。

靜態屬性、靜態方法和非靜態的屬性都可以被 繼承 和 隱藏 而不能夠被重寫,因此不能實現多型,不能實現父類的引用可以指向不同子類的物件。 非靜態的方法可以被繼承和重寫,因此可以實現多型。

介面中的實現和類中的繼承是兩個不同的概念,因此不可以說實現介面的子類從介面那裡繼承了常量和方法

public class MyStatic {
             public static void main(String[] args)
                {
                    J j = new J();
                    System.out.println(j.name);
                    System.out.println(j.str);
                    j.sing();//輸出的結果都是父類中的非靜態屬性、靜態屬性和靜態方法,推出靜態屬性和靜態方法可以被繼承

                    G j1 = new J();
                    System.out.println(j1.name);
                    System.out.println(j1.str);
                    j1.sing();//結果同上,輸出的結果都是父類中的非靜態屬性、靜態屬性和靜態方法,推出靜態屬性和靜態方法可以被繼承

                    H h = new H();
                    System.out.println(h.name);
                    System.out.println(h.str);
                    h.sing();//結果都是子類的非靜態屬性,靜態屬性和靜態方法,這裡和非靜態屬性和非靜態類的繼承相同
                    h.run();//子類的改寫後非靜態方法


                    G h1 = new H();
                    System.out.println(h1.str);//結果是父類的靜態屬性,說明靜態屬性不可以被重寫,不能實現多型
                    System.out.println(h1.name);//結果是父類的非靜態屬性,說明非靜態屬性不可以被重寫,不能實現多型
                    h1.sing();//結果都是父類的靜態方法,說明靜態方法不可以被重寫,不能實現多型
                 h1.run();//結果是子類的改寫後的非靜態方法
                }
}
class G{
            public static String str = "靜態屬性";
    public String name = "非靜態屬性";
    public static void sing()
    {
        System.out.println("靜態方法");
    }

    public void run()
    {
        System.out.println("非靜態方法");
    }
}
class H extends G{
             public static String str = "H該改寫後的靜態屬性";
                public String name ="H改寫後的非靜態屬性";
                public static void sing()
                {
                    System.out.println("H改寫後的靜態方法");
                }
                public void run()
                {
                    System.out.println("H改寫後的非靜態方法");
                }
}
class J extends G{

}

--------------------- 本文來自 俺叫趙小邪 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/zhaojw_420/article/details/70477636?utm_source=copy