1. 程式人生 > 其它 >面向物件之繼承【Java】

面向物件之繼承【Java】

技術標籤:Java面向物件繼承Java

繼承的描述

在現實生活中,繼承一般指的是子女繼承父輩的財產。在程式中,繼承描述的是事物之間的所屬關係,通過繼承可以使多種事物之間形成一種關係體系。例如貓和狗都屬於動物,程式中便可以描述為貓和狗繼承自動物,同理,波斯貓和巴釐貓繼承自貓,而沙皮狗和斑點狗繼承自狗。這些動物之間會形成一個繼承體系,具體如下圖所示。

在這裡插入圖片描述

在Java中,類的繼承是指在一個現有類的基礎上去構建一個新的類,構建出來的新類被稱作子類,現有類被稱作父類,子類會自動擁有父類所有可繼承的屬性和方法。在程式中,如果想宣告一個類繼承另一個類,需要使用extends關鍵字。

通過 extends 關鍵字讓類與類之間產生繼承關係。

多個類中存在相同屬性和行為時,將這些內容抽取到單獨一個類中,那麼多個類無需再定義這些屬性和行為,只要繼承那個類即可。多個類可以稱為子類,單獨這個類稱為父類或者超類。

注意事項:

  • 子類可以直接訪問父類中的非私有的屬性和行為。
  • 子類無法繼承父類中私有的內容。
  • 父類怎麼來的?共性不斷向上抽取而來的。

示例:

class Person{
    String name;
    int age ;
}
class Student extends Person{
    void study(){
        System.out.println("student study..."
+ age); } } class Worker extends Person{ void work(){ System.out.println("worker work..." + age); } } class ExtendDemo{ public static void main(String[] args){ Student s = new Student(); s. name = "zhangsan" ; s. age = 20; s.
study(); Worker w = new Worker(); w. name = "lisi" ; w. age = 30; w.work(); } }

執行結果:

在這裡插入圖片描述

好處:

  • 繼承的出現提高了程式碼的複用性。
  • 繼承的出現讓類與類之間產生了關係,提供了多型的前提。

繼承的特點

在類的繼承中,需要注意一些問題,具體如下:

1.在Java中,類只支援單繼承,不允許多重繼承,也就是說一個類只能有一個直接父類,例如下面這種情況是不合法的。
在這裡插入圖片描述

2.多個類可以繼承一個父類,例如下面這種情況是允許的。

在這裡插入圖片描述

3.在Java中,多層繼承是可以的,即一個類的父類可以再去繼承另外的父類,例如C類繼承自B類,而B類又可以去繼承A類,這時,C類也可稱作A類的子類。例如下面這種情況是允許的。
在這裡插入圖片描述

4.在Java中,子類和父類是一種相對概念,也就是說一個類是某個類父類的同時,也可以是另一個類的子類。例如上面的示例中,B類是A類的子類,同時又是C類的父類。

Java只支援單繼承,不支援多繼承。一個類只能有一個父類,不可以有多個父類。
原因:因為多繼承容易出現問題。兩個父類中有相同的方法,子類到底要執行哪一個是不確定的。

示例:

class A{
    void show(){
        System.out.println("a" );
    }
}
class B{
    void show(){
        System.out.println("b" );
    }
}
class C extends B,A{
}

那麼建立類C的物件,呼叫show方法就不知道呼叫類A中的show方法還是類B中的show方法。所以java不支援多繼承,但將這種機制換了另一個安全的方式來體現,也就是多實現(後面會詳細說明)。

Java支援多層繼承(繼承體系):

C繼承B,B繼承A,就會出現繼承體系。多層繼承出現的繼承體系中,通常看父類中的功能,瞭解該體系的基本功能,建立子類物件,即可使用該體系功能。

定義繼承需要注意:不要僅為了獲取其他類中某個功能而去繼承,類與類之間要有所屬( “is a”)關係。

super關鍵字&函式覆蓋

在繼承關係中,子類會自動繼承父類中定義的方法,但有時在子類中需要對繼承的方法進行一些修改,即對父類的方法進行重寫。需要注意的是,在子類中重寫的方法需要和父類被重寫的方法具有相同的方法名、引數列表以及返回值型別。

