Java類與類之間的繼承關係

2020-09-04 Java數據全能者

對於面向對象的程序設計語言來說,類毫無疑問是其最重要的基礎。抽象、封裝、繼承、多態這四大特性都離不開類,只有存在類,才能體現面向對象編程的特點,今天我們就來了解一些類與繼承的相關知識。首先,我們講述一下與類的初始化相關的東西,然後再從幾個方面闡述繼承這一大特性。以下是本文的目錄大綱:

  一.你了解類嗎?

  二.你了解繼承嗎?

  三.常見的面試筆試題

  若有不正之處,請多多諒解並歡迎批評指正。

一.你了解類嗎?

  在Java中,類文件是以.java為後綴的代碼文件,在每個類文件中最多只允許出現一個public類,當有public類的時候,類文件的名稱必須和public類的名稱相同,若不存在public,則類文件的名稱可以為任意的名稱(當然以數字開頭的名稱是不允許的)。

  在類內部,對於成員變量,如果在定義的時候沒有進行顯示的賦值初始化,則Java會保證類的每個成員變量都得到恰當的初始化:

  1)對於 char、short、byte、int、long、float、double等基本數據類型的變量來說會默認初始化為0(boolean變量默認會被初始化為false);

  2)對於引用類型的變量,會默認初始化為null。

  如果沒有顯示地定義構造器,則編譯器會自動創建一個無參構造器,但是要記住一點,如果顯示地定義了構造器,編譯器就不會自動添加構造器。注意,所有的構造器默認為static的。

  下面我們著重講解一下 初始化 順序:

  當程序執行時,需要生成某個類的對象,Java執行引擎會先檢查是否加載了這個類,如果沒有加載,則先執行類的加載再生成對象,如果已經加載,則直接生成對象。

  在類的加載過程中,類的static成員變量會被初始化,另外,如果類中有static語句塊,則會執行static語句塊。static成員變量和static語句塊的執行順序同代碼中的順序一致。記住,在Java中,類是按需加載,只有當需要用到這個類的時候,才會加載這個類,並且只會加載一次。看下面這個例子就明白了:

public class Test { public static void main(String[] args) throws ClassNotFoundException { Bread bread1 = new Bread(); Bread bread2 = new Bread(); }} class Bread { static{ System.out.println(&34;); } public Bread() { System.out.println(&34;); }}

  運行這段代碼就會發現&34;只會被列印一次。

  在生成對象的過程中,會先初始化對象的成員變量,然後再執行構造器。也就是說類中的變量會在任何方法(包括構造器)調用之前得到初始化,即使變量散步於方法定義之間。

public class Test { public static void main(String[] args) { new Meal(); }} class Meal { public Meal() { System.out.println(&34;); } Bread bread = new Bread();} class Bread { public Bread() { System.out.println(&34;); }}    Bread bread = new Bread();} class Bread {         public Bread() {        System.out.println(&34;);    }}

  輸出結果為:

breadmeal


二.你了解繼承嗎?

  繼承是所有OOP語言不可缺少的部分,在java中使用extends關鍵字來表示繼承關係。當創建一個類時,總是在繼承,如果沒有明確指出要繼承的類,就總是隱式地從根類Object進行繼承。比如下面這段代碼:

