面向物件之繼承【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.函式名獲取。
函式兩個特性:
-
過載,同一個類中。
-
覆蓋,子類中,覆蓋也稱為重寫,覆寫,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);
}
}
執行結果:
子類的例項化過程
子類中所有的建構函式預設都會訪問父類中空引數的建構函式。因為每一個建構函式的第一行都有一條預設的語句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();
為例
- JVM會讀取指定的路徑下的Person.class檔案,並載入進記憶體,並會先載入Person的父類(如果有直接的父類的情況下)
- 在記憶體中開闢空間,並分配地址
- 並在物件空間中,對物件的屬性進行預設初始化
- 呼叫對應的建構函式進行初始化
- 在建構函式中,第一行會先到呼叫父類中建構函式進行初始化
- 父類初始化完畢後,再對子類的屬性進行顯示初始化
- 再進行子類建構函式的特定初始化
- 初始化完畢後,將地址值賦值給引用變數
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());
}
}