當子類重寫父類的方法後,子類物件將無法訪問父類被重寫的方法,為了解決這個問題,在Java中專門提供了一個super關鍵字用於訪問父類的成員。例如訪問父類的成員變數、成員方法和構造方法。

在子父類中,成員的特點體現:

成員變數

  • this和super的用法很相似
  • this代表本類物件的引用
  • super代表父類的記憶體空間的標識
  • 當本類的成員和區域性變數同名用this區分
  • 當子父類中的成員變數同名用super區分父類

示例:

class Fu{
   private int num = 4;

   public int getNum(){
       return num ;
   }
}

class Zi extends Fu{
   private int num = 5;

   void show(){
       System.out.println(this.num + "..." + super.getNum());
   }
}

class ExtendDemo{
   public static void main(String[] args){
       Zi z = new Zi();
       z.show();
   }
}

執行結果

在這裡插入圖片描述

成員函式

當子父類中出現成員函式一模一樣的情況,會執行子類的函式。這種現象,稱為覆蓋操作,這是函式在子父類中的特性。

在子類覆蓋方法中,繼續使用被覆蓋的方法可以通過super.函式名獲取。

函式兩個特性:

  1. 過載,同一個類中。

  2. 覆蓋,子類中,覆蓋也稱為重寫,覆寫,override。

示例:

class Fu{
   public void show(){
       System.out.println("fu show run" );
   }
}

class Zi extends Fu{
   public void show(){
       System.out.println("zi show run" );
   }
}

class ExtendDemo{
   public static void main(String[] args){
       Zi z = new Zi();
       z.show();
   }
}

執行結果:

在這裡插入圖片描述

什麼時候使用覆蓋操作?

當子類需要父類的功能,而功能主體子類有自己特有內容時,可以複寫父類中的方法,這樣,即沿襲了父類的功能,又定義了子類特有的內容。

示例:

class Phone{
   void call(){}
   void show(){
       System.out.println("number" );
   }
}

class NewPhone extends Phone{
   void show(){
       System.out.println("name" );
       System.out.println("pic" );
       super.show();
   }
}

class ExtendDemo{
   public static void main(String[] args){
       NewPhone p = new NewPhone();
       p.show();
   }
}

執行結果:

在這裡插入圖片描述

注意事項:

  • 父類中的私有方法不可以被覆蓋
  • 父類為static的方法無法覆蓋
  • 覆蓋時,子類方法許可權一定要大於等於父類方法許可權

示例:

class Fu{
    public void show(){
        System.out.println("fu show run" );
    }
}

class Zi extends Fu{
    private void show(){
        System.out.println("zi show run" );
    }
}

class ExtendDemo{
    public static void main(String[] args){
        Zi z = new Zi();
        z.show();
    }
}

執行結果:

在這裡插入圖片描述

建構函式

子父類中建構函式的特點:在子類建構函式執行時,發現父類建構函式也運行了。
原因:在子類的建構函式中,第一行有一個預設的隱式語句:super();。
注意:如果使用super(4);語句呼叫父類的其他建構函式,那麼預設的父類建構函式將不會再被呼叫。

示例:

class Fu{
    int num ;
    Fu(){
        num = 10;
        System.out.println("A fu run" );
    }
    Fu(int x){
        System.out.println("B fu run..." + x);
    }
}

class Zi extends Fu{
    Zi(){
        //super();//預設呼叫的就是父類中的空引數的建構函式
        System.out.println("C zi run " + num);
    }
    Zi(int x){
        super(4);
        System.out.println("D zi run " + x);
    }
}

class ExtendDemo{
    public static void main(String[] args){
        new Zi();
        System.out.println("-------------------" );
        new Zi(6);
    }
}

執行結果:

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片儲存下來直接上傳(img-YA3XS2TW-1610001761890)(img/1491308168245.png)]

子類的例項化過程

子類中所有的建構函式預設都會訪問父類中空引數的建構函式。因為每一個建構函式的第一行都有一條預設的語句super();。

