java面向?qū)ο蠡A(chǔ)(中)
## 本章學(xué)習(xí)目標(biāo)
- 理解封裝的意義
- 知道public、protected、缺省、private幾種權(quán)限修飾符的區(qū)別
- 知道標(biāo)準(zhǔn)Javabean的要求
- 掌握對象數(shù)組的聲明、初始化、遍歷
- 掌握增強(qiáng)for循環(huán)的使用
- 理解繼承的意義
- 掌握用extends實(shí)現(xiàn)子類繼承父類
- 知道Java子類繼承父類的要求
- 掌握方法重寫的概念和要求
- 掌握toString、hashCode和equals方法的重寫
- 掌握關(guān)鍵字final的使用
- 了解native關(guān)鍵字
- 掌握用abstract聲明抽象類和抽象方法
- 掌握用interface聲明接口
- 掌握用implements實(shí)現(xiàn)接口
- 掌握接口與抽象類的區(qū)別
## 6.1 封裝
### 6.1.1 封裝概述
為什么需要封裝?
* 我要用洗衣機(jī),只需要按一下開關(guān)和洗滌模式就可以了。有必要了解洗衣機(jī)內(nèi)部的結(jié)構(gòu)嗎?有必要碰電動機(jī)嗎?
* 我們使用的電腦,內(nèi)部有CPU、硬盤、鍵盤、鼠標(biāo)等等,每一個(gè)部件通過某種連接方式一起工作,但是各個(gè)部件之間又是獨(dú)立的。
* 現(xiàn)實(shí)生活中,每一個(gè)個(gè)體與個(gè)體之間是有邊界的,每一個(gè)團(tuán)體與團(tuán)體之間是有邊界的,而同一個(gè)個(gè)體、團(tuán)體內(nèi)部的信息是互通的,只是對外有所隱瞞。
面向?qū)ο缶幊陶Z言是對客觀世界的模擬,客觀世界里每一個(gè)事物的內(nèi)部信息都是隱藏在對象內(nèi)部的,外界無法直接操作和修改,只能通過指定的方式進(jìn)行訪問和修改。封裝可以被認(rèn)為是一個(gè)保護(hù)屏障,防止該類的代碼和數(shù)據(jù)被其他類隨意訪問。適當(dāng)?shù)姆庋b可以讓代碼更容易理解與維護(hù),也加強(qiáng)了代碼的安全性。
隨著我們系統(tǒng)越來越復(fù)雜,類會越來越多,那么類之間的訪問邊界必須把握好,面向?qū)ο蟮拈_發(fā)原則要遵循“高內(nèi)聚、低耦合”,而“高內(nèi)聚,低耦合”的體現(xiàn)之一:
* 高內(nèi)聚:類的內(nèi)部數(shù)據(jù)操作細(xì)節(jié)自己完成,不允許外部干涉;
* 低耦合:僅對外暴露少量的方法用于使用
隱藏對象內(nèi)部的復(fù)雜性,只對外公開簡單和可控的訪問方式,從而提高系統(tǒng)的可擴(kuò)展性、可維護(hù)性。通俗的講,把該隱藏的隱藏起來,該暴露的暴露出來。這就是封裝性的設(shè)計(jì)思想。
```java
package com.atguigu.oop.encapsulation;
public class Circle {
private double radius;
public void setRadius(double radius) {
if(radius < 0){
return;
}
this.radius = radius;
}
public double getRadius() {
return radius;
}
}
```
```java
package com.atguigu.oop.encapsulation;
public class TestCircle {
public static void main(String[] args) {
double r1 = -1.5;
double r2 = 2.5;
Circle c1 = new Circle();
Circle c2 = new Circle();
// c1.radius = r1;
// c2.radius = r2;
/*
if(r1>0){
c1.radius = r1;
}
if(r2 > 0){
c2.radius = r2;
}*/
c1.setRadius(r1);
c2.setRadius(r2);
System.out.println(c1.getRadius());
System.out.println(c2.getRadius());
}
}
```
### 6.1.2 幾種權(quán)限修飾符
如何實(shí)現(xiàn)封裝呢?實(shí)現(xiàn)封裝就是指控制類或成員的可見性范圍,這就需要依賴訪問控制修飾符(也稱為權(quán)限修飾符)來控制。
| 修飾符 | 本類 | 本包 | 其他包子類 | 其他包非子類 |
| --------- | ---- | ---- | ---------- | ------------ |
| private | √ | × | × | × |
| 缺省 | √ | √ | × | × |
| protected | √ | √ | √ | × |
| public | √ | √ | √ | √ |
外部類:public和缺省
成員變量、成員方法、構(gòu)造器、成員內(nèi)部類:public,protected,缺省,private
### 6.1.3 成員變量/屬性私有化問題
**<span style="color:red">成員變量(field)私有化</span>之后,提供標(biāo)準(zhǔn)的<span style="color:red">get/set</span>方法,我們把這種成員變量也稱為<span style="color:red">屬性(property)</span>。**
> 或者可以說只要能通過get/set操作的就是事物的屬性,哪怕它沒有對應(yīng)的成員變量。
1、成員變量封裝的目的
* 隱藏類的實(shí)現(xiàn)細(xì)節(jié)
* 讓使用者只能通過事先預(yù)定的方法來訪問數(shù)據(jù),從而可以在該方法里面加入控制邏輯,限制對成員變量的不合理訪問。還可以進(jìn)行數(shù)據(jù)檢查,從而有利于保證對象信息的完整性。
* 便于修改,提高代碼的可維護(hù)性。主要說的是隱藏的部分,在內(nèi)部修改了,如果其對外可以的訪問方式不變的話,外部根本感覺不到它的修改。例如:Java8->Java9,String從char[]轉(zhuǎn)為byte[]內(nèi)部實(shí)現(xiàn),而對外的方法不變,我們使用者根本感覺不到它內(nèi)部的修改。
2、實(shí)現(xiàn)步驟
(1)使用 `private` 修飾成員變量
```java
private 數(shù)據(jù)類型 變量名 ;
```
代碼如下:
```java
public class Person {
private String name;
private int age;
private boolean marry;
}
```
(2)提供 `getXxx`方法 / `setXxx` 方法,可以訪問成員變量,代碼如下:
靜態(tài)變量的get/set方法也靜態(tài)的,當(dāng)局部變量與靜態(tài)變量重名時(shí),使用“類名.靜態(tài)變量”進(jìn)行區(qū)分。
```java
public class Person {
private String name;
private int age;
private boolean marry;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setMarry(boolean marry){
this.marry = marry;
}
public boolean isMarry(){
return marry;
}
}
```
IDEA自動生成get/set方法模板
- 大部分鍵盤模式按Alt + Insert鍵。
- 部分鍵盤模式需要按Alt + Insert + Fn鍵。
- Mac電腦快捷鍵需要單獨(dú)設(shè)置


