一文打盡Java繼承的相關問題
阿新 • • 發佈:2020-08-03
相關文章:
1. [《面向物件再探究》](https://mp.weixin.qq.com/s/NQK3aYXUh3eOwmwmP_VluQ):介紹了面向物件的基本概念
2. [《詳解Java的物件建立》](https://mp.weixin.qq.com/s/lCsGhvovwWEw4DE4r92a3Q):介紹了物件的建立、構造器的使用
在[《面向物件再探究》](https://mp.weixin.qq.com/s/NQK3aYXUh3eOwmwmP_VluQ)這篇文章中已經籠統的介紹過繼承的概念了,下面就來具體介紹繼承的使用等相關問題。
# 1. 引入例子
以`Animal`類和`Dog`類例子,下文都會圍繞該例展開。
```java
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public Animal() {
}
public void say() {
System.out.println("我是" + name + ",今年" + age + "歲了");
}
//getters and setters ...
}
```
```java
public class Dog extends Animal {
private String address;
public Dog(String name, int age, String address) {
super(name, age);
this.address = address;
}
public Dog() {
}
public void say() {
System.out.println("我叫" + super.getName() + ",今年" + super.getAge() + "歲了,家住" + address + ",汪汪汪...");
}
public void watchDoor() {
System.out.println("我在" + address +"看門...");
}
//getters and setters ...
}
```
`Dog`類繼承`Animal`類,`Animal`類有`name`、`age`屬性和`say()`方法,`Dog`類增加了`address`屬性和`watchDoor()`方法。
# 2. 概念介紹
在現實生活中有許多繼承的例子,比如“子承父業”、“繼承父母財產”、“繼承某人意志”等,這些都是在強調一個體得到另一個體的東西,他們之間存在“繼承關係”。
在Java中,繼承是類和類之間的關係。在[《面向物件再探究》](https://mp.weixin.qq.com/s/NQK3aYXUh3eOwmwmP_VluQ)這篇文章中舉了動物`Animal`類和狗`Dog`類、貓`Cat`類、兔子`Rabbit`類的例子並寫了程式碼,這裡直接拿來用不再列出,請移步此文章。
我們可以說狗、貓、兔子是動物,但不可以說動物是狗、貓、兔子。動物普遍有眼睛、耳朵等屬性,有跑、吃、叫等行為。狗顯然也具有這些屬性和行為,但是狗也具有一些它特有的屬性和行為,比如靈敏的鼻子、看門等。
在這個例子中,`Animal`類是父類,`Dog`類、`Cat`類、`Rabbit`類是子類。子類和父類存在繼承關係,可以說:**子類“是”父類**,該關係是繼承的一個明顯特徵。
**下面是關於繼承的幾個Q&A:**
**Q1:** 什麼時候需要用到繼承?
**A1:** 個人認為可從以下兩點出發:
1. 面嚮物件語言是對現實世界的抽象,所謂“萬物皆物件”,那麼我們在實際開發中的類肯定都是與現實世界的實物相對應的,所以看實物之間的關係有沒有繼承關係。
2. 當有若干類有大量重複的屬性和方法,那麼就可以考慮使用繼承,將這些重複的屬性和方法抽成父類。
**Q2:** 怎樣使用繼承?
**A2:** 子類使用`extends`關鍵字繼承父類,也只有使用了該關鍵字後,才存在繼承關係及“父子類”:
```java
public class Animal {//父類
//屬性和方法
}
```
```java
public class Dog extends Animal {//子類繼承父類
//屬性和方法
}
```
**Q3:** 在具體開發中,是先編寫父類,然後擴展出子類,還是先編寫子類,然後抽象出父類?
**A3:** 個人認為不能一概而論。如果你的系統設計非常好,可以先寫好父類,然後擴充套件子類。如果你沒設計好,或者考慮不周,那麼後期可能會出現許多類有重複程式碼,這時候可以考慮抽取父類。
**Q4:** 父類和子類在內容上有什麼區別?
**A4:** 通常,我們將更通用的一些屬性和方法放到父類中,比如狗、貓、兔子都具有的屬性和方法會方法動物類中。子類中是更加特別的屬性和方法,比如狗的看門行為、貓的抓老鼠行為。由於子類繼承了父類,所以子類中只需要編寫和父類的不同之處。
所以,子類往往會比父類擁有更加豐富的屬性和方法。
**Q5:** 子類繼承的父類的哪些東西?
**A5:** 這裡先給出結論,下文再詳細介紹。子類繼承了父類的**非私有`private`的**成員變數、方法、巢狀類,子類不繼承父類的構造器,但是子類可以呼叫父類的構造器。
**Q6:** Java中可以繼承多個類嗎?
**A6:** Java中的繼承是單繼承,只能繼承一個類。
# 3. 具體使用
## 3.1. 子類重寫父類的方法
有時子類從父類繼承得到的方法不一定適用,這時子類可以重寫父類的方法。如`Dog`類重寫了其父類的`say()`方法。
注意要和**過載**加以區分。
簡單來說,重寫涉及到的是兩個類的同名方法,過載涉及到的是一個類的同名方法。關於過載介紹請移步[這裡](https://mp.weixin.qq.com/s/lCsGhvovwWEw4DE4r92a3Q)。
## 3.2. 子類新增屬性和方法
前面提到:子類是對父類的擴充套件,子類內容比父類更加豐富。所以通常子類都會有自己的屬性和方法,比如`Dog`類的`address`屬性和`watchDoor()`方法。
## 3.3. 子類如何使用父類的私有成員?
**子類不繼承父類的私有成員**,但是如果父類有能訪問其私有成員變數的`public`或`protected`的方法(比如getter方法),那麼子類可以通過繼承這些方法來使用父類的私有方法([Java官方教程](https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html))。
下面用上面的程式碼例子解釋:
`Animal`類有私有成員變數、和公有方法`say()`。`say()`直接訪問本類的私有成員變數,沒毛病!
```java
public class Animal {
private String name;
private int age;
public void say() {
System.out.println("我是" + name + ",今年" + age + "歲了");
}
//其他程式碼...
}
```
現在`Dog`類繼承`Anima`類
```java
public class Dog extends Animal {
private String address;
public void say() {//name和age報紅
System.out.println("我叫" + name + ",今年" + age + "歲了,家住" + address + ",汪汪汪...");
}
//其他程式碼...
}
```
**請注意**`Dog`類的`say()`方法,直接訪問了`name`和`age`屬性,如果子類繼承了父類的私有成員,那麼這樣寫是沒問題的,**但事實是`name`和`age`報紅了**,說明子類沒有繼承父類的私有成員。
雖然子類沒有繼承父類的私有成員,但是我們可以通過父類的公有方法使用其私有成員變數,程式碼改動如下:
```java
public class Dog extends Animal {
private String address;
public void say() {
System.out.println("我叫" + super.getName() + ",今年" + super.getAge() + "歲了,家住" + address + ",汪汪汪...");
}
//其他程式碼...
}
```
使用`super.xxx()`可以呼叫父類的方法,由於`getName()`是父類公有方法,且能訪問父類的私有成員變數`name`,所以子類呼叫`getName()`方法可以使用`name`變數。
## 3.4. 子類如何對父類的私有成員賦值?
子類沒有繼承父類的私有成員變數,當然不能直接賦值。通常有兩種方法:呼叫父類的公有setter方法或呼叫父類的構造器。**本質上還是子類通過使用父類提供的公有方法(setter或構造器)使用其私有成員變數。**
### 3.4.1. 使用setter方法:
```java
public class Dog extends Animal {
private String address;
public void setName(String name) {
super.setName(name); //注意super
}
public void setAge(int age) {
super.setAge(age);
}
public void say() {
System.out.println("我叫" + super.getName() + ",今年" + super.getAge() + "歲了,家住" + address + ",汪汪汪...");
}
//其他程式碼...
}
```
`Dog`類的兩個setter方法呼叫了`Animal`類的兩個setter方法,由於子類的setter方法名和父類的setter方法名取得一樣(重寫了),所以一定要使用`super`關鍵字加以區分,否則就成遞迴了。
### 3.4.2. 呼叫父類的構造器:
```java
public class Dog extends Animal {
private String address;
public Dog(String name, int age, String address) {
super(name, age); //呼叫父類構造器
this.address = address;
}
public Dog() {
}
public void say() {
System.out.println("我叫" + super.getName() + ",今年" + super.getAge() + "歲了,家住" + address + ",汪汪汪...");
}
//其他程式碼...
}
```
```java
public class Animal {
private String name;
private int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
//其他程式碼...
}
```
子類使用`super()`語句呼叫父類構造器,該語句必須是子類構造器的第一行程式碼。
如果子類構造器沒有顯式呼叫父類構造器,則會預設呼叫父類的無參構造器。所以如果父類沒有無參構造器,而子類又沒有顯式呼叫父類的其他構造器,則會報錯。
# 4. [關於我](https://mp.weixin.qq.com/s/PF7srGAwzd_w5pU6eOEZow)
>如有錯誤,還請