為什麼子類例項化的時候要訪問父類中的建構函式呢?

那是因為子類繼承了父類,獲取到了父類中內容(屬性),所以在使用父類內容之前,要先看父類是如何對自己的內容進行初始化的。

注意事項:

  • 當父類中沒有空引數的建構函式時,子類的建構函式必須通過this或者super語句指定要訪問的建構函式。
  • 子類建構函式中如果使用this呼叫了本類建構函式,那麼預設的super();就沒有了,因為super和this都只能定義在第一行,所以只能有一個。但是可以保證的是,子類中肯定會有其他的建構函式訪問父類的建構函式。
  • super語句必須要定義在子類建構函式的第一行!因為父類的初始化動作要先完成。

示例:

class Fu{
    Fu(){
        super();
        //呼叫的是子類的show方法,此時其成員變數num還未進行顯示初始化
        show();
        return;
    }
    void show(){
        System.out.println("fu show" );
    }
}
class Zi extends Fu{
    int num = 8;
    Zi(){
        super();
        //通過super初始化父類內容時,子類的成員變數並未顯示初始化,等super()父類初始化完畢後,才進行子類的成員變數顯示初始化
        return;
    }
    void show(){
        System.out.println("zi show..." + num);
    }
}
class ExtendDemo{
    public static void main(String[] args){
        Zi z = new Zi();
        z.show();
    }
}

執行結果:

在這裡插入圖片描述

總結:一個物件例項化過程,以Person p = new Person();為例

  1. JVM會讀取指定的路徑下的Person.class檔案,並載入進記憶體,並會先載入Person的父類(如果有直接的父類的情況下)
  2. 在記憶體中開闢空間,並分配地址
  3. 並在物件空間中,對物件的屬性進行預設初始化
  4. 呼叫對應的建構函式進行初始化
  5. 在建構函式中,第一行會先到呼叫父類中建構函式進行初始化
  6. 父類初始化完畢後,再對子類的屬性進行顯示初始化
  7. 再進行子類建構函式的特定初始化
  8. 初始化完畢後,將地址值賦值給引用變數

final關鍵字

final關鍵字可用於修飾類、變數和方法,它有“無法改變”或者“最終”的含義,因此被final修飾的類、變數和方法將具有以下特性:

  • final可以修飾類,方法,變數
  • final修飾的類不可以被繼承
  • final修飾的方法不可以被覆蓋
  • final修飾的變數是一個常量,只能被賦值一次
  • 為什麼要用final修飾變數,其實,在程式中如果一個數據是固定的。那麼直接使用這個資料就可以了,但是這種閱讀性差,所以應該給資料起個名稱。而且這個變數名稱的值不能變化,所以加上final固定
  • 寫法規範:常量所有字母都大寫,多個單詞,中間用_連線

示例1:

 //繼承弊端:打破了封裝性
 class Fu{
        void method(){
        }
 }

 class Zi extends Fu{
        public static final double PI = 3.14;
        void method(){
             System.out.println(PI);
        }
 }

 class FinalDemo{
        public static void main(String[] args){
             Zi zi = new Zi();
             zi.method();
        }
 }

執行結果:
在這裡插入圖片描述

示例2:

class FinalDemo{
    public static void main(String[] args){
        final int x = 4;
        x = 5;
    }
}

執行結果:
在這裡插入圖片描述

記憶體結構

靜態繫結,當方法被 static private final三個關鍵字其中一個修飾,執行的靜態繫結

動態繫結,方法執行的動態繫結

屬性看變數的型別,Person.this.salary

public class TestChunApp
{

	public static void main(String[] args) {
		Person p = new Manager();
      	p.sing();
		//System.out.println(p);	//Person.this.salary
	}
}

class Person{
	private int salary = 10000;
	
	public static void sing(){
		System.out.println("忘情水");
	}
	int getSalary(){
		return salary;
	}
	public void printSalary(){
		System.out.println(salary);
	}
}

class Manager extends Person{
	int salary = 30000;
	
	public static void sing(){
		System.out.println("中國人");
	}
	public void printSalary(){
		System.out.println(getSalary());
	}
}