(3)測試
```java
package com.atguigu.encapsulation;
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
//實(shí)例變量私有化,跨類是無法直接使用的
/* p.name = "張三";
p.age = 23;
p.marry = true;*/
p.setName("張三");
System.out.println("p.name = " + p.getName());
p.setAge(23);
System.out.println("p.age = " + p.getAge());
p.setMarry(true);
System.out.println("p.marry = " + p.isMarry());
}
}
```
### 6.1.4 標(biāo)準(zhǔn)JavaBean
`JavaBean` 是 Java語言編寫類的一種標(biāo)準(zhǔn)規(guī)范。符合`JavaBean` 的類,要求:
(1)類必須是公共的和具體的(非抽象的,關(guān)于抽象請看6.5小節(jié))
(2)成員變量私有化,并提供用來操作成員變量的`set` 和`get` 方法
(3)必須有無參構(gòu)造
(4)提供有參構(gòu)造(可選)
(5)建議重寫toString方法、equals和hashCode方法等(關(guān)于重寫請看6.3小節(jié))
```java
public class ClassName{
//成員變量
//構(gòu)造方法
//無參構(gòu)造方法【必須】
//有參構(gòu)造方法【建議】
//getXxx()
//setXxx()
//其他成員方法
}
```
編寫符合`JavaBean` 規(guī)范的類,以學(xué)生類為例,標(biāo)準(zhǔn)代碼如下:
```java
public class Student {
// 成員變量
private String name;
private int age;
// 構(gòu)造方法
public Student() {
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
// get/set成員方法
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return age;
}
//其他成員方法列表
public String getInfo(){
return "姓名:" + name + ",年齡:" + age;
}
}
```
測試類,代碼如下:
```java
public class TestStudent {
public static void main(String[] args) {
// 無參構(gòu)造使用
Student s = new Student();
s.setName("柳巖");
s.setAge(18);
System.out.println(s.getName() + "---" + s.getAge());
System.out.println(s.getInfo());
// 帶參構(gòu)造使用
Student s2 = new Student("趙麗穎", 18);
System.out.println(s2.getName() + "---" + s2.getAge());
System.out.println(s2.getInfo());
}
}
```
## 6.2 對象數(shù)組
數(shù)組是用來存儲一組數(shù)據(jù)的容器,一組基本數(shù)據(jù)類型的數(shù)據(jù)可以用數(shù)組裝,那么一組對象也可以使用數(shù)組來裝。
即數(shù)組的元素可以是基本數(shù)據(jù)類型,也可以是引用數(shù)據(jù)類型。當(dāng)元素是引用數(shù)據(jù)類型是,我們稱為對象數(shù)組。
> 注意:對象數(shù)組,首先要創(chuàng)建數(shù)組對象本身,即確定數(shù)組的長度,然后再創(chuàng)建每一個(gè)元素對象,如果不創(chuàng)建,數(shù)組的元素的默認(rèn)值就是null,所以很容易出現(xiàn)空指針異常NullPointerException。
### 6.2.1 對象數(shù)組的聲明和使用
案例:
(1)定義矩形類,包含長、寬屬性,area()求面積方法,perimeter()求周長方法,String getInfo()返回圓對象的詳細(xì)信息的方法
(2)在測試類中創(chuàng)建長度為5的Rectangle[]數(shù)組,用來裝3個(gè)矩形對象,并給3個(gè)矩形對象的長分別賦值為10,20,30,寬分別賦值為5,15,25,遍歷輸出
```java
package com.atguigu.test08.array;
public class Rectangle {
double length;
double width;
double area(){//面積
return length * width;
}
double perimeter(){//周長
return 2 * (length + width);
}
String getInfo(){
return "長:" + length +
",寬:" + width +
",面積:" + area() + //直接調(diào)用本類的另一個(gè)實(shí)例方法
",周長:" + perimeter();
}
}
```
```java
package com.atguigu.test08.array;
public class ObjectArrayTest {
public static void main(String[] args) {
//聲明并創(chuàng)建一個(gè)長度為3的矩形對象數(shù)組
Rectangle[] array = new Rectangle[3];
//創(chuàng)建3個(gè)矩形對象,并為對象的實(shí)例變量賦值,
//3個(gè)矩形對象的長分別是10,20,30
//3個(gè)矩形對象的寬分別是5,15,25
//調(diào)用矩形對象的getInfo()返回對象信息后輸出
for (int i = 0; i < array.length; i++) {
//創(chuàng)建矩形對象
array[i] = new Rectangle();
//為矩形對象的成員變量賦值
array[i].length = (i+1) * 10;
array[i].width = (2*i+1) * 5;
//獲取并輸出對象對象的信息
System.out.println(array[i].getInfo());
}
}
}
```
### 6.2.2 對象數(shù)組的內(nèi)存圖分析
對象數(shù)組中數(shù)組元素存儲的是元素對象的首地址。