class Person { public Person() { }} class Man extends Person { public Man() { }}

  類Man繼承於Person類,這樣一來的話,Person類稱為父類(基類),Man類稱為子類(導出類)。如果兩個類存在繼承關係,則子類會自動繼承父類的方法和變量,在子類中可以調用父類的方法和變量。在java中,只允許單繼承,也就是說 一個類最多只能顯示地繼承於一個父類。但是一個類卻可以被多個類繼承,也就是說一個類可以擁有多個子類。

  1.子類繼承父類的成員變量

  當子類繼承了某個類之後,便可以使用父類中的成員變量,但是並不是完全繼承父類的所有成員變量。具體的原則如下:

  1)能夠繼承父類的public和protected成員變量;不能夠繼承父類的private成員變量;

  2)對於父類的包訪問權限成員變量,如果子類和父類在同一個包下,則子類能夠繼承;否則,子類不能夠繼承;

  3)對於子類可以繼承的父類成員變量,如果在子類中出現了同名稱的成員變量,則會發生隱藏現象,即子類的成員變量會屏蔽掉父類的同名成員變量。如果要在子類中訪問父類中同名成員變量,需要使用super關鍵字來進行引用。

  2.子類繼承父類的方法

  同樣地,子類也並不是完全繼承父類的所有方法。

  1)能夠繼承父類的public和protected成員方法;不能夠繼承父類的private成員方法;

  2)對於父類的包訪問權限成員方法,如果子類和父類在同一個包下,則子類能夠繼承;否則,子類不能夠繼承;

  3)對於子類可以繼承的父類成員方法,如果在子類中出現了同名稱的成員方法,則稱為覆蓋,即子類的成員方法會覆蓋掉父類的同名成員方法。如果要在子類中訪問父類中同名成員方法,需要使用super關鍵字來進行引用。

  注意:隱藏和覆蓋是不同的。隱藏是針對成員變量和靜態方法的,而覆蓋是針對普通方法的。(後面會講到)

  3.構造器

  子類是不能夠繼承父類的構造器,但是要注意的是,如果父類的構造器都是帶有參數的,則必須在子類的構造器中顯示地通過super關鍵字調用父類的構造器並配以適當的參數列表。如果父類有無參構造器,則在子類的構造器中用super關鍵字調用父類構造器不是必須的,如果沒有使用super關鍵字,系統會自動調用父類的無參構造器。看下面這個例子就清楚了:

class Shape { protected String name; public Shape(){ name = &34;; } public Shape(String name) { this.name = name; }} class Circle extends Shape { private double radius; public Circle() { radius = 0; } public Circle(double radius) { this.radius = radius; } public Circle(double radius,String name) { this.radius = radius; this.name = name; }}

  這樣的代碼是沒有問題的,如果把父類的無參構造器去掉,則下面的代碼必然會出錯:

  改成下面這樣就行了:

  4.super

  super主要有兩種用法:

  1)super.成員變量/super.成員方法;

  2)super(parameter1,parameter2....)

  第一種用法主要用來在子類中調用父類的同名成員變量或者方法;第二種主要用在子類的構造器中顯示地調用父類的構造器,要注意的是,如果是用在子類構造器中,則必須是子類構造器的第一個語句。


三.常見的面試筆試題

1.下面這段代碼的輸出結果是什麼?

public class Test { public static void main(String[] args) { new Circle(); }} class Draw { public Draw(String type) { System.out.println(type+&34;); }} class Shape { private Draw draw = new Draw(&34;); public Shape(){ System.out.println(&34;); }} class Circle extends Shape { private Draw draw = new Draw(&34;); public Circle() { System.out.println(&34;); }}


shape draw constructorshape constructorcircle draw constructorcircle constructor

  這道題目主要考察的是類繼承時構造器的調用順序和初始化順序。要記住一點:父類的構造器調用以及初始化過程一定在子類的前面。由於Circle類的父類是Shape類,所以Shape類先進行初始化,然後再執行Shape類的構造器。接著才是對子類Circle進行初始化,最後執行Circle的構造器。

2.下面這段代碼的輸出結果是什麼?

public class Test { public static void main(String[] args) { Shape shape = new Circle(); System.out.println(shape.name); shape.printType(); shape.printName(); }} class Shape { public String name = &34;; public Shape(){ System.out.println(&34;); } public void printType() { System.out.println(&34;); } public static void printName() { System.out.println(&34;); }} class Circle extends Shape { public String name = &34;; public Circle() { System.out.println(&34;); } public void printType() { System.out.println(&34;); } public static void printName() { System.out.println(&34;); }}


shape constructorcircle constructorshapethis is circleshape

  這道題主要考察了隱藏和覆蓋的區別(當然也和多態相關,在後續博文中會繼續講到)。

  覆蓋只針對非靜態方法(終態方法不能被繼承,所以就存在覆蓋一說了),而隱藏是針對成員變量和靜態方法的。這2者之間的區別是:覆蓋受RTTI(Runtime type identification)約束的,而隱藏卻不受該約束。也就是說只有覆蓋方法才會進行動態綁定,而隱藏是不會發生動態綁定的。在Java中,除了static方法和final方法,其他所有的方法都是動態綁定。因此,就會出現上面的輸出結果。

