# 繼承
[TOC]
## 導學
在本章節內容中,我們將繼續來學習面向對象三大特性之一的繼承。之前的封裝,實際上是針對一個類進行隱藏和訪問控制操作,而即將要學習的繼承,其實是在描述兩個類之間的關系。
>[success]繼承是軟件實現復用的重要手段之一
首先,我們來想一想生活中的繼承,比如兒子繼承父親的外貌與性格,徒弟繼承師傅的手藝等等。這些都是繼承,在兩個主體之間有著傳承的關系。
對于面向對象程序而言,它的編程思想同樣來自于生活。我們在程序的開發中同樣也會有和生活中一樣的感受。比如,我們使用兩個類來描述貓和狗:

我們發現在描述過程中,它們有一些相同的屬性和方法,也有一些不同的屬性和方法。那么,如果我們接著描述其他的動物,是不是也會產生大量的重復的代碼呢?
最好能有一種方法能把這些重復代碼收集起來,然后我每次要使用的時候,就直接調用這個方法,進行重復利用就可以了。而,這種方法就是我們今天要學習的繼承了。
比如我們可以將貓類和狗類中的共同屬性和方法抽取出來,組成一個動物類:

當貓狗類和動物類實現繼承關系時,貓狗類就可以直接使用動物類中屬性和方法了,而不必在寫那些重復的代碼了。即使我們再寫如企鵝類,獅子類,烏龜類等,也可以去繼承動物類,不必再寫那些重復的代碼。
>[info]將一些具有相似邏輯的類中的公共的**屬性**和**方法**抽取出來,組成一個類,這個類我們稱之為**父類**。**父類**和**子類**是一種一般和特殊的關系。例如水果和蘋果,蘋果是一種特殊的水果
所以,我們發現繼承其實有著如下的特點:
* 利于代碼復用
* 縮短開發周期
那么,說了這么多,我們再來對繼承做個總結吧:
* 一種類與類之間的關系
* 使用已存在的類的定義作為基礎建立新類
* 新類的定義可以增加新的數據或新的功能,也可以用父類的功能,但不能選擇性地繼承父類的功能!
## 繼承的實現
在Java中使用`extends`關鍵字實現類與類之間的繼承關系