### 6.2.3 增強(qiáng)for循環(huán)
增強(qiáng)for循環(huán)是一種語法糖,即在遍歷數(shù)組中,表面上是一種新語法,但是編譯后仍然是我們學(xué)過的普通for循環(huán)。
```java
for(元素類型 元素名 : 數(shù)組名){
}
```
注意:增強(qiáng)for循環(huán)只能用于查看元素,或修改元素屬性值,但不能替換元素。增強(qiáng)for循環(huán)遍歷數(shù)組是沒有下標(biāo)的。
```java
package com.atguigu.test08.array;
public class TestForeach {
public static void main(String[] args) {
//聲明并創(chuàng)建一個(gè)長度為3的矩形對象數(shù)組
Rectangle[] array = new Rectangle[3];
array[0] = new Rectangle(10,5);
array[1] = new Rectangle(20,15);
array[2] = new Rectangle(30,25);
for(Rectangle r : array){
System.out.println(r.getInfo());
}
}
}
```
普通for循環(huán)與增強(qiáng)for循環(huán)在遍歷數(shù)組時(shí)的區(qū)別:
| | 普通for循環(huán) | 增強(qiáng)for循環(huán) |
| ------------------------ | ------------ | ----------- |
| 是否需要指定下標(biāo)信息 | 是 | 否 |
| 是否可以替換元素 | 是 | 否 |
| 是否可以遍歷數(shù)組部分元素 | 是 | 否 |
| 遍歷查看元素信息時(shí) | 稍微復(fù)雜一點(diǎn) | 更簡潔 |
## 6.3 繼承
### 6.3.1 繼承的概述
1、生活中的繼承
* 財(cái)產(chǎn):富二代
* 樣貌:如圖所示:
<img src="images/繼承2.jpg" style="zoom:50%;" />
繼承有延續(xù)(下一代延續(xù)上一代的基因、財(cái)富)、擴(kuò)展(下一代和上一代又有所不同)的意思。
社會的進(jìn)步就是源于知識、財(cái)富、經(jīng)驗(yàn)得以繼承,又可以不斷的翻新。
2、Java中的繼承
如圖所示:
<img src="images/貓狗繼承1.jpg" style="zoom:50%;" />
多個(gè)類中存在相同屬性和行為時(shí),將這些內(nèi)容抽取到單獨(dú)一個(gè)類中,那么多個(gè)類中無需再定義這些屬性和行為,只需要和抽取出來的類構(gòu)成某種關(guān)系。如圖所示:
<img src="images/貓狗繼承2.jpg" style="zoom: 50%;" />
其中,多個(gè)類可以稱為**子類**,也叫**派生類**;多個(gè)類抽取出來的這個(gè)類稱為**父類**、**超類(superclass)**或者**基類**。
繼承描述的是事物之間的所屬關(guān)系,這種關(guān)系是:`is-a` 的關(guān)系。例如,圖中貓屬于動物,狗也屬于動物??梢?,父類更通用或更一般,子類更具體。我們通過繼承,可以使多種事物之間形成一種關(guān)系體系。
3、繼承的好處
* 提高**代碼的復(fù)用性**。
* 提高**代碼的擴(kuò)展性**。
* 表示類與類之間的is-a關(guān)系
### 6.3.2 繼承的語法格式
通過 `extends` 關(guān)鍵字,可以聲明一個(gè)子類繼承另外一個(gè)父類,定義格式如下:
```java
【修飾符】 class 父類 {
...
}
【修飾符】 class 子類 extends 父類 {
...
}
```
1、父類
```java
package com.atguigu.inherited.grammar;
/*
* 定義動物類Animal,做為父類
*/
public class Animal {
// 定義name屬性
String name;
// 定義age屬性
int age;
// 定義動物的吃東西方法
public void eat() {
System.out.println(age + "歲的" + name + "在吃東西");
}
}
```
2、子類
```java
package com.atguigu.inherited.grammar;
/*
* 定義貓類Cat 繼承 動物類Animal
*/
public class Cat extends Animal {
int count;//記錄每只貓抓的老鼠數(shù)量
// 定義一個(gè)貓抓老鼠的方法catchMouse
public void catchMouse() {
count++;
System.out.println("抓老鼠,已經(jīng)抓了" + count + "只老鼠");
}
}
```
3、測試類
```java
package com.atguigu.inherited.grammar;
public class TestCat {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)貓類對象
Cat cat = new Cat();
// 為該貓類對象的name屬性進(jìn)行賦值
cat.name = "Tom";
// 為該貓類對象的age屬性進(jìn)行賦值
cat.age = 2;
// 調(diào)用該貓繼承來的eat()方法
cat.eat();
// 調(diào)用該貓的catchMouse()方法
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
//調(diào)用該貓的eat()方法
cat.eat();
}
}
```
### 6.3.3 IDEA中如何查看繼承關(guān)系
1、子類和父類是一種相對的概念
例如:B類對于A來說是子類,但是對于C類來說是父類
2、查看繼承關(guān)系快捷鍵
例如:選擇A類名,按Ctrl + H就會顯示A類的繼承樹。
:A類的父類和子類
:A類的父類
:A類的所有子類
例如:在類繼承目錄樹中選中某個(gè)類,比如C類,按Ctrl+ Alt+U就會用圖形化方式顯示C類的繼承祖宗
<img src="images/image-20211229180113255.png" alt="image-20211229180113255" style="zoom: 67%;" />
### 6.3.4 繼承的特點(diǎn)
1、每一個(gè)類有一個(gè)默認(rèn)的父類java.lang.Object類,它也是所有類的根父類
<img src="images/image-20220616110817804.png" alt="image-20220616110817804" style="zoom: 67%;" />
2、子類會繼承父類所有的實(shí)例變量和實(shí)例方法
從類的定義來看,類是一類具有相同特性的事物的抽象描述。父類是所有子類共同特征的抽象描述。而實(shí)例變量和實(shí)例方法就是事物的特征,那么父類中聲明的實(shí)例變量和實(shí)例方法代表子類事物也有這個(gè)特征。
- 當(dāng)子類對象被創(chuàng)建時(shí),在堆中給對象申請內(nèi)存時(shí),就要看子類和父類都聲明了什么實(shí)例變量,這些實(shí)例變量都要分配內(nèi)存。
- 當(dāng)子類對象調(diào)用方法時(shí),編譯器會先在子類模板中看該類是否有這個(gè)方法,如果沒找到,會看它的父類甚至父類的父類是否聲明了這個(gè)方法,遵循從下往上找的順序,找到了就停止,一直到根父類都沒有找到,就會報(bào)編譯錯誤。
==所以繼承意味著子類的對象除了看子類的類模板還要看父類的類模板。==

3、Java只支持單繼承,不支持多重繼承
```java
public class A{}
class B extends A{}
//一個(gè)類只能有一個(gè)父類,不可以有多個(gè)直接父類。
class C extends B{} //ok
class C extends A,B... //error
```
4、Java支持多層繼承(繼承體系)
```java
class A{}
class B extends A{}
class C extends B{}
```
> 頂層父類是Object類。所有的類默認(rèn)繼承Object,作為父類。
5、一個(gè)父類可以同時(shí)擁有多個(gè)子類
```java
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
```
### 6.3.5 繼承時(shí)權(quán)限修飾符限制問題
權(quán)限修飾符:public,protected,缺省,private
| 修飾符 | 本類 | 本包(包含子類和非子類) | 其他包子類 | 其他包非子類 |
| --------- | ---- | ------------------------- | ----------------------------------------- | ------------ |
| private | √ | × | × | × |
| 缺省 | √ | √(本包子類非子類都可見) | × | × |
| protected | √ | √(本包子類非子類都可見) | √(其他包僅限于子類中可見,直接使用方式) | × |
| public | √ | √ | √ | √ |
外部類:public和缺省
成員變量、成員方法等:public,protected,缺省,private
1、外部類要跨包使用必須是public,否則僅限于本包使用
(1)外部類的權(quán)限修飾符如果缺省,本包使用沒問題

(2)外部類的權(quán)限修飾符如果缺省,跨包使用有問題

2、父類成員變量私有化(private)
子類雖會繼承父類私有(private)的成員變量,但子類不能對繼承的私有成員變量直接進(jìn)行訪問,可通過繼承的get/set方法進(jìn)行訪問。如圖所示:

父類代碼:
```java
package com.atguigu.inherited.modifier;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年齡:" + age;
}
}
```
子類代碼:
```java
package com.atguigu.inherited.modifier;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
public String getInfo(){
// return "姓名:" + name + ",年齡:" + age;
//在子類中不能直接使用父類私有的name和age
return "姓名:" + getName() + ",年齡:" + getAge();
}
}
```
測試類代碼:
```java
package com.atguigu.inherited.modifier;
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
student.setName("張三");
student.setAge(23);
student.setScore(89);
System.out.println(student.getInfo());
}
}
```
IDEA在Debug模式下查看學(xué)生對象信息:

3、成員的權(quán)限修飾符問題
(1)本包下使用:成員的權(quán)限修飾符可以是public、protected、缺省

(2)跨包使用時(shí),如果類的權(quán)限修飾符缺省,成員權(quán)限修飾符>類的權(quán)限修飾符也沒有意義

(3)跨包下使用:要求嚴(yán)格

注意:跨包時(shí)父類中protected修飾的成員,僅限于在子類中訪問,且是子類對象自己訪問。
### 6.3.6 繼承時(shí)構(gòu)造器問題
子類繼承父類時(shí),不會繼承父類的構(gòu)造器。必須通過super()或super(實(shí)參列表)的方式調(diào)用父類的構(gòu)造器。
- super();:子類構(gòu)造器中一定會調(diào)用父類的構(gòu)造器,默認(rèn)調(diào)用父類的無參構(gòu)造,super();可以省略。
- super(實(shí)參列表);:如果父類沒有無參構(gòu)造或者有無參構(gòu)造但是子類就是想要調(diào)用父類的有參構(gòu)造,則必須使用super(實(shí)參列表);的語句。
- super()和super(實(shí)參列表)都只能出現(xiàn)在子類構(gòu)造器的首行。
```java
package com.atguigu.constructor;
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
System.out.println("父類Employee無參構(gòu)造");
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
System.out.println("父類Employee有參構(gòu)造");
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public String getInfo(){
return "姓名:" + name + ",年齡:" + age +",薪資:" + salary;
}
}
```
```java
package com.atguigu.constructor;
public class Manager extends Employee{
private double bonusRate;
public Manager() {
super();//可以省略
}
public Manager(String name, int age, double salary, double bonusRate) {
super(name, age, salary);//調(diào)用父類的有參構(gòu)造
this.bonusRate = bonusRate;
}
public double getBonusRate() {
return bonusRate;
}
public void setBonusRate(double bonusRate) {
this.bonusRate = bonusRate;
}
public String getInfo() {
return "姓名:" + getName() + ",年齡:" + getAge() +",薪資:" + getSalary() +",獎金比例:" + bonusRate;
}
}
```