相關焦點

  • 一張圖搞定java類之間的6種關係,以後再也不怕畫UML圖了
    java的類之間的關係:泛化、依賴、關聯、實現、聚合、組合車的類圖結構為<<abstract>>,表示車是一個抽象類;它有兩個繼承類小汽車和自行車;它們之間的關係為實現關係,使用帶空心箭頭的虛線表示; 小汽車為與SUV之間也是繼承關係,它們之間的關係為泛化關係,
  • 為什麼Java類不支持多繼承而接口可以?
    每個用Java的人都知道java不支持多繼承,但為什麼呢?無論從抽象還是多態等層面思考,感覺都是行得通的,那麼為什麼不支持呢?很多人都是分析一旦一個類繼承了多個父類,那麼父類中如果有相同的成員變量和方法,就不好處理,如下圖,這就是Diamond
  • 淺析java繼承與多態,抽象類與接口的區別
    二、繼承層級上面的例子只講了一層繼承關係,當然也可以進行多次繼承。A 繼承 B,B 繼承 C,C 繼承 D 理論上可以一直繼續,但是肯定不建議嵌套太多繼承關係,這會讓程序變得複雜且性能降低。Java 可以有多個繼承層次,一個類也可以有多條繼承鏈路,M 繼承 A,N 繼承 A。
  • 一起學JAVA——常用類
    java提供了很多已經封裝好的類供開發者使用,掌握一些常用類可以大大提高開發效率。Object類Object類被成為超類、根類、頂級父類或上帝類。因為,Object類是所有類的父類,除Object本身外,所有java類都必須直接或間接的繼承java.lang.Object類。
  • Java之 Scanner類
    所有的異常類是從 java.lang.Exception 類繼承的子類。由於 java.lang 包是默認加載到所有的 Java 程序的,所以大部分從運行時異常類繼承而來的異常都可以直接使用。2.3 捕獲異常使用 try 和 catch 關鍵字可以捕獲異常。try/catch 代碼塊放在異常可能發生的地方。
  • java創建類的過程,基礎面試必問題
    方法區」中保存類信息。java成員變量和局部變量的區別成員變量就是類的屬性,局部變量就是方法體內部聲明的變量。它們的作用不同,作用域不同。成員變量標誌對象的狀態,區分不同的人員。局部變量記錄中間計算結果。
  • Java基礎之:類加載原理
    今天小編打算講一下Java的類加載原理,可能有朋友會覺得大部分開發都不會直接和加載機制打交道,沒有必要去理解這麼底層的東西。但我不這麼認為,譬如對於java.lang.ClassNotFoundExcetpion這個異常很多人都不陌生,但是你知道如何去排查這個異常麼,這就涉及到類加載機制。而且了解類加載對理解java虛擬機的連接模型和java語言的動態性都有很大幫助。
  • 跟我學java編程—Java類的祖先Object類
    前面一節談到Java所有的類都繼承於Objec類,此類是所有Java類的父類,如果在類的聲明中未使用extends關鍵字指定父類,則默認為繼承自Object類,任何繼承Object的類,都可以調用Object類的方法。Object類在java.lang包中有如下幾個實用的方法。
  • java集合類框架,你了解多少?
    在平時寫代碼的時候,我們經常會使用到java的一些集合類,還記得我在剛剛學習的時候,因為這些集合類太多,沒有把他們的特點還有使用場景記清楚,所以在使用的時候對軟體的性能造成了很大的影響,因此現在我想把這些集合類進行一個詳細的整理,方便別人面試或者是初學者理解其概念。
  • Java類加載器的底層原理
    作者:龔生出處:https://segmentfault.com/a/1190000037678946類加載器的關係啟動類加載器(引導類加載器、Bootstrap ClassLoader)由c/c++語言實現的,嵌套在jvm內部用來加載java核心庫並不繼承java.lang.ClassLoader,沒有父加載器為擴展類加載器和系統類加載器的父加載器只能加載
  • Java面向對象之繼承
    從上面的列子可以看出,繼承關係解決的是代碼重複的問題。什麼是繼承關係?在java中,我們使用"extends"關鍵字來表示子類和父類之間的關係語法格式如下:在定義子類的時候來表明自己拓展於哪一個類父類public class 子類類名 extends 父類類名{//編寫自己特有的狀態(即欄位)和行為(即方法)}示例如下:改寫上文的代碼
  • 你真的了解java類加載器嗎?
    加載器類圖由類圖可知,保證這種委託邏輯的並不是繼承關係,而是組合,即每個加載器在創建或初始化的時候會被設置自己的父類加載器屬性委託加載模型雙親委派說明:每個ClassLoader實例都有一個父類加載器的引用(不是繼承的關係,是一個組合包含的關係),虛擬機內置的類加載器(Bootstrap ClassLoader)本身沒有父類加載器
  • Java為什麼不支持類多繼承
    一、前言我們都知道,Java裡邊有一個特殊的特性的,就是支持類(注意是類,接口是可以多繼承的)都單繼承,那究竟是為什麼,多繼承不行嗎?有什麼原因,下面通過一個反例,來論證下。二、反證法如下圖,B、C都相同都方法 doSomething,並皆為D類所繼承。這時,D所繼承的方法中,就出現二義性問題了,即調用父類的方法 doSomething,不知道具體執行那個父類的方法。
  • jvm - 深入理解Java類加載
    這裡類加載器之間的父子關係一般不會以繼承(Inheritance,均繼承自抽象類 java.lang.ClassLoader ,開發者可直接使用這兩個類加載器。這個類加載器可以通過 java.lang.Thread.setContextClassLoader() 方法進行設置,如果創建線程時還未設置,它將會從父線程中繼承一個,如果在應用程式的全局範圍內都沒有設置過的話,那這個類加載器默認就是應用程式類加載器。
  • java類加載機制和類加載器
    程序在啟動的時候,並不會一次性加載程序所要用到的所有class文件,而是根據需要,通過java的類加載器機制(classLoader)來動態加載某個class文件到內存中。jvm在運行時會產生三個classLoader:啟動類加載器(BootStrap ClassLoader):是java類加載層次中最頂層的類加載器,負責加載jdk中的核心類庫。
  • Jaskson精講第7篇-類繼承關係下的JSON序列化與反序列化JsonTypeInfo
    屬性序列化自定義排序與字母表排序-JSON框架Jackson精解第3篇》《@JacksonInject與@JsonAlias註解-JSON框架Jackson精解第4篇》《@JsonCreator自定義反序列化函數-JSON框架Jackson精解第5篇》《Jaskson精講第6篇-自定義JsonSerialize與Deserialize實現數據類型轉換》本篇文章是系列文章的第7篇,主要是為大家介紹一下,在Java 類繼承的情況下如何實現父類及子類的
  • Java抽象類與接口的區別
    在討論它們之間的不同點之前,我們先看看抽象類、接口各自的特性。抽象類抽象類是用來捕捉子類的通用特性的 。它不能被實例化,只能被用作子類的超類。抽象類是被用來創建繼承層級裡子類的模板。多繼承抽象方法可以繼承一個類和實現多個接口接口只可以繼承一個或多個其它接口速度它比接口速度要快接口是稍微有點慢的,因為它需要時間去尋找在類中實現的方法。添加新方法如果你往抽象類中添加新的方法,你可以給它提供默認的實現。因此你不需要改變你現在的代碼。如果你往接口中添加方法,那麼你必須改變實現該接口的類。
  • Java集合 - TreeMap類詳解
    根據上一條,我們要想使用TreeMap存儲並排序我們自定義的類(如User類),那麼必須自己定義比較機制:一種方式是User類去實現java.lang.Comparable接口,並實現其compareTo()方法。
  • Java提高篇——詳解內部類
    在使用內部類之間我們需要明白為什麼要使用內部類,內部類能夠為我們帶來什麼樣的好處。一、為什麼要使用內部類為什麼要使用內部類?在《Think in java》中有這樣一句話:使用內部類最吸引人的原因是:每個內部類都能獨立地繼承一個(接口的)實現,所以無論外圍類是否已經繼承了某個(接口的)實現,對於內部類都沒有影響。
  • Java 能不能自己寫一個類
    最近學習了下java類加載相關的知識。然後看到網上有一道面試題是能不能自己寫個類叫java.lang.System?網上提供的答案:通常不可以,但可以採取另類方法達到這個需求。所謂的另類方法指自己寫個類加載器來加載java.lang.System達到目的。