在Java中,**繼承只能是單繼承**,即子類只能有一個父類,而父類可以被多個子類繼承。就像現實生活中孩子只能有一個親爹,而父親可能有多個子女一樣。
但是Java中存在**多層繼承**,常用的比如每個子類只有一個直接父類,但是該父類依然存在其自身的父類。**注意**:雖然類繼承可以實現代碼的復用,但是如果繼承的結構過多,也會造成代碼段的閱讀困難,一般不建議繼承的結構超過3層。
>[warning]當實現繼承時,子類可以獲得父類**非私有**屬性和方法的使用權
接下來就結合就結合具體的案例來看看吧
父類:
~~~java
public class Animal {
private String name;//名稱
private int month;//月份
private String species;//品種
//動物共有方法吃東西
public void eat() {
System.out.println(this.getName() + "在吃東西");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public Animal() {
}
public Animal(String name, int Month, String species) {
super();
this.setName(name);
this.setMonth(Month);
this.setSpecies(species);
}
}
~~~
子類-貓:
~~~java
public class Cat extends Animal{
//貓自己的屬性-體重
private double weight;
//貓自己的方法-跑動
public void run() {
//子類可以使用父類的非私有成員,這里可以使用父類的getName()方法,不能使用父類的name屬性
System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快樂地奔跑");
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public Cat() {
}
public Cat(double weight) {
this.setWeight(weight);
}
}
~~~
子類-狗:
~~~java
public class Dog extends Animal {
//自有的屬性
private String sex;
//自有的睡覺方法
public void sleep() {
System.out.println(this.getName() + "現在" + this.getMonth() + "個月大,它在睡覺~");
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Dog() {
}
}
~~~
測試:
~~~java
public class Test {
public static void main(String[] args) {
Cat one = new Cat();
one.setName("花花");
one.setSpecies("中華田園貓");
one.eat();
one.run();
//sleep()方法不屬于父類Animal,也不屬于子類Cat中定義的方法
//one.sleep();
System.out.println("=====================");
Dog two = new Dog();
two.setName("田田");
two.setMonth(1);
two.eat();
two.sleep();
System.out.println("=====================");
Animal three = new Animal();
//three.run();無法訪問
//three.sleep();
}
}
~~~
**總結:**
* 一個子類只能有一個父類
* 子類可以訪問父類非私有成員
* 子類自己自有成員其他兄弟類無法訪問
* 父類不可以訪問子類自有成員
## 方法的重寫
在上述案例中,子類都調用了父類的`eat()`方法。但是對于這樣的方法,貓與狗都需要吃東西,但可能各自都有自己不同的想法。比如貓吃魚,狗吃肉,具體的表現形式不同,那么各自類中的方法體描述也不應該相同。
對于這樣的問題,我們可以使用方法的重寫來完成這樣的操作。對于方法的重寫,指的是**在子類中重新描述父類中的方法**。
>[danger]方法的重寫,要求返回值類型,方法名,參數類型、順序、個數都要與父類繼承的方法完全一致。
~~~
public class Cat extends Animal{
//貓自己的屬性-體重
private double weight;
//貓自己的方法-跑動
public void run() {
//子類可以使用父類的非私有成員,這里可以使用父類的getName()方法,不能使用父類的name屬性
System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快樂地奔跑");
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public Cat() {
}
public Cat(double weight) {
this.setWeight(weight);
}
@Override//注解,表示方法的重寫,方法前面加上@Override 系統可以幫你檢查方法的正確性
public void eat() {
System.out.println(this.getName() + "愛吃魚");
}
}
~~~
~~~
public class Dog extends Animal {
//自有的屬性
private String sex;
//自有的睡覺方法
public void sleep() {
System.out.println(this.getName() + "現在" + this.getMonth() + "個月大,它在睡覺~");
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public Dog() {
}
@Override
public void eat() {
System.out.println(this.getName() + "愛吃肉");
}
}
~~~
~~~
public class Test {
public static void main(String[] args) {
Cat one = new Cat();
one.setName("花花");
one.setSpecies("中華田園貓");
one.eat();
System.out.println("=====================");
Dog two = new Dog();
two.setName("田田");
two.setMonth(1);
two.eat();
}
}
~~~
輸出結果:
~~~
花花愛吃魚
=====================
田田愛吃肉
~~~
**當子類重寫父類方法之后,子類對象調用的是重寫之后的方法。**
### 方法重載與方法重寫
在學習中,我們有可能會把方法重載和方法重寫混淆。所以我們來通過一個表格認識一下方法重載與方法重寫的區別
>[info]方法的簽名是由方法的方法名和形參列表(注意:包含方法的參數和類型)組成
| | 方法重載 |方法重寫 |
| :--- | :--- | :--- |
| **類關系** | 發生在同一個類中 | 發生在有繼承關系的子類與父類中 |
| **方法簽名** | 方法名相同,參數列表不同(參數順序、個數、類型) | 方法名相同,參數列表相同(參數順序、個數、類型)|
| **返回值** | 返回值任意 | 返回值相同或返回值呈現父子關系 |
| **訪問修飾符** | 訪問修飾符任意 | 訪問修飾符的訪問范圍需要大于等于父類的訪問范圍 |
| **參數名** | 與方法的參數名無關 | 與方法的參數名無關 |
### 關于屬性
對于重寫,在Java中只有針對方法的重寫,沒有針對屬性的重寫。但是,我們在子類中也是可以定義與父類同名的屬性的,此時子類對象調用的是子類的屬性。
~~~
public class Animal {
public int temp = 15;
}
public class Cat extends Animal {
public int temp = 30;
}
public class Test {
public static void main(String[] args) {
Cat one = new Cat();
System.out.println(one.temp);//30
}
}
~~~
## 訪問修飾符
訪問控制修飾符能修飾的對象包括:**屬性**、**方法**、**構造器**
訪問修飾符可以用來限制成員被訪問的范圍,即用來控制被修飾的成員可以在哪里(包)被使用。
| | private | default | protected | public |
| :---: | :---: | :---: | :---: | :---: |
| 同一個類 | √ | √ | √ | √ |
| 同一個包 | | √ | √ | √ |
| 子類 | | | √ | √ |
| 全局范圍 | | | | √ |
* private: 只允許在本類中進行訪問(訪問權限最小的)
* public: 允許在任意位置訪問(訪問權限最大的)
* protected: 允許在當前類、同包子類/非子類、跨包子類調用;跨包非子類不允許
* default: 允許在當前類、同包子類/非子類調用;跨包子類/非子類不允許調用
>[success]在實際的開發中,大部分的場景使用 private 定義屬性,public 定義方法。
### 訪問修飾符對方法重寫的影響
子類中重寫父類方法時,子類方法的訪問修飾符的訪問范圍需要大于等于父類方法的訪問范圍
~~~
public class Animal {
protected void eat() {
}
}
public class Cat extends Animal {
public void eat() {
}
/*void eat() {
有問題
}*/
}
~~~
## super關鍵字
在上節內容的學習中,我們知道子類可以繼承父類的方法,也可以使用自己重寫的方法。那么該如何判定子類調用的方法是繼承自父類,還是自己重寫的呢。
~~~
public class Animal {
public String name;
//動物共有方法吃東西
public void eat() {
System.out.println(this.getName() + "在吃東西");
}
}
public class Cat extends Animal{
//貓自己的方法-跑動
public void run() {
eat();//調用的是子類重寫的方法
System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快樂地奔跑");
}
@Override
public void eat() {
System.out.println(this.getName() + "愛吃魚");
}
}
~~~
如果我想要使用父類的方法,則需要使用`super`關鍵字。`super`關鍵字代表著對父類對象的引用。
~~~
public void run() {
super.eat();//調用的是子類重寫的方法
System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快樂地奔跑");
}
~~~
當然也可以使用`super`訪問父類中允許被子類訪問到的任意成員。
~~~
public void run() {
super.eat();//調用的是子類重寫的方法
super.name = "貓貓";
System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快樂地奔跑");
}
~~~
1. 子類重寫父類的方法,則父類的方法會被隱藏,隱藏的方法或者成員變量可以通過super關鍵字訪問
2. 引入super關鍵字的原因是可以使用被隱藏的成員變量和方法,而且super只能在子類的方法中定義使用
### 繼承的初始化順序
**父類的構造方法不允許被繼承,不允許被重寫。** 那么父類的構造器除了構建父類對象是否有其他作用呢?
~~~java
public class Animal {
private String name = "妮妮";//名稱
protected int month = 2;//月份
String species = "英短";//品種
public int temp = 15;
private static int st1 = 22;
private static int st2 = 23;
static {
System.out.println("我是父類的靜態代碼塊");
}
{
System.out.println("我是父類的構造代碼塊");
}
//動物共有方法吃東西
public void eat() {
System.out.println(this.getName() + "在吃東西");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public String getSpecies() {
return species;
}
public void setSpecies(String species) {
this.species = species;
}
public Animal() {
System.out.println("我是父類的無參構造方法");
}
public Animal(String name, int Month, String species) {
super();
this.setName(name);
this.setMonth(Month);
this.setSpecies(species);
}
}
~~~
~~~java
public class Cat extends Animal{
//貓自己的屬性-體重
private double weight;
private static int st3 = 44;
static {
System.out.println("我是子類的靜態代碼塊");
}
{
System.out.println("我是子類的構造代碼塊");
}
//貓自己的方法-跑動
public void run() {
System.out.println(this.getName()+ "是一只" + this.getSpecies() +",它正在快樂地奔跑");
}
public double getWeight() {
return weight;
}
public void setWeight(double weight) {
this.weight = weight;
}
public Cat() {
System.out.println("我是子類的無參構造方法");
}
public Cat(double weight) {
this.setWeight(weight);
}
@Override
public void eat() {
System.out.println(this.getName() + "愛吃魚");
}
}
~~~
~~~java
public class Test {
public static void main(String[] args) {
Cat one = new Cat();
System.out.println(one.temp);
}
}
~~~
繼承后的初始化順序(實例化子類對象的時候):先加載父類靜態成員,然后加載子類靜態成員,然后父類對象構造(構造代碼塊-->屬性-->方法),最后子類對象構造。
### super()
在上節內容中,我們提到子類對象在實例化的時候會先去調用父類對象的構造器。那么子類構造器是否有權利去選擇具體使用父類的哪個構造器呢?
在父類中添加一個有參構造器
~~~
public Animal(String name, int month, String species) {
System.out.println("我是父類的有參構造器");
}
~~~
在子類中也使用三參構造器
~~~
public Cat(String name, int Month, String species) {
System.out.println("我是子類的有參構造器");
}
~~~
測試:
~~~
public class Test {
public static void main(String[] args) {
Cat one = new Cat("花花",3,"英短");
System.out.println(one.temp);
}
}
~~~
最終的運行結果表明,子類應用帶參構造器實例化對象的時候,同樣調用的是父類無參的構造器。
**這也就是在實際的開發過程中,我們都會保證一個類中有一個無參的構造器存在的原因。**
這也是Java繼承的一個特點,如果在子類的構造器中,沒有顯式的調用父類的構造器,會**默認的調用父類的構造器**。
那么,問題又來啦,如何在子類的構造器中調用父類的構造器呢?
~~~
public Cat(String name, int month, String species) {
//super();調用的是父類的無參構造方法
super(name, month,species);//調用的是父類對應參數的構造方法
System.out.println("我是子類的有參構造器");
}
~~~
>[warning]使用`super()`關鍵字,可以完成對父類構造器的調用。
**總結:**
1. 子類默認調用父類無參構造方法;
2. 可以通過`super()`調用父類允許訪問的其他構造方法;
3. `super()`必須放在子類構造方法的有效方法的第一行,而且只能出現一次。
4. `this()`和`super()`在同一構造器中只可能出現一個。
### super,this,super()和this()
||super | this | super() | this() |
| --- | --- | --- | --- | ---|
| 用處 | super可以用在子類的成員方法中 | this可以用在本類中的成員方法中 | super()可以用在子類的構造器中 | this()可以用在本類的構造器中 |
| 目的 | 調用父類非私有(no private)的成員變量或方法 | 調用本類的成員變量或方法 | 調用父類的構造器 | 調用本類的構造器 |
>[danger]super和this可以同時出現的,而super()和this()不能同時出現在一個構造器中
### 子類調用父類的構造器情況
**子類構造器調用父類的構造器可能出現的情況有**
1. 子類構造器執行體的第一行顯式的使用super調用父類的構造器,此時系統將根據super(params)去調用對應的構造器。
2. 子類構造器在執行體的第一行顯式的使用this調用重載的構造器,系統將會根據this(params) 調用對應 的重載構造器,本類中的對應的構造器再去調用父類的構造器
3. 子類構造器中既沒有super有沒有this,那么子類在執行構造器語句的時候會去執行父類的無參構造器。
4. 無論如何子類都會調用一次父類的構造器
## Object類
`Object`類是所有類的老祖宗,如果有一個類沒有顯式的說明繼承自哪個類,那么該類就默認的繼承Object類。
對于`Object`類,它有著如下的特點
1. `Obejct`類是所有類的父類
2. 一個類沒有使用`extends`關鍵字明確標識繼承關系,則默認繼承Object類(包括數組)
3. Java中每個類都可以使用`Ojbect`類中定義的方法
接下來,我們就來結合Java API(JDK文檔)來看看Java中對于`Object`類的介紹
文檔地址:[http://www.matools.com/api/java8](http://www.matools.com/api/java8)
### equals()方法
在`Object`類中,我們首先要學習的就是`equals()`方法。
>[info]**當直接繼承Object類中的`equals()`方法時**,它的作用是判斷調用`equals()`方法的對象的內存空間引用與傳入參數對象的內存空間引用是否一致(是否指向的是同一塊內存地址)。
在Java中,`==`不但可以判斷兩個基本數據類型的數據是佛相等,也可以用來判斷兩個對象的內存地址是否一致。本章節中所講的,equals()方法如果不對其進行重寫,則作用和`==`沒有什么太大的差異。
~~~Java
public class Test {
public static void main(String[] args) {
Animal one = new Animal("花花",2,"英短");
Animal two = new Animal("花花",2,"英短");
System.out.println(one == two);
System.out.println(one.equals(two));
}
}
~~~
上述代碼的運行結果都為`false`。這是因為`one`對象通過`new`關鍵字開辟了一塊堆內存空間,`two`對象通過new關鍵字開辟了另一塊內存空間。`one`對象和`two`對象各自指向的內存地址不一樣,即使對象中的屬性值一致,也被判定為`false`。
在String類中,Java開發人員重寫了`equals()`方法。那我們來看看在String類中重寫后的`equals()`方法的作用。
~~~
public static void main(String[] args) {
String str = "abc";
//String作為一個類,同樣有其構造方法
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str == str1);
System.out.println(str == str2);
System.out.println(str1 == str2);
System.out.println("================");
System.out.println(str.equals(str1));
System.out.println(str.equals(str2));
System.out.println(str1.equals(str2));
}
~~~
在這次的運行中,我們可以看到分割線下`equals()`方法的返回值都為`true`,這是因為String類中重寫的`equals()`方法會用來比較保存的字符串內容。
### 重寫equals()方法
~~~Java
public class Test {
public static void main(String[] args) {
Animal one = new Animal("花花",2,"英短");
Animal two = new Animal("花花",2,"英短");
System.out.println(one == two);
System.out.println(one.equals(two));
}
}
~~~
回到剛剛的代碼,如果只想去比較`one`和`two`這兩個對象屬性值是否相同怎么辦呢?這時候就需要我們去Animal類中重寫`equals()`方法了。
~~~
public boolean equals(Object obj) {
// 1. 首先判斷傳入的對象是否為null
if (obj == null) {
// 如果是null則直接返回false
return false;
} else {
// 2. 接著判斷兩個對象中的屬性值是否一致
// 此時是引用數據類型的強制類型轉換,形式也是類似于之前學習過的基本數據類型強制類型轉換
Animal animal = (Animal) obj;
if (this.getName().equals(animal.getName()) && this.getMonth() == animal.getMonth()
&& this.getSpecies().equals(animal.getSpecies())) {
return true;
} else {
return false;
}
}
}
~~~
完成重寫后,我們會發現再去運行測試類中的方法,結果已經發生了改變。
當然,上述代碼還是有問題的,最大的問題在于強制類型轉換的時候,如果傳入的參數不能匹配`Animal`類,則會發生一個類型轉換錯誤的異常。
~~~
/**
* 此方法就不是對equals()方法的重載了,而是對Animal類中已經存在的equals()方法的重載
* @param ani
* @return
*/
public boolean equals(Animal ani) {
// 1. 首先判斷傳入的對象是否為null
if (ani == null) {
// 如果是null則直接返回false
return false;
} else {
// 2. 接著判斷兩個對象中的屬性值是否一致
// 此時是引用數據類型的強制類型轉換,形式也是類似于之前學習過的基本數據類型強制類型轉換
if (this.getName().equals(ani.getName()) && this.getMonth() == ani.getMonth()
&& this.getSpecies().equals(ani.getSpecies())) {
return true;
} else {
return false;
}
}
}
~~~
針對于,有可能發生的類型轉換異常,我們可以強制限定傳入的參數為`Animal`類型,避免異常的發生。所以,我們重載了一個參數為`Animal`類型的方法,在測試類中代碼運行的時候,會自動根據參數類型定位到類型匹配的重載方法上。
**總計:**
1. 繼承Object中的equals()方法時,比較的是兩個引用是否指向同一個對象
2. 子類可以通過重寫equals()方法的形式,改變比較的內容
>[danger]需要注意的是,需要針對傳入的參數進行非空判斷,避免空指針異常
### toString()方法
toString()方法也是使用頻率比較高的一個方法。
>[info]在沒有重寫toString()方法之前,對象調用toString()方法會返回一個類的字符串表現形式,這個表現形式就是一個類的類名 + @ + 對象在內存中位置表現的哈希值。
~~~
public class Test {
public static void main(String[] args) {
Animal one = new Animal("花花",2,"英短");
Animal two = new Animal("花花",2,"英短");
System.out.println(one);
System.out.println(one.toString());
String str = new String("Hello,world!");
System.out.println(str);
System.out.println(str.toString());
}
}
~~~
輸出結果
~~~
com.dodoke.animal.model.Animal@15db9742
com.dodoke.animal.model.Animal@15db9742
Hello,world!
Hello,world!
~~~
由此可見,當直接打印對象的對象名時,會默認調用對象繼承自`Object`類的`toString()`方法
### 在類中重寫toString()方法
可以通過eclipse完成重寫toString()方法
~~~
@Override
public String toString() {
return "Animal [name=" + name + ", month=" + month + ", species=" + species + "]";
}
~~~
總結:
* 直接輸出對象名時,默認會直接調用類中的toString方法
* 繼承Object中的`toString()`方法時,輸出對象的字符串表現形式:類型信息+@+地址信息
* 子類可以通過重寫`toString()`方法的形式,改變輸出的內容及其表現形式
## final關鍵字
通過繼承,我們可以提高代碼的復用性和靈活性。但是在有些時候,我們并不希望這個類被繼承,這個方法被重寫和這個變量的值被修改,那么這個時候`final`關鍵字就起了大作用了。
1. `final`修飾類,則該類不允許被繼承
~~~
public final class Animal {}
final public class Animal {}
~~~
我們常用的`String`類和`System`類等,都是使用final去修飾的。
2. `final`修飾方法,則該方法不允許被重寫,但可以正常被子類繼承使用
~~~
public final void eat() {}
~~~
3. `final`修飾變量
* `final`修飾局部變量,初始化(賦值)后不允許被修改
~~~
public void eat() {
int temp = 10;
final int temp1 = 12;
temp1 = 14;//不可修改
final int temp2;
temp2 = 13;
temp2 = 14;//不可修改
System.out.println(this.getName() + "在吃東西");
}
~~~
* `final`修飾屬性變量,同樣復制后不可以修改
~~~
public class Animal {
public final int temp = 15;
public final int temp1;
public final int temp2;
public final int temp3;
public Animal() {
super();
temp1 = 20;
}
{
temp2 = 20;
}
static {
temp3 = 20;//不可以賦值
}
}
~~~
被final定義的成員屬性,只有三種方式進行賦值:
* 定義時直接賦值;
* 在構造代碼塊中賦值;
* 在構造方法中賦值。
### final其他應用
之前我們使用final修飾變量的時候,實驗的都是基本數據類型,如果使用final修飾引用數據類型會發生什么樣的變化呢?
~~~
public void eat() {
final Animal ani = new Animal();
ani = new Animal();//不允許重新賦值,也就是ani所代表的引用地址不允許被修訂
ani.setMonth(3);//對象中的屬性值是允許被修改的
ani.setMonth(2);
System.out.println(this.getName() + "在吃東西");
}
~~~
**總結:**
`final `當修飾的變量為引用類型時,對象不允許再被重新實例化(不允許再被new),但是此對象里的屬性的值依然可以更改。
>[info]在實際的開發中, `final `用的最多的場景是結合 static 關鍵字定義類變量,即靜態變量。定義的這個變量我們也稱之為常量。
定義為` final `另一個意圖就是將變量的值保護起來。
~~~
public class Animal {
public static final int TEMP = 200;
public final static int TEMP1 = 200;
}
~~~
`final`修飾的這個常量需要字母全部大寫
>[danger]`final`不能修飾構造方法
## 注解
在之前的的學習中,我們提到可以適應`@Override`來添加對于重寫方法的約束,同樣的在eclipse中采用快捷方式完成重寫方法時也會出現`@Override`。這樣一個事物,我們稱之為注解。在本章節中,我們就來簡單的學習使用注解。
注解是JDK1.5版本引入的一個特性,可以聲明在包、類、屬性、方法、局部變量、方法參數等的前面,用來對這些元素進行說明、注釋。簡單來說,注解就相當于一個標記,在程序中使用了注解就相當于給這個程序打上了某種標簽。被打過標簽的一些程序,以后Java編輯器、開發工具、以及其他的程序就會借由這個標簽來了解你所寫的程序。
按照運行機制分:
- 源碼注解:注解只在源碼中存在,編譯成.class文件就不存在了。如`@Override`
- 編譯時注解:注解在源碼和.class文件中都存在。
- 運行時注解:在運行階段還起作用,甚至會影響運行邏輯的注解。如以后要學習到的框架的注解`@Autowired`
按照來源分:
- 來自JDK的注解
- 來自第三方的注解
- 我們自己定義的注解
>[warning]還有特殊的注解:元注解:對注解進行注釋的
## 練習
一、選擇
1. 在Java中,以下程序的輸出結果是

~~~
A. Super-->print
B. Test-->print
C. Super-->print
Test-->print
D. 編譯錯誤
~~~
2. 在Java中,以下關于方法重載和方法重寫描述正確的是
~~~
A. 方法重載和方法重寫實現的功能相同
B. 方法重載出現在父子關系中,方法重寫是在同一類中
C. 方法重載的返回類型必須一致,參數項必須不同
D. 方法重寫需要出現在滿足繼承關系的子類中
~~~
3. 哪個選項中的方法插入到(1)處可以正確實現方法重寫

~~~
A. public static void bark(){}
B. public final void display(){}
C. public void eat(String food){}
D. public boolean eat(String food){}
~~~
4. 在下面程序的注釋1處補充上下列()方法,會導致在編譯過程中發生錯誤
~~~
A. public float getNum() { return 4.0f; }
B. private float getNum() {return 4.0f;}
C. public void getNum(double d){}
D. public double getNum(float d){ return 4.0f; }
~~~
5. 如下Java源文件,編譯并運行Child.java后,以下結果描述正確的是

~~~
A. 編譯錯誤:沒有找到構造器Child()
B. 編譯錯誤:沒有找到構造器Parent1()
C. 正確運行,沒有輸出值
D. 正確運行,輸出結果為:parent2
~~~
6. 分析如下所示的Java代碼,則選項中的說法正確的是

~~~
A. 第2行錯誤,Test類的構造函數中參數名稱應與其父類構造函數中的參數名相同
B. 第3行錯誤,應使用super關鍵字調用父類的name屬性,改為super.name="hello"
C. 第4行錯誤,調用父類構造方法的語句必須放在子類構造方法中的第一行
D. 程序編譯通過,無錯誤
~~~
7. 關于super的說法正確的是
~~~
A. 是指當前子類的對象
B. 是指當前類的對象
C. 是指當前父類的對象
D. 可以用在main()方法中
~~~
8. 閱讀下面JAVA代碼片段,正確的選項是

~~~
A. 第1行編譯錯誤,但能輸出正確結果
B. 第2行編譯錯誤,但能輸出正確結果
C. 第3行編譯錯誤,不能輸出正確結果
D. 第4行編譯錯誤,不能輸出正確結果
~~~
9. 下列關于super和this的說法正確的是(多選)
~~~
A. this關鍵字通常指當前對象
B. super關鍵字則指父類對象
C. 在一個類中this只可以調用當前類中公有屬性和方法
D. 在一個類中super可以調用父類中允許被訪問的屬性和方法
~~~
10. 下列關于Object類的敘述錯誤的是
~~~
A. Object類是所有類的父類
B. 所有類都可以繼承Object中允許被繼承的方法
C. 一個類沒有使用extends關鍵字明確標識繼承關系,則默認繼承Object類
D. 要想繼承Object類,必須使用extends關鍵字標識繼承關系,否則不會實現繼承
~~~
11. 該段代碼的運行結果為

~~~
A. true
B. 相等
C. 不相等
D. false
~~~
12. 在Java中,關于繼承的說法錯誤的是
~~~
A. 使用extends關鍵字實現一個類繼承另一個類
B. 所有的Java類都直接或間接地繼承了java.lang.Object類
C. 在子類的構造方法中,必須顯式調用父類的構造方法
D. 在子類的構造方法中,可以通過super關鍵字調用父類的構造方法
~~~
13. 下列關于final的說法錯誤的是
~~~
A. final修飾的變量值不允許被修改
B. final修飾的方法不可被重寫
C. final可以修飾所有方法
D. final不可以修飾構造方法
~~~
二、編程
1. 編程練習:某公司要開發“XX車行管理系統”,請使用面向對象的思想,設計自定義類描述自行車、電動車和三輪車。
程序參考運行效果圖如下:

任務
任務分析;
**第一步:分析自行車、電動車和三輪車的共性:**
1. 都是非機動車,具有非機動車的基本特征
2. 都有運行的方法
**第二步:根據共性,定義非機動車**
屬性:品牌、顏色、輪子(默認2個)、座椅(默認?? 1個)
方法:
1. 編寫無參構造方法、雙參構造方法和四參構造方法,其中,在雙參構造方法中,完成對品牌和顏色的賦值;在四參構造方法中,完成對所有屬性的賦值
2. 編寫運行的方法,描述內容為:這是一輛\*\*顏色的,\*\*牌的非機動車,有\*\*個輪子,有\*\*個座椅的非機動車。其中\*\*的數據由屬性提供
**第三步:定義自行車、電動車和三輪車分別繼承自行車類,要求:**
* **自行車類:**
1. 在構造方法中調用父類多參構造,完成屬性賦值
2. 重寫運行方法,描述內容為:這是一輛\*\*顏色的,\*\*牌的自行車。其中\*\*的數據由屬性提供
* **電動車:**
1. ?增加“電池品牌”屬性
2. ?重寫運行方法,描述內容為:這是一輛使用\*\*牌電池的電動車。其中\*\*的數據由屬性提供
* **三輪車:**
1. 在無參構造中實現對輪子屬性值進行修改
2. 重寫運行方法,描述內容為:三輪車是一款有\*\*個輪子的非機動車。其中\*\*的數據由屬性提供
2. 請使用面向對象的思想,設計自定義類Person繼承Object類,重寫toString方法實現對象信息輸出。
運行效果如下圖所示:

思路分析
* 創建一個 Person 類繼承自 Object,其中類的結構要求為:
屬性:name(姓名)、age(年齡)、sex(性別)
方法:
* 創建帶參(name、age、sex為參數)構造方法
* 重寫 toString 方法,輸出信息格式為:姓名:\*\* 年齡:\*\* 性別:\*\*(其中,\*\*為對象對應屬性值)
* 創建測試類,在測試方法中,實例化 Person對 象,并傳入三個屬性值。然后,分別通過直接打印Person對象以及利用重寫的 toString 方法,打印輸出2行對象信息。
~~~
public class Person{
//私有屬性:name(姓名)、age(年齡)、sex(性別)
//帶參構造方法(name、age、sex為參數)
//通過封裝實現對屬性的get/set方法設定
//重寫toString方法,表示形式為:姓名:+**+ 年齡:+**+ 性別:+**
}
~~~
3. 請使用面向對象的思想,實現楊梅和仙人蕉的信息描述。
程序參考運行效果圖如下:

思路分析:
**1、根據楊梅和香蕉的共性,抽取父類水果(Fruits)**
私有屬性:水果的形狀(shape)和口感(taste)
方法:
* 帶參構造函數(參數為shape和taste)
* 創建無參無返回值得方法eat(描述內容為:水果可供人們食用!)
* 重寫equals方法,比較兩個對象是否相等(比較shape,taste)
**2、子類Waxberry**
私有屬性:顏色(color)
方法:
* 調用父類的構造方法,完成屬性賦值
* 創建不允許重寫的face方法,描述為:楊梅:\*\*、\*\*,果味酸甜適中。
* 重寫父類eat方法,描述為:楊梅酸甜適中,非常好吃!
* 重寫toString方法,輸出的表現形式不同(輸出shape,color,taste)
* 要求Waxberry類不允許有子類
**3、子類:Banana**
私有屬性:品種(variety)
方法:
* 帶參構造方法為所有屬性賦值
* 創建無參無返回值的advantage方法,描述為:\*\*果形\*\*,果肉香甜,可供生食。
* 重載要求(2)中的advantage方法(帶參數color),描述為:\*\*顏色為\*\*
**4、測試,運行效果參照效果圖:**
* 實例化2個父類對象,并傳入兩組相同的參數值
* 調用父類eat方法
* 測試重寫equals方法,判斷兩個對象是否相等
* 實例化子類Wacberry對象,并傳入相關參數值
* 調用子類face方法和重寫父類eat方法后的eat方法
* 測試重寫toString方法,輸出子類對象的信息
* 實例化Banana類對象,并傳入相關參數值
* 調用子類的advantage和它的重載方法