```java
package com.atguigu.constructor;
public class TestEmployee {
public static void main(String[] args) {
Manager m1 = new Manager();
System.out.println(m1.getInfo());
Manager m2 = new Manager("張三",23,20000,0.1);
System.out.println(m2.getInfo());
}
}
```
形式一:
```java
class A{
}
class B extends A{
}
class Test1{
public static void main(String[] args){
B b = new B();
//A類和B類都是默認(rèn)有一個(gè)無參構(gòu)造,B類的默認(rèn)無參構(gòu)造中還會默認(rèn)調(diào)用A類的默認(rèn)無參構(gòu)造
//但是因?yàn)槎际悄J(rèn)的,沒有打印語句,看不出來
}
}
```
形式二:
```java
class A{
A(){
System.out.println("A類無參構(gòu)造器");
}
}
class B extends A{
}
class Test2{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個(gè)無參構(gòu)造,
//B類默認(rèn)有一個(gè)無參構(gòu)造,
//B類的默認(rèn)無參構(gòu)造中會默認(rèn)調(diào)用A類的無參構(gòu)造
//可以看到會輸出“A類無參構(gòu)造器"
}
}
```
形式三:
```java
class A{
A(){
System.out.println("A類無參構(gòu)造器");
}
}
class B extends A{
B(){
System.out.println("B類無參構(gòu)造器");
}
}
class Test3{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個(gè)無參構(gòu)造,
//B類顯示聲明一個(gè)無參構(gòu)造,
//B類的無參構(gòu)造中雖然沒有寫super(),但是仍然會默認(rèn)調(diào)用A類的無參構(gòu)造
//可以看到會輸出“A類無參構(gòu)造器"和"B類無參構(gòu)造器")
}
}
```
形式四:
```java
class A{
A(){
System.out.println("A類無參構(gòu)造器");
}
}
class B extends A{
B(){
super();
System.out.println("B類無參構(gòu)造器");
}
}
class Test4{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個(gè)無參構(gòu)造,
//B類顯示聲明一個(gè)無參構(gòu)造,
//B類的無參構(gòu)造中明確寫了super(),表示調(diào)用A類的無參構(gòu)造
//可以看到會輸出“A類無參構(gòu)造器"和"B類無參構(gòu)造器")
}
}
```
形式五:
```java
class A{
A(int a){
System.out.println("A類有參構(gòu)造器");
}
}
class B extends A{
B(){
System.out.println("B類無參構(gòu)造器");
}
}
class Test5{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個(gè)有參構(gòu)造,沒有寫無參構(gòu)造,那么A類就沒有無參構(gòu)造了
//B類顯示聲明一個(gè)無參構(gòu)造,
//B類的無參構(gòu)造沒有寫super(...),表示默認(rèn)調(diào)用A類的無參構(gòu)造
//編譯報(bào)錯,因?yàn)锳類沒有無參構(gòu)造
}
}
```


形式六:
```java
class A{
A(int a){
System.out.println("A類有參構(gòu)造器");
}
}
class B extends A{
B(){
super();
System.out.println("B類無參構(gòu)造器");
}
}
class Test6{
public static void main(String[] args){
B b = new B();
//A類顯示聲明一個(gè)有參構(gòu)造,沒有寫無參構(gòu)造,那么A類就沒有無參構(gòu)造了
//B類顯示聲明一個(gè)無參構(gòu)造,
//B類的無參構(gòu)造明確寫super(),表示調(diào)用A類的無參構(gòu)造
//編譯報(bào)錯,因?yàn)锳類沒有無參構(gòu)造
}
}
```

形式七:
```java
class A{
A(int a){
System.out.println("A類有參構(gòu)造器");
}
}
class B extends A{
B(int a){
super(a);
System.out.println("B類有參構(gòu)造器");
}
}
class Test7{
public static void main(String[] args){
B b = new B(10);
//A類顯示聲明一個(gè)有參構(gòu)造,沒有寫無參構(gòu)造,那么A類就沒有無參構(gòu)造了
//B類顯示聲明一個(gè)有參構(gòu)造,
//B類的有參構(gòu)造明確寫super(a),表示調(diào)用A類的有參構(gòu)造
//會打印“A類有參構(gòu)造器"和"B類有參構(gòu)造器"
}
}
```
形式八:
```java
class A{
A(){
System.out.println("A類無參構(gòu)造器");
}
A(int a){
System.out.println("A類有參構(gòu)造器");
}
}
class B extends A{
B(){
super();//可以省略,調(diào)用父類的無參構(gòu)造
System.out.println("B類無參構(gòu)造器");
}
B(int a){
super(a);//調(diào)用父類有參構(gòu)造
System.out.println("B類有參構(gòu)造器");
}
}
class Test8{
public static void main(String[] args){
B b1 = new B();
B b2 = new B(10);
}
}
```
### 6.3.7 方法重寫(Override)
我們說父類的所有方法子類都會繼承,但是當(dāng)某個(gè)方法被繼承到子類之后,子類覺得父類原來的實(shí)現(xiàn)不適合于子類,該怎么辦呢?我們可以進(jìn)行方法重寫 (Override)
1、方法重寫
```java
package com.atguigu.inherited.override;
public class Person {
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getInfo(){
return "姓名:" + name + ",年齡:" + age;
}
}
```
```java
package com.atguigu.inherited.override;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//方法的重寫
public String getInfo(){
// return "姓名:" + name + ",年齡:" + age;
//在子類中不能直接使用父類私有的name和age
return "姓名:" + getName() + ",年齡:" + getAge() + ",成績:" + score;
}
}
```
```java
package com.atguigu.inherited.override;
public class TestStudent {
public static void main(String[] args) {
Student student = new Student();
student.setName("張三");
student.setAge(23);
student.setScore(89);
System.out.println(student.getInfo());
}
}
```
2、在子類中如何調(diào)用父類被重寫的方法
在子類中可以通過super關(guān)鍵字調(diào)用父類被重寫的方法
```java
super.被重寫方法(【實(shí)參列表】)
```
示例代碼:
```java
package com.atguigu.inherited.override;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//方法的重寫
public String getInfo(){
// return "姓名:" + name + ",年齡:" + age;
//在子類中不能直接使用父類私有的name和age
// return "姓名:" + getName() + ",年齡:" + getAge() + ",成績:" + score;
return super.getInfo() + ",成績:" + score;
}
}
```
3、IDEA重寫方法快捷鍵:Ctrl + O

```java
package com.atguigu.inherited.override;
public class Student extends Person {
private int score;
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String getInfo() {
return super.getInfo() +",成績:" + score;
}
}
```
> @Override:寫在方法上面,用來檢測是不是滿足重寫方法的要求。這個(gè)注解就算不寫,只要滿足要求,也是正確的方法覆蓋重寫。建議保留,這樣編譯器可以幫助我們檢查格式,另外也可以讓閱讀源代碼的程序員清晰的知道這是一個(gè)重寫的方法。
4、重寫方法的要求
(1)必須保證父子類之間重寫方法的名稱相同。
(2)必須保證父子類之間重寫方法的參數(shù)列表也完全相同。
(3)子類方法的返回值類型必須【小于等于】父類方法的返回值類型(小于其實(shí)就是是它的子類,例如:Student < Person)。
> 注意:如果返回值類型是基本數(shù)據(jù)類型和void,那么必須是相同
(4)子類方法的權(quán)限必須【大于等于】父類方法的權(quán)限修飾符。
> 注意:public > protected > 缺省 > private
>
> 父類私有方法不能重寫
>
> 跨包的父類缺省的方法也不能重寫
5、方法的重載和方法的重寫
方法的重載:方法名相同,形參列表不同。不看返回值類型。
方法的重寫:見上面。
(1)同一個(gè)類中
```java
package com.atguigu.inherited.method;
public class TestOverload {
public int max(int a, int b){
return a > b ? a : b;
}
public double max(double a, double b){
return a > b ? a : b;
}
public int max(int a, int b,int c){
return max(max(a,b),c);
}
}
```
(2)父子類中
```java
package com.atguigu.inherited.method;
public class TestOverloadOverride {
public static void main(String[] args) {
Son s = new Son();
s.method(1);//只有一個(gè)形式的method方法
Daughter d = new Daughter();
d.method(1);
d.method(1,2);//有兩個(gè)形式的method方法
}
}
class Father{
public void method(int i){
System.out.println("Father.method");
}
}
class Son extends Father{
public void method(int i){//重寫
System.out.println("Son.method");
}
}
class Daughter extends Father{
public void method(int i,int j){//重載
System.out.println("Daughter.method");
}
}
```
### 6.3.8 Object根父類
#### 1、Object根父類
**API(Application Programming Interface)**,應(yīng)用程序編程接口。Java API是一本程序員的`字典` ,是JDK中提供給我們使用的類的說明文檔。所以我們可以通過查詢API的方式,來學(xué)習(xí)Java提供的類,并得知如何使用它們。在API文檔中是無法得知這些類具體是如何實(shí)現(xiàn)的,如果要查看具體實(shí)現(xiàn)代碼,那么我們需要查看**src源碼**。
類 `java.lang.Object`是類層次結(jié)構(gòu)的根類,即所有類的父類。每個(gè)類都使用 `Object` 作為超類。所有對象(包括數(shù)組)都實(shí)現(xiàn)這個(gè)類的方法。
* 如果一個(gè)類沒有特別指定父類,那么默認(rèn)則繼承自O(shè)bject類。例如:
```java
public class MyClass /*extends Object*/ {
// ...
}
```
- 所有對象(包括數(shù)組)都實(shí)現(xiàn)這個(gè)類的方法。而且很多方法子類都會重寫,通過子類對象調(diào)用方法后執(zhí)行的是重寫后的方法。
#### 2、Object類的方法
Object類有11個(gè)方法,先介紹如下3個(gè):
(1)public String toString():建議子類重寫。沒有重寫的話,默認(rèn)返回的是 對象的運(yùn)行時(shí)類型 @ 對象的哈希值的十六進(jìn)制值。所有對象的toString方法,在打印對象時(shí),或者對象與字符串進(jìn)行拼接時(shí),都會自動調(diào)用。
(2)public boolean equals(Object obj):用于比較兩個(gè)對象是否“相等”。
- 絕對相等:同一個(gè)對象,地址值完全相同。沒有重寫,繼承Object里面的equals,默認(rèn)就是比較地址值,等價(jià)于==比較。
- 相對相等(邏輯相等):比較兩個(gè)對象的內(nèi)容。這個(gè)時(shí)候需要重寫。核心類庫中大部分類都重寫了equals方法,例如:String類。
(3)public int hashCode():返回該對象的哈希碼值。支持此方法是為了提高哈希表的性能。 暫時(shí)可以認(rèn)為它相當(dāng)于這個(gè)對象的身份證號碼。如果設(shè)計(jì)的好,那么對象的哈希值相同的概率就會降低。y=f(x)。不同的x,都可能得到相同的y。后面學(xué)習(xí)哈希表時(shí),再來演示哈希值真正的作用是什么。
```java
package com.atguigu.object;
import java.util.Objects;
public class Student{
private String name;
private int score;
public Student(String name, int score) {
this.name = name;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
//重寫hashCode,最快捷的方式 Alt + Insert
@Override
public boolean equals(Object o) {
if (this == o) {//如果當(dāng)前對象的地址值與o對象的地址值相同,直接返回true
return true;
}
//(1)非空對象與null比較
//(2)getClass() != o.getClass()
// this.getClass() != o.getClass() 兩個(gè)對象的類型不同
if (o == null || getClass() != o.getClass()){
return false;
}
//向下轉(zhuǎn)型
//這里為什么沒有instanceof判斷呢?
//因?yàn)槿绻鹢對象的運(yùn)行時(shí)類型不是Student類型,上一個(gè)if就回去了
//this對象的類型肯定是Student類型。
Student student = (Student) o;
//為什么要向下轉(zhuǎn)型?
//如果不向下轉(zhuǎn)型,o的編譯時(shí)類型是Object,只能調(diào)用Object里面定義的成員。
//必須向下轉(zhuǎn)型,才能調(diào)用o對象的Student類聲明的成員
return score == student.score && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, score);
//根據(jù)某個(gè)規(guī)則,把對象的所有屬性值 合起來算出一個(gè)int值
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
```
#### 3、native關(guān)鍵字
native:本地的,原生的
native只能修飾方法,表示這個(gè)方法的方法體代碼不是用Java語言實(shí)現(xiàn)的,而是由C/C++語言編寫的。但是對于Java程序員來說,可以當(dāng)做Java的方法一樣去正常調(diào)用它,或者子類重寫它。

### 6.3.9 final關(guān)鍵字
final:最終的,不可更改的
final修飾類:表示這個(gè)類不能被繼承,沒有子類
final修飾方法:表示這個(gè)方法不能被子類重寫
final修飾某個(gè)變量(成員變量或局部變量),表示它的值就不能被修改,稱為常量。
- 其中static final的常量名建議使用大寫字母,其余的常量名通常和變量名的命名規(guī)范一樣。
> 如果某個(gè)成員變量用final修飾后,沒有set方法,并且必須初始化(可以顯式賦值、或在初始化塊賦值、實(shí)例變量還可以在構(gòu)造器中賦值)
```java
final class Eunuch{//太監(jiān)類
}
class Son extends Eunuch{//錯誤
}
```
```java
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//錯誤
System.out.println("son");
}
}
```
```java
package com.atguigu.keyword.finals;
public class TestFinal {
public static void main(String[] args){
final int min = 0;
final int max = 100;
MyDate m1 = new MyDate();
System.out.println(m1.getInfo());
MyDate m2 = new MyDate(2022,2,14);
System.out.println(m2.getInfo());
System.out.println(Math.PI)
}
}
class MyDate{
//沒有set方法,必須有顯示賦值的代碼
private final int year;
private final int month;
private final int day;
public MyDate(){
year = 1970;
month = 1;
day = 1;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public int getMonth() {
return month;
}
public int getDay() {
return day;
}
public String getInfo(){
return year + "年" + month + "月" + day + "日";
}
}
```
## 6.4 抽象類
### 6.4.1 由來
抽象:即不具體、或無法具體
例如:當(dāng)我們聲明一個(gè)幾何圖形類:圓、矩形、三角形類等,發(fā)現(xiàn)這些類都有共同特征:求面積、求周長、獲取圖形詳細(xì)信息。那么這些共同特征應(yīng)該抽取到一個(gè)公共父類中。但是這些方法在父類中又**無法給出具體的實(shí)現(xiàn)**,而是應(yīng)該交給子類各自具體實(shí)現(xiàn)。那么父類在聲明這些方法時(shí),**就只有方法簽名,沒有方法體**,我們把沒有方法體的方法稱為**抽象方法**。Java語法規(guī)定,包含抽象方法的類必須是**抽象類**。
### 6.4.2 語法格式
* **抽象方法**:被abstract修飾沒有方法體的方法。
* **抽象類**:被abstract修飾的類。
抽象類的語法格式
```java
【權(quán)限修飾符】 abstract class 類名{
}
【權(quán)限修飾符】 abstract class 類名 extends 父類{
}
```
抽象方法的語法格式
```java
【其他修飾符】 abstract 返回值類型 方法名(【形參列表】);
```
> 注意:抽象方法沒有方法體
代碼舉例:
```java
public abstract class Animal {
public abstract void eat();
}
```
```java
public class Cat extends Animal {
public void run (){
System.out.println("小貓吃魚和貓糧");
}
}
```
```java
public class CatTest {
public static void main(String[] args) {
// 創(chuàng)建子類對象
Cat c = new Cat();
// 調(diào)用eat方法
c.eat();
}
}
```
此時(shí)的方法重寫,是子類對父類抽象方法的完成實(shí)現(xiàn),我們將這種方法重寫的操作,也叫做**實(shí)現(xiàn)方法**。
### 6.4.3 注意事項(xiàng)
關(guān)于抽象類的使用,以下為語法上要注意的細(xì)節(jié),雖然條目較多,但若理解了抽象的本質(zhì),無需死記硬背。
1. 抽象類**不能創(chuàng)建對象**,如果創(chuàng)建,編譯無法通過而報(bào)錯。只能創(chuàng)建其非抽象子類的對象。
> 理解:假設(shè)創(chuàng)建了抽象類的對象,調(diào)用抽象的方法,而抽象方法沒有具體的方法體,沒有意義。
2. 抽象類中,也有構(gòu)造方法,是供子類創(chuàng)建對象時(shí),初始化父類成員變量使用的。
> 理解:子類的構(gòu)造方法中,有默認(rèn)的super()或手動的super(實(shí)參列表),需要訪問父類構(gòu)造方法。
3. 抽象類中,不一定包含抽象方法,但是有抽象方法的類必定是抽象類。
> 理解:未包含抽象方法的抽象類,目的就是不想讓調(diào)用者創(chuàng)建該類對象,通常用于某些特殊的類結(jié)構(gòu)設(shè)計(jì)。
4. 抽象類的子類,必須重寫抽象父類中**所有的**抽象方法,否則,編譯無法通過而報(bào)錯。除非該子類也是抽象類。
> 理解:假設(shè)不重寫所有抽象方法,則類中可能包含抽象方法。那么創(chuàng)建對象后,調(diào)用抽象的方法,沒有意義。
## 6.5 接口
### 6.5.1 為什么要使用接口?
多態(tài)的使用前提必須是“繼承”。而類繼承有如下問題:
(1)類繼承有單繼承限制
(2)類繼承表示的是事物之間is-a的關(guān)系,但是is-a的關(guān)系要求太嚴(yán)格了。
為了解決這兩個(gè)問題,引入了接口,接口支持:
(1)多實(shí)現(xiàn)
(2)實(shí)現(xiàn)類和接口是is-like-a關(guān)。只要A類想要B接口聲明的行為功能,就可以讓A類實(shí)現(xiàn)B接口,不用考慮邏輯關(guān)系。
```java
Bird is a Animal. 鳥是一種動物。
Plane is not a Animal. 飛機(jī)不是一種動物。
Plane is a Vehicle. 飛機(jī)是一種交通工具。
Bird is like a Flyable。 鳥具有飛的能力。或 鳥會飛。
Plane is like a Flyable。 飛機(jī)具有飛的功能?;蝻w機(jī)會飛。
is-a解決的是:是不是的問題
is-like-a解決的是:要不要的問題
```
生活中的USB接口等思想,也是接口的思想。
<img src="images/image-20220616183122869.png" alt="image-20220616183122869" style="zoom:50%;" />
### 6.5.2 定義和使用格式
接口的定義,它與定義類方式相似,但是使用 `interface` 關(guān)鍵字。它也會被編譯成.class文件,但一定要明確它并不是類,而是另外一種引用數(shù)據(jù)類型。
> 引用數(shù)據(jù)類型:數(shù)組,類,枚舉,接口,注解。
1、接口的聲明格式
```java
【修飾符】 interface 接口名{
//接口的成員列表:
// 公共的靜態(tài)常量
// 公共的抽象方法
// 公共的默認(rèn)方法(JDK1.8以上)
// 公共的靜態(tài)方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
```
2、接口的成員說明
接口定義的是多個(gè)類共同的公共行為規(guī)范,這些行為規(guī)范是與外部交流的通道,這就意味著接口里通常是定義一組公共方法。
在JDK8之前,接口中只允許出現(xiàn):
(1)公共的靜態(tài)的常量:其中public static final可以省略
(2)公共的抽象的方法:其中public abstract可以省略
> 理解:接口是從多個(gè)相似類中抽象出來的規(guī)范,不需要提供具體實(shí)現(xiàn)
在JDK1.8時(shí),接口中允許聲明默認(rèn)方法和靜態(tài)方法:
(3)公共的默認(rèn)的方法:其中public 可以省略,建議保留,但是default不能省略
(4)公共的靜態(tài)的方法:其中public 可以省略,建議保留,但是static不能省略
在JDK1.9時(shí),接口又增加了:
(5)私有方法:其中private不可以省略
(6)除此之外,接口中不能有其他成員,沒有構(gòu)造器,沒有初始化塊,因?yàn)榻涌谥袥]有成員變量需要動態(tài)初始化。
3、示例代碼
```java
package com.atguigu.interfacetype;
public interface Flyable {
long MAX_SPEED = 299792458;//光速:299792458米/秒, 省略public static final
void fly();//省略public abstract
static void start(){//省略public
System.out.println("start");
}
default void end(){//省略public
System.out.println("end");
}
private void show(){
System.out.println("cool!");
}
}
```
4、其他說明
為什么接口中只能聲明公共的靜態(tài)的常量?(面試題)
因?yàn)榻涌谑菢?biāo)準(zhǔn)規(guī)范,那么在規(guī)范中需要聲明一些底線邊界值,當(dāng)實(shí)現(xiàn)者在實(shí)現(xiàn)這些規(guī)范時(shí),不能去隨意修改和觸碰這些底線,否則就有“危險(xiǎn)”。
例如:USB1.0規(guī)范中規(guī)定最大傳輸速率是1.5Mbps,最大輸出電流是5V/500mA
? USB3.0規(guī)范中規(guī)定最大傳輸速率是5Gbps(500MB/s),最大輸出電流是5V/900mA
例如:尚硅谷學(xué)生行為規(guī)范中規(guī)定學(xué)員,早上8:25之前進(jìn)班,晚上21:30之后離開等等。
為什么JDK1.8之后要允許接口定義靜態(tài)方法和默認(rèn)方法呢?因?yàn)樗`反了接口作為一個(gè)抽象標(biāo)準(zhǔn)定義的概念。
**靜態(tài)方法**:因?yàn)橹暗臉?biāo)準(zhǔn)類庫設(shè)計(jì)中,有很多Collection/Colletions或者Path/Paths這樣成對的接口和類,后面的類中都是靜態(tài)方法,而這些靜態(tài)方法都是為前面的接口服務(wù)的,那么這樣設(shè)計(jì)一對API,不如把靜態(tài)方法直接定義到接口中使用和維護(hù)更方便。
**默認(rèn)方法**:(1)我們要在已有的老版接口中提供新方法時(shí),如果添加抽象方法,就會涉及到原來使用這些接口的類就會有問題,那么為了保持與舊版本代碼的兼容性,只能允許在接口中定義默認(rèn)方法實(shí)現(xiàn)。比如:Java8中對Collection、List、Comparator等接口提供了豐富的默認(rèn)方法。(2)當(dāng)我們接口的某個(gè)抽象方法,在很多實(shí)現(xiàn)類中的實(shí)現(xiàn)代碼是一樣的,此時(shí)將這個(gè)抽象方法設(shè)計(jì)為默認(rèn)方法更為合適,那么實(shí)現(xiàn)類就可以選擇重寫,也可以選擇不重寫。
為什么JDK1.9之后要允許接口定義私有的方法呢?
因?yàn)镴DK1.8增加的靜態(tài)方法和默認(rèn)方法都是有方法體的,多個(gè)方法之間就可能存在“重復(fù)的冗余”代碼,這些代碼可以抽取出來“內(nèi)部共用”。
### 6.5.3 接口的使用
#### 1、使用接口的靜態(tài)成員
接口不能直接創(chuàng)建對象,但是可以通過接口名直接調(diào)用接口的靜態(tài)方法和靜態(tài)常量。
```java
package com.atguigu.interfacetype;
public class TestFlyable {
public static void main(String[] args) {
System.out.println(Flyable.MAX_SPEED);//調(diào)用接口的靜態(tài)常量
Flyable.start();//調(diào)用接口的靜態(tài)方法
}
}
```
#### 2、類實(shí)現(xiàn)接口(implements)
接口**不能創(chuàng)建對象**,但是可以被類實(shí)現(xiàn)(`implements` ,類似于被繼承)。
類與接口的關(guān)系為實(shí)現(xiàn)關(guān)系,即**類實(shí)現(xiàn)接口**,該類可以稱為接口的實(shí)現(xiàn)類,也可以稱為接口的子類。實(shí)現(xiàn)的動作類似繼承,格式相仿,只是關(guān)鍵字不同,實(shí)現(xiàn)使用 ` implements`關(guān)鍵字。
```java
【修飾符】 class 實(shí)現(xiàn)類 implements 接口{
// 重寫接口中抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類是抽象類,那么可以不重寫
// 重寫接口中默認(rèn)方法【可選】
}
【修飾符】 class 實(shí)現(xiàn)類 extends 父類 implements 接口{
// 重寫接口中抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類是抽象類,那么可以不重寫
// 重寫接口中默認(rèn)方法【可選】
}
```
注意:
1. 如果接口的實(shí)現(xiàn)類是非抽象類,那么必須==重寫接口中所有抽象方法==。
2. 默認(rèn)方法可以選擇保留,也可以重寫。
> 重寫時(shí),default單詞就不要再寫了,它只用于在接口中表示默認(rèn)方法,到類中就沒有默認(rèn)方法的概念了
3. **接口中的靜態(tài)方法不能被繼承也不能被重寫**
示例代碼:
```java
package com.atguigu.interfacetype;
public class Animal {
public void eat(){
System.out.println("吃東西");
}
}
```
```java
package com.atguigu.interfacetype;
public class Bird extends Animal implements Flyable{
//重寫父接口的抽象方法,【必選】
@Override
public void fly() {
System.out.println("我要飛的更高~~~");
}
//重寫父接口的默認(rèn)方法,【可選】
@Override
public void end() {
System.out.println("輕輕落在樹枝上~~~");
}
}
```
#### 3、使用接口的非靜態(tài)方法
* 對于接口的靜態(tài)方法,直接使用“接口名.”進(jìn)行調(diào)用即可
* 也只能使用“接口名."進(jìn)行調(diào)用,不能通過實(shí)現(xiàn)類的對象進(jìn)行調(diào)用
* 對于接口的抽象方法、默認(rèn)方法,只能通過實(shí)現(xiàn)類對象才可以調(diào)用
* 接口不能直接創(chuàng)建對象,只能創(chuàng)建實(shí)現(xiàn)類的對象
```java
package com.atguigu.interfacetype;
public class TestBirdFlyable {
public static void main(String[] args) {
Bird bird = new Bird();
Flyable.start();//調(diào)用接口的靜態(tài)方法,只能通過 接口名.
//必須依賴于實(shí)現(xiàn)類的對象
bird.fly();//調(diào)用接口的抽象方法
bird.end();//調(diào)用接口的默認(rèn)方法
bird.eat();//調(diào)用父類繼承的方法
}
}
```
#### 4、接口的多實(shí)現(xiàn)(implements)
之前學(xué)過,在繼承體系中,一個(gè)類只能繼承一個(gè)父類。而對于接口而言,一個(gè)類是可以實(shí)現(xiàn)多個(gè)接口的,這叫做接口的**多實(shí)現(xiàn)**。并且,一個(gè)類能繼承一個(gè)父類,同時(shí)實(shí)現(xiàn)多個(gè)接口。
實(shí)現(xiàn)格式:
```java
【修飾符】 class 實(shí)現(xiàn)類 implements 接口1,接口2,接口3。。。{
// 重寫接口中所有抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類是抽象類,那么可以不重寫
// 重寫接口中默認(rèn)方法【可選】
}
【修飾符】 class 實(shí)現(xiàn)類 extends 父類 implements 接口1,接口2,接口3。。。{
// 重寫接口中所有抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類是抽象類,那么可以不重寫
// 重寫接口中默認(rèn)方法【可選】
}
```
> 接口中,有多個(gè)抽象方法時(shí),實(shí)現(xiàn)類必須重寫所有抽象方法。**如果抽象方法有重名的,只需要重寫一次**。
定義多個(gè)接口:
```java
package com.atguigu.interfacetype;
public interface Jumpable {
void jump();
}
```
```java
package com.atguigu.interfacetype;
public interface Runnable {
void jump();
void run();
}
```
定義實(shí)現(xiàn)類:
```java
package com.atguigu.interfacetype;
public class Bird implements Flyable,Jumpable,Runnable{
//重寫父接口的抽象方法,【必選】
@Override
public void fly() {
System.out.println("我要飛的更高~~~");
}
//重寫父接口的默認(rèn)方法,【可選】
@Override
public void end() {
System.out.println("輕輕落在樹枝上~~~");
}
@Override
public void jump() {
System.out.println("我會跳跳~~~");
}
@Override
public void run() {
System.out.println("我會跑~~");
}
}
```
測試類
```java
package com.atguigu.interfacetype;
public class TestBird {
public static void main(String[] args) {
Bird bird = new Bird();
bird.fly();//調(diào)用Flyable接口的抽象方法
bird.jump();//調(diào)用Jumpable接口的抽象方法
bird.run();//調(diào)用Runnable接口的抽象方法
}
}
```
#### 5、接口的多繼承 (extends)
一個(gè)接口能繼承另一個(gè)或者多個(gè)接口,接口的繼承也使用 `extends` 關(guān)鍵字,子接口繼承父接口的方法。
定義父接口:
```java
package com.atguigu.interfacetype;
public interface A {
void a();
}
```
```java
package com.atguigu.interfacetype;
public interface B {
void b();
}
```
定義子接口:
```java
package com.atguigu.interfacetype;
public interface C extends A,B {
void c();
}
```
定義子接口的實(shí)現(xiàn)類:
```java
package com.atguigu.interfacetype;
public class D implements C {
@Override
public void c() {
System.out.println("重寫C接口的抽象方法c");
}
@Override
public void a() {
System.out.println("重寫C接口的抽象方法a");
}
@Override
public void b() {
System.out.println("重寫C接口的抽象方法b");
}
}
```
>所有父接口的抽象方法都有重寫。
>
>方法簽名相同的抽象方法只需要實(shí)現(xiàn)一次。
### 6.5.4 接口的特點(diǎn)總結(jié)
- 接口本身不能創(chuàng)建對象,只能創(chuàng)建接口的實(shí)現(xiàn)類對象,接口類型的變量可以與實(shí)現(xiàn)類對象構(gòu)成多態(tài)引用。
- 聲明接口用interface,接口的成員聲明有限制:(1)公共的靜態(tài)常量(2)公共的抽象方法(3)公共的默認(rèn)方法(4)公共的靜態(tài)方法(5)私有方法(JDK1.9以上)
- 類可以實(shí)現(xiàn)接口,關(guān)鍵字是implements,而且支持多實(shí)現(xiàn)。如果實(shí)現(xiàn)類不是抽象類,就必須實(shí)現(xiàn)接口中所有的抽象方法。如果實(shí)現(xiàn)類既要繼承父類又要實(shí)現(xiàn)父接口,那么繼承(extends)在前,實(shí)現(xiàn)(implements)在后。
- 接口可以繼承接口,關(guān)鍵字是extends,而且支持多繼承。
- 接口的默認(rèn)方法可以選擇重寫或不重寫。如果有沖突問題,另行處理。子類重寫父接口的默認(rèn)方法,要去掉default,子接口重寫父接口的默認(rèn)方法,不要去掉default。
- 接口的靜態(tài)方法不能被繼承,也不能被重寫。接口的靜態(tài)方法只能通過“接口名.靜態(tài)方法名”進(jìn)行調(diào)用。
### 6.5.5 成員沖突問題
#### 1、默認(rèn)方法兩種沖突情況
(1)親爹優(yōu)先原則
當(dāng)一個(gè)類,既繼承一個(gè)父類,又實(shí)現(xiàn)若干個(gè)接口時(shí),父類中的成員方法與接口中的抽象方法重名,子類就近選擇執(zhí)行父類的成員方法。代碼如下:
定義接口:
```java
package com.atguigu.interfacetype;
public interface Friend {
default void date(){//約會
System.out.println("吃喝玩樂");
}
}
```
定義父類:
```java
package com.atguigu.interfacetype;
public class Father {
public void date(){//約會
System.out.println("爸爸約吃飯");
}
}
```
定義子類:
```java
package com.atguigu.interfacetype;
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重寫默認(rèn)保留父類的
//(2)調(diào)用父類被重寫的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重寫
System.out.println("學(xué)Java");
}
}
```
定義測試類:
```java
package com.atguigu.interfacetype;
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
```
(2)左右為難
- 當(dāng)一個(gè)類同時(shí)實(shí)現(xiàn)了多個(gè)父接口,而多個(gè)父接口中包含方法簽名相同的默認(rèn)方法時(shí),怎么辦呢?

無論你多難抉擇,最終都是要做出選擇的。
聲明接口:
```java
package com.atguigu.interfacetype;
public interface BoyFriend {
default void date(){//約會
System.out.println("神秘約會");
}
}
```
選擇保留其中一個(gè),通過“接口名.super.方法名"的方法選擇保留哪個(gè)接口的默認(rèn)方法。
```java
package com.atguigu.interfacetype;
public class Girl implements Friend,BoyFriend{
@Override
public void date() {
//(1)保留其中一個(gè)父接口的
// Friend.super.date();
// BoyFriend.super.date();
//(2)完全重寫
System.out.println("學(xué)Java");
}
}
```
測試類
```java
package com.atguigu.interfacetype;
public class TestGirl {
public static void main(String[] args) {
Girl girl = new Girl();
girl.date();
}
}
```
- 當(dāng)一個(gè)子接口同時(shí)繼承了多個(gè)接口,而多個(gè)父接口中包含方法簽名相同的默認(rèn)方法時(shí),怎么辦呢?
另一個(gè)父接口:
```java
package com.atguigu.interfacetype;
public interface Usb2 {
//靜態(tài)常量
long MAX_SPEED = 60*1024*1024;//60MB/s
//抽象方法
void in();
void out();
//默認(rèn)方法
public default void start(){
System.out.println("開始");
}
public default void stop(){
System.out.println("結(jié)束");
}
//靜態(tài)方法
public static void show(){
System.out.println("USB 2.0可以高速地進(jìn)行讀寫操作");
}
}
```
子接口:
```java
package com.atguigu.interfacetype;
public interface Usb extends Usb2,Usb3 {
@Override
default void start() {
System.out.println("Usb.start");
}
@Override
default void stop() {
System.out.println("Usb.stop");
}
}
```
> 小貼士:
>
> 子接口重寫默認(rèn)方法時(shí),default關(guān)鍵字可以保留。
>
> 子類重寫默認(rèn)方法時(shí),default關(guān)鍵字不可以保留。
#### 2、變量沖突問題
- 當(dāng)子類繼承父類又實(shí)現(xiàn)父接口,而父類中存在與父接口常量同名的成員變量,并且該成員變量名在子類中仍然可見。
- 當(dāng)子類同時(shí)繼承多個(gè)父接口,而多個(gè)父接口存在相同同名常量。
此時(shí)在子類中想要引用父類或父接口的同名的常量或成員變量時(shí),就會有沖突問題。
父類和父接口:
```java
package com.atguigu.interfacetype;
public class SuperClass {
int x = 1;
}
```
```java
package com.atguigu.interfacetype;
public interface SuperInterface {
int x = 2;
int y = 2;
}
```
```java
package com.atguigu.interfacetype;
public interface MotherInterface {
int x = 3;
}
```
子類:
```java
package com.atguigu.interfacetype;
public class SubClass extends SuperClass implements SuperInterface,MotherInterface {
public void method(){
// System.out.println("x = " + x);//模糊不清
System.out.println("super.x = " + super.x);
System.out.println("SuperInterface.x = " + SuperInterface.x);
System.out.println("MotherInterface.x = " + MotherInterface.x);
System.out.println("y = " + y);//沒有重名問題,可以直接訪問
}
}
```