java面向?qū)ο蠡A(chǔ)(中)
## 本章學(xué)習(xí)目標(biāo)
- 理解封裝的意義
- 知道public、protected、缺省、private幾種權(quán)限修飾符的區(qū)別
- 知道標(biāo)準(zhǔn)Javabean的要求
- 掌握對(duì)象數(shù)組的聲明、初始化、遍歷
- 掌握增強(qiáng)for循環(huán)的使用
- 理解繼承的意義
- 掌握用extends實(shí)現(xiàn)子類(lèi)繼承父類(lèi)
- 知道Java子類(lèi)繼承父類(lèi)的要求
- 掌握方法重寫(xiě)的概念和要求
- 掌握toString、hashCode和equals方法的重寫(xiě)
- 掌握關(guān)鍵字final的使用
- 了解native關(guān)鍵字
- 掌握用abstract聲明抽象類(lèi)和抽象方法
- 掌握用interface聲明接口
- 掌握用implements實(shí)現(xiàn)接口
- 掌握接口與抽象類(lèi)的區(qū)別
## 6.1 封裝
### 6.1.1 封裝概述
為什么需要封裝?
* 我要用洗衣機(jī),只需要按一下開(kāi)關(guān)和洗滌模式就可以了。有必要了解洗衣機(jī)內(nèi)部的結(jié)構(gòu)嗎?有必要碰電動(dòng)機(jī)嗎?
* 我們使用的電腦,內(nèi)部有CPU、硬盤(pán)、鍵盤(pán)、鼠標(biāo)等等,每一個(gè)部件通過(guò)某種連接方式一起工作,但是各個(gè)部件之間又是獨(dú)立的。
* 現(xiàn)實(shí)生活中,每一個(gè)個(gè)體與個(gè)體之間是有邊界的,每一個(gè)團(tuán)體與團(tuán)體之間是有邊界的,而同一個(gè)個(gè)體、團(tuán)體內(nèi)部的信息是互通的,只是對(duì)外有所隱瞞。
面向?qū)ο缶幊陶Z(yǔ)言是對(duì)客觀世界的模擬,客觀世界里每一個(gè)事物的內(nèi)部信息都是隱藏在對(duì)象內(nèi)部的,外界無(wú)法直接操作和修改,只能通過(guò)指定的方式進(jìn)行訪問(wèn)和修改。封裝可以被認(rèn)為是一個(gè)保護(hù)屏障,防止該類(lèi)的代碼和數(shù)據(jù)被其他類(lèi)隨意訪問(wèn)。適當(dāng)?shù)姆庋b可以讓代碼更容易理解與維護(hù),也加強(qiáng)了代碼的安全性。
隨著我們系統(tǒng)越來(lái)越復(fù)雜,類(lèi)會(huì)越來(lái)越多,那么類(lèi)之間的訪問(wèn)邊界必須把握好,面向?qū)ο蟮拈_(kāi)發(fā)原則要遵循“高內(nèi)聚、低耦合”,而“高內(nèi)聚,低耦合”的體現(xiàn)之一:
* 高內(nèi)聚:類(lèi)的內(nèi)部數(shù)據(jù)操作細(xì)節(jié)自己完成,不允許外部干涉;
* 低耦合:僅對(duì)外暴露少量的方法用于使用
隱藏對(duì)象內(nèi)部的復(fù)雜性,只對(duì)外公開(kāi)簡(jiǎn)單和可控的訪問(wèn)方式,從而提高系統(tǒng)的可擴(kuò)展性、可維護(hù)性。通俗的講,把該隱藏的隱藏起來(lái),該暴露的暴露出來(lái)。這就是封裝性的設(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)封裝就是指控制類(lèi)或成員的可見(jiàn)性范圍,這就需要依賴(lài)訪問(wèn)控制修飾符(也稱(chēng)為權(quán)限修飾符)來(lái)控制。
| 修飾符 | 本類(lèi) | 本包 | 其他包子類(lèi) | 其他包非子類(lèi) |
| --------- | ---- | ---- | ---------- | ------------ |
| private | √ | × | × | × |
| 缺省 | √ | √ | × | × |
| protected | √ | √ | √ | × |
| public | √ | √ | √ | √ |
外部類(lèi):public和缺省
成員變量、成員方法、構(gòu)造器、成員內(nèi)部類(lèi):public,protected,缺省,private
### 6.1.3 成員變量/屬性私有化問(wèn)題
**<span style="color:red">成員變量(field)私有化</span>之后,提供標(biāo)準(zhǔn)的<span style="color:red">get/set</span>方法,我們把這種成員變量也稱(chēng)為<span style="color:red">屬性(property)</span>。**
> 或者可以說(shuō)只要能通過(guò)get/set操作的就是事物的屬性,哪怕它沒(méi)有對(duì)應(yīng)的成員變量。
1、成員變量封裝的目的
* 隱藏類(lèi)的實(shí)現(xiàn)細(xì)節(jié)
* 讓使用者只能通過(guò)事先預(yù)定的方法來(lái)訪問(wèn)數(shù)據(jù),從而可以在該方法里面加入控制邏輯,限制對(duì)成員變量的不合理訪問(wèn)。還可以進(jìn)行數(shù)據(jù)檢查,從而有利于保證對(duì)象信息的完整性。
* 便于修改,提高代碼的可維護(hù)性。主要說(shuō)的是隱藏的部分,在內(nèi)部修改了,如果其對(duì)外可以的訪問(wèn)方式不變的話,外部根本感覺(jué)不到它的修改。例如:Java8->Java9,String從char[]轉(zhuǎn)為byte[]內(nèi)部實(shí)現(xiàn),而對(duì)外的方法不變,我們使用者根本感覺(jué)不到它內(nèi)部的修改。
2、實(shí)現(xiàn)步驟
(1)使用 `private` 修飾成員變量
```java
private 數(shù)據(jù)類(lèi)型 變量名 ;
```
代碼如下:
```java
public class Person {
private String name;
private int age;
private boolean marry;
}
```
(2)提供 `getXxx`方法 / `setXxx` 方法,可以訪問(wèn)成員變量,代碼如下:
靜態(tài)變量的get/set方法也靜態(tài)的,當(dāng)局部變量與靜態(tài)變量重名時(shí),使用“類(lèi)名.靜態(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自動(dòng)生成get/set方法模板
- 大部分鍵盤(pán)模式按Alt + Insert鍵。
- 部分鍵盤(pán)模式需要按Alt + Insert + Fn鍵。
- Mac電腦快捷鍵需要單獨(dú)設(shè)置
![image-20211229171605642](images/image-20211229171605642.png)
![image-20211229171757032](images/image-20211229171757032.png)
(3)測(cè)試
```java
package com.atguigu.encapsulation;
public class TestPerson {
public static void main(String[] args) {
Person p = new Person();
//實(shí)例變量私有化,跨類(lèi)是無(wú)法直接使用的
/* 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語(yǔ)言編寫(xiě)類(lèi)的一種標(biāo)準(zhǔn)規(guī)范。符合`JavaBean` 的類(lèi),要求:
(1)類(lèi)必須是公共的和具體的(非抽象的,關(guān)于抽象請(qǐng)看6.5小節(jié))
(2)成員變量私有化,并提供用來(lái)操作成員變量的`set` 和`get` 方法
(3)必須有無(wú)參構(gòu)造
(4)提供有參構(gòu)造(可選)
(5)建議重寫(xiě)toString方法、equals和hashCode方法等(關(guān)于重寫(xiě)請(qǐng)看6.3小節(jié))
```java
public class ClassName{
//成員變量
//構(gòu)造方法
//無(wú)參構(gòu)造方法【必須】
//有參構(gòu)造方法【建議】
//getXxx()
//setXxx()
//其他成員方法
}
```
編寫(xiě)符合`JavaBean` 規(guī)范的類(lèi),以學(xué)生類(lèi)為例,標(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;
}
}
```
測(cè)試類(lèi),代碼如下:
```java
public class TestStudent {
public static void main(String[] args) {
// 無(wú)參構(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 對(duì)象數(shù)組
數(shù)組是用來(lái)存儲(chǔ)一組數(shù)據(jù)的容器,一組基本數(shù)據(jù)類(lèi)型的數(shù)據(jù)可以用數(shù)組裝,那么一組對(duì)象也可以使用數(shù)組來(lái)裝。
即數(shù)組的元素可以是基本數(shù)據(jù)類(lèi)型,也可以是引用數(shù)據(jù)類(lèi)型。當(dāng)元素是引用數(shù)據(jù)類(lèi)型是,我們稱(chēng)為對(duì)象數(shù)組。
> 注意:對(duì)象數(shù)組,首先要?jiǎng)?chuàng)建數(shù)組對(duì)象本身,即確定數(shù)組的長(zhǎng)度,然后再創(chuàng)建每一個(gè)元素對(duì)象,如果不創(chuàng)建,數(shù)組的元素的默認(rèn)值就是null,所以很容易出現(xiàn)空指針異常NullPointerException。
### 6.2.1 對(duì)象數(shù)組的聲明和使用
案例:
(1)定義矩形類(lèi),包含長(zhǎng)、寬屬性,area()求面積方法,perimeter()求周長(zhǎng)方法,String getInfo()返回圓對(duì)象的詳細(xì)信息的方法
(2)在測(cè)試類(lèi)中創(chuàng)建長(zhǎng)度為5的Rectangle[]數(shù)組,用來(lái)裝3個(gè)矩形對(duì)象,并給3個(gè)矩形對(duì)象的長(zhǎng)分別賦值為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(){//周長(zhǎng)
return 2 * (length + width);
}
String getInfo(){
return "長(zhǎng):" + length +
",寬:" + width +
",面積:" + area() + //直接調(diào)用本類(lèi)的另一個(gè)實(shí)例方法
",周長(zhǎng):" + perimeter();
}
}
```
```java
package com.atguigu.test08.array;
public class ObjectArrayTest {
public static void main(String[] args) {
//聲明并創(chuàng)建一個(gè)長(zhǎng)度為3的矩形對(duì)象數(shù)組
Rectangle[] array = new Rectangle[3];
//創(chuàng)建3個(gè)矩形對(duì)象,并為對(duì)象的實(shí)例變量賦值,
//3個(gè)矩形對(duì)象的長(zhǎng)分別是10,20,30
//3個(gè)矩形對(duì)象的寬分別是5,15,25
//調(diào)用矩形對(duì)象的getInfo()返回對(duì)象信息后輸出
for (int i = 0; i < array.length; i++) {
//創(chuàng)建矩形對(duì)象
array[i] = new Rectangle();
//為矩形對(duì)象的成員變量賦值
array[i].length = (i+1) * 10;
array[i].width = (2*i+1) * 5;
//獲取并輸出對(duì)象對(duì)象的信息
System.out.println(array[i].getInfo());
}
}
}
```
### 6.2.2 對(duì)象數(shù)組的內(nèi)存圖分析
對(duì)象數(shù)組中數(shù)組元素存儲(chǔ)的是元素對(duì)象的首地址。
![image-20211228153827819](images/image-20211228153827819-17028727226681.png)
### 6.2.3 增強(qiáng)for循環(huán)
增強(qiáng)for循環(huán)是一種語(yǔ)法糖,即在遍歷數(shù)組中,表面上是一種新語(yǔ)法,但是編譯后仍然是我們學(xué)過(guò)的普通for循環(huán)。
```java
for(元素類(lèi)型 元素名 : 數(shù)組名){
}
```
注意:增強(qiáng)for循環(huán)只能用于查看元素,或修改元素屬性值,但不能替換元素。增強(qiáng)for循環(huán)遍歷數(shù)組是沒(méi)有下標(biāo)的。
```java
package com.atguigu.test08.array;
public class TestForeach {
public static void main(String[] args) {
//聲明并創(chuàng)建一個(gè)長(zhǎng)度為3的矩形對(duì)象數(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) | 更簡(jiǎn)潔 |
## 6.3 繼承
### 6.3.1 繼承的概述
1、生活中的繼承
* 財(cái)產(chǎn):富二代
* 樣貌:如圖所示:
<img src="images/繼承2.jpg" style="zoom:50%;" />
繼承有延續(xù)(下一代延續(xù)上一代的基因、財(cái)富)、擴(kuò)展(下一代和上一代又有所不同)的意思。
社會(huì)的進(jìn)步就是源于知識(shí)、財(cái)富、經(jīng)驗(yàn)得以繼承,又可以不斷的翻新。
2、Java中的繼承
如圖所示:
<img src="images/貓狗繼承1.jpg" style="zoom:50%;" />
多個(gè)類(lèi)中存在相同屬性和行為時(shí),將這些內(nèi)容抽取到單獨(dú)一個(gè)類(lèi)中,那么多個(gè)類(lèi)中無(wú)需再定義這些屬性和行為,只需要和抽取出來(lái)的類(lèi)構(gòu)成某種關(guān)系。如圖所示:
<img src="images/貓狗繼承2.jpg" style="zoom: 50%;" />
其中,多個(gè)類(lèi)可以稱(chēng)為**子類(lèi)**,也叫**派生類(lèi)**;多個(gè)類(lèi)抽取出來(lái)的這個(gè)類(lèi)稱(chēng)為**父類(lèi)**、**超類(lèi)(superclass)**或者**基類(lèi)**。
繼承描述的是事物之間的所屬關(guān)系,這種關(guān)系是:`is-a` 的關(guān)系。例如,圖中貓屬于動(dòng)物,狗也屬于動(dòng)物。可見(jiàn),父類(lèi)更通用或更一般,子類(lèi)更具體。我們通過(guò)繼承,可以使多種事物之間形成一種關(guān)系體系。
3、繼承的好處
* 提高**代碼的復(fù)用性**。
* 提高**代碼的擴(kuò)展性**。
* 表示類(lèi)與類(lèi)之間的is-a關(guān)系
### 6.3.2 繼承的語(yǔ)法格式
通過(guò) `extends` 關(guān)鍵字,可以聲明一個(gè)子類(lèi)繼承另外一個(gè)父類(lèi),定義格式如下:
```java
【修飾符】 class 父類(lèi) {
...
}
【修飾符】 class 子類(lèi) extends 父類(lèi) {
...
}
```
1、父類(lèi)
```java
package com.atguigu.inherited.grammar;
/*
* 定義動(dòng)物類(lèi)Animal,做為父類(lèi)
*/
public class Animal {
// 定義name屬性
String name;
// 定義age屬性
int age;
// 定義動(dòng)物的吃東西方法
public void eat() {
System.out.println(age + "歲的" + name + "在吃東西");
}
}
```
2、子類(lèi)
```java
package com.atguigu.inherited.grammar;
/*
* 定義貓類(lèi)Cat 繼承 動(dòng)物類(lèi)Animal
*/
public class Cat extends Animal {
int count;//記錄每只貓抓的老鼠數(shù)量
// 定義一個(gè)貓抓老鼠的方法catchMouse
public void catchMouse() {
count++;
System.out.println("抓老鼠,已經(jīng)抓了" + count + "只老鼠");
}
}
```
3、測(cè)試類(lèi)
```java
package com.atguigu.inherited.grammar;
public class TestCat {
public static void main(String[] args) {
// 創(chuàng)建一個(gè)貓類(lèi)對(duì)象
Cat cat = new Cat();
// 為該貓類(lèi)對(duì)象的name屬性進(jìn)行賦值
cat.name = "Tom";
// 為該貓類(lèi)對(duì)象的age屬性進(jìn)行賦值
cat.age = 2;
// 調(diào)用該貓繼承來(lái)的eat()方法
cat.eat();
// 調(diào)用該貓的catchMouse()方法
cat.catchMouse();
cat.catchMouse();
cat.catchMouse();
//調(diào)用該貓的eat()方法
cat.eat();
}
}
```
### 6.3.3 IDEA中如何查看繼承關(guān)系
1、子類(lèi)和父類(lèi)是一種相對(duì)的概念
例如:B類(lèi)對(duì)于A來(lái)說(shuō)是子類(lèi),但是對(duì)于C類(lèi)來(lái)說(shuō)是父類(lèi)
2、查看繼承關(guān)系快捷鍵
例如:選擇A類(lèi)名,按Ctrl + H就會(huì)顯示A類(lèi)的繼承樹(shù)。
![image-20211230090701383](images/image-20211230090701383.png):A類(lèi)的父類(lèi)和子類(lèi)
![image-20211230090719430](images/image-20211230090719430.png):A類(lèi)的父類(lèi)
![image-20211230090732532](images/image-20211230090732532.png):A類(lèi)的所有子類(lèi)
例如:在類(lèi)繼承目錄樹(shù)中選中某個(gè)類(lèi),比如C類(lèi),按Ctrl+ Alt+U就會(huì)用圖形化方式顯示C類(lèi)的繼承祖宗
<img src="images/image-20211229180113255.png" alt="image-20211229180113255" style="zoom: 67%;" />
### 6.3.4 繼承的特點(diǎn)
1、每一個(gè)類(lèi)有一個(gè)默認(rèn)的父類(lèi)java.lang.Object類(lèi),它也是所有類(lèi)的根父類(lèi)
<img src="images/image-20220616110817804.png" alt="image-20220616110817804" style="zoom: 67%;" />
2、子類(lèi)會(huì)繼承父類(lèi)所有的實(shí)例變量和實(shí)例方法
從類(lèi)的定義來(lái)看,類(lèi)是一類(lèi)具有相同特性的事物的抽象描述。父類(lèi)是所有子類(lèi)共同特征的抽象描述。而實(shí)例變量和實(shí)例方法就是事物的特征,那么父類(lèi)中聲明的實(shí)例變量和實(shí)例方法代表子類(lèi)事物也有這個(gè)特征。
- 當(dāng)子類(lèi)對(duì)象被創(chuàng)建時(shí),在堆中給對(duì)象申請(qǐng)內(nèi)存時(shí),就要看子類(lèi)和父類(lèi)都聲明了什么實(shí)例變量,這些實(shí)例變量都要分配內(nèi)存。
- 當(dāng)子類(lèi)對(duì)象調(diào)用方法時(shí),編譯器會(huì)先在子類(lèi)模板中看該類(lèi)是否有這個(gè)方法,如果沒(méi)找到,會(huì)看它的父類(lèi)甚至父類(lèi)的父類(lèi)是否聲明了這個(gè)方法,遵循從下往上找的順序,找到了就停止,一直到根父類(lèi)都沒(méi)有找到,就會(huì)報(bào)編譯錯(cuò)誤。
==所以繼承意味著子類(lèi)的對(duì)象除了看子類(lèi)的類(lèi)模板還要看父類(lèi)的類(lèi)模板。==
![image-20211230090255997](images/image-20211230090255997.png)
3、Java只支持單繼承,不支持多重繼承
```java
public class A{}
class B extends A{}
//一個(gè)類(lèi)只能有一個(gè)父類(lèi),不可以有多個(gè)直接父類(lèi)。
class C extends B{} //ok
class C extends A,B... //error
```
4、Java支持多層繼承(繼承體系)
```java
class A{}
class B extends A{}
class C extends B{}
```
> 頂層父類(lèi)是Object類(lèi)。所有的類(lèi)默認(rèn)繼承Object,作為父類(lèi)。
5、一個(gè)父類(lèi)可以同時(shí)擁有多個(gè)子類(lèi)
```java
class A{}
class B extends A{}
class D extends A{}
class E extends A{}
```
### 6.3.5 繼承時(shí)權(quán)限修飾符限制問(wèn)題
權(quán)限修飾符:public,protected,缺省,private
| 修飾符 | 本類(lèi) | 本包(包含子類(lèi)和非子類(lèi)) | 其他包子類(lèi) | 其他包非子類(lèi) |
| --------- | ---- | ------------------------- | ----------------------------------------- | ------------ |
| private | √ | × | × | × |
| 缺省 | √ | √(本包子類(lèi)非子類(lèi)都可見(jiàn)) | × | × |
| protected | √ | √(本包子類(lèi)非子類(lèi)都可見(jiàn)) | √(其他包僅限于子類(lèi)中可見(jiàn),直接使用方式) | × |
| public | √ | √ | √ | √ |
外部類(lèi):public和缺省
成員變量、成員方法等:public,protected,缺省,private
1、外部類(lèi)要跨包使用必須是public,否則僅限于本包使用
(1)外部類(lèi)的權(quán)限修飾符如果缺省,本包使用沒(méi)問(wèn)題
![image-20211230093627763](images/image-20211230093627763.png)
(2)外部類(lèi)的權(quán)限修飾符如果缺省,跨包使用有問(wèn)題
![image-20211230094236974](images/image-20211230094236974.png)
2、父類(lèi)成員變量私有化(private)
子類(lèi)雖會(huì)繼承父類(lèi)私有(private)的成員變量,但子類(lèi)不能對(duì)繼承的私有成員變量直接進(jìn)行訪問(wèn),可通過(guò)繼承的get/set方法進(jìn)行訪問(wèn)。如圖所示:
![](images/繼承私有成員1.jpg)
父類(lèi)代碼:
```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;
}
}
```
子類(lèi)代碼:
```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;
//在子類(lèi)中不能直接使用父類(lèi)私有的name和age
return "姓名:" + getName() + ",年齡:" + getAge();
}
}
```
測(cè)試類(lèi)代碼:
```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é)生對(duì)象信息:
![image-20211230101938382](images/image-20211230101938382.png)
3、成員的權(quán)限修飾符問(wèn)題
(1)本包下使用:成員的權(quán)限修飾符可以是public、protected、缺省
![image-20211230095320646](images/image-20211230095320646.png)
(2)跨包使用時(shí),如果類(lèi)的權(quán)限修飾符缺省,成員權(quán)限修飾符>類(lèi)的權(quán)限修飾符也沒(méi)有意義
![image-20211230100219840](images/image-20211230100219840.png)
(3)跨包下使用:要求嚴(yán)格
![image-20211230095817784](images/image-20211230095817784.png)
注意:跨包時(shí)父類(lèi)中protected修飾的成員,僅限于在子類(lèi)中訪問(wèn),且是子類(lèi)對(duì)象自己訪問(wèn)。
### 6.3.6 繼承時(shí)構(gòu)造器問(wèn)題
子類(lèi)繼承父類(lèi)時(shí),不會(huì)繼承父類(lèi)的構(gòu)造器。必須通過(guò)super()或super(實(shí)參列表)的方式調(diào)用父類(lèi)的構(gòu)造器。
- super();:子類(lèi)構(gòu)造器中一定會(huì)調(diào)用父類(lèi)的構(gòu)造器,默認(rèn)調(diào)用父類(lèi)的無(wú)參構(gòu)造,super();可以省略。
- super(實(shí)參列表);:如果父類(lèi)沒(méi)有無(wú)參構(gòu)造或者有無(wú)參構(gòu)造但是子類(lèi)就是想要調(diào)用父類(lèi)的有參構(gòu)造,則必須使用super(實(shí)參列表);的語(yǔ)句。
- super()和super(實(shí)參列表)都只能出現(xiàn)在子類(lèi)構(gòu)造器的首行。
```java
package com.atguigu.constructor;
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
System.out.println("父類(lèi)Employee無(wú)參構(gòu)造");
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
System.out.println("父類(lèi)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)用父類(lèi)的有參構(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() +",獎(jiǎng)金比例:" + bonusRate;
}
}
```
![image-20211231112340813](images/image-20211231112340813.png)
```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類(lèi)和B類(lèi)都是默認(rèn)有一個(gè)無(wú)參構(gòu)造,B類(lèi)的默認(rèn)無(wú)參構(gòu)造中還會(huì)默認(rèn)調(diào)用A類(lèi)的默認(rèn)無(wú)參構(gòu)造
//但是因?yàn)槎际悄J(rèn)的,沒(méi)有打印語(yǔ)句,看不出來(lái)
}
}
```
形式二:
```java
class A{
A(){
System.out.println("A類(lèi)無(wú)參構(gòu)造器");
}
}
class B extends A{
}
class Test2{
public static void main(String[] args){
B b = new B();
//A類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)默認(rèn)有一個(gè)無(wú)參構(gòu)造,
//B類(lèi)的默認(rèn)無(wú)參構(gòu)造中會(huì)默認(rèn)調(diào)用A類(lèi)的無(wú)參構(gòu)造
//可以看到會(huì)輸出“A類(lèi)無(wú)參構(gòu)造器"
}
}
```
形式三:
```java
class A{
A(){
System.out.println("A類(lèi)無(wú)參構(gòu)造器");
}
}
class B extends A{
B(){
System.out.println("B類(lèi)無(wú)參構(gòu)造器");
}
}
class Test3{
public static void main(String[] args){
B b = new B();
//A類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)的無(wú)參構(gòu)造中雖然沒(méi)有寫(xiě)super(),但是仍然會(huì)默認(rèn)調(diào)用A類(lèi)的無(wú)參構(gòu)造
//可以看到會(huì)輸出“A類(lèi)無(wú)參構(gòu)造器"和"B類(lèi)無(wú)參構(gòu)造器")
}
}
```
形式四:
```java
class A{
A(){
System.out.println("A類(lèi)無(wú)參構(gòu)造器");
}
}
class B extends A{
B(){
super();
System.out.println("B類(lèi)無(wú)參構(gòu)造器");
}
}
class Test4{
public static void main(String[] args){
B b = new B();
//A類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)的無(wú)參構(gòu)造中明確寫(xiě)了super(),表示調(diào)用A類(lèi)的無(wú)參構(gòu)造
//可以看到會(huì)輸出“A類(lèi)無(wú)參構(gòu)造器"和"B類(lèi)無(wú)參構(gòu)造器")
}
}
```
形式五:
```java
class A{
A(int a){
System.out.println("A類(lèi)有參構(gòu)造器");
}
}
class B extends A{
B(){
System.out.println("B類(lèi)無(wú)參構(gòu)造器");
}
}
class Test5{
public static void main(String[] args){
B b = new B();
//A類(lèi)顯示聲明一個(gè)有參構(gòu)造,沒(méi)有寫(xiě)無(wú)參構(gòu)造,那么A類(lèi)就沒(méi)有無(wú)參構(gòu)造了
//B類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)的無(wú)參構(gòu)造沒(méi)有寫(xiě)super(...),表示默認(rèn)調(diào)用A類(lèi)的無(wú)參構(gòu)造
//編譯報(bào)錯(cuò),因?yàn)锳類(lèi)沒(méi)有無(wú)參構(gòu)造
}
}
```
![image-20200227141228450](images/image-20200227141228450.png)
![image-20200227141051954](images/image-20200227141051954.png)
形式六:
```java
class A{
A(int a){
System.out.println("A類(lèi)有參構(gòu)造器");
}
}
class B extends A{
B(){
super();
System.out.println("B類(lèi)無(wú)參構(gòu)造器");
}
}
class Test6{
public static void main(String[] args){
B b = new B();
//A類(lèi)顯示聲明一個(gè)有參構(gòu)造,沒(méi)有寫(xiě)無(wú)參構(gòu)造,那么A類(lèi)就沒(méi)有無(wú)參構(gòu)造了
//B類(lèi)顯示聲明一個(gè)無(wú)參構(gòu)造,
//B類(lèi)的無(wú)參構(gòu)造明確寫(xiě)super(),表示調(diào)用A類(lèi)的無(wú)參構(gòu)造
//編譯報(bào)錯(cuò),因?yàn)锳類(lèi)沒(méi)有無(wú)參構(gòu)造
}
}
```
![image-20200303183542807](images/image-20200303183542807.png)
形式七:
```java
class A{
A(int a){
System.out.println("A類(lèi)有參構(gòu)造器");
}
}
class B extends A{
B(int a){
super(a);
System.out.println("B類(lèi)有參構(gòu)造器");
}
}
class Test7{
public static void main(String[] args){
B b = new B(10);
//A類(lèi)顯示聲明一個(gè)有參構(gòu)造,沒(méi)有寫(xiě)無(wú)參構(gòu)造,那么A類(lèi)就沒(méi)有無(wú)參構(gòu)造了
//B類(lèi)顯示聲明一個(gè)有參構(gòu)造,
//B類(lèi)的有參構(gòu)造明確寫(xiě)super(a),表示調(diào)用A類(lèi)的有參構(gòu)造
//會(huì)打印“A類(lèi)有參構(gòu)造器"和"B類(lèi)有參構(gòu)造器"
}
}
```
形式八:
```java
class A{
A(){
System.out.println("A類(lèi)無(wú)參構(gòu)造器");
}
A(int a){
System.out.println("A類(lèi)有參構(gòu)造器");
}
}
class B extends A{
B(){
super();//可以省略,調(diào)用父類(lèi)的無(wú)參構(gòu)造
System.out.println("B類(lèi)無(wú)參構(gòu)造器");
}
B(int a){
super(a);//調(diào)用父類(lèi)有參構(gòu)造
System.out.println("B類(lèi)有參構(gòu)造器");
}
}
class Test8{
public static void main(String[] args){
B b1 = new B();
B b2 = new B(10);
}
}
```
### 6.3.7 方法重寫(xiě)(Override)
我們說(shuō)父類(lèi)的所有方法子類(lèi)都會(huì)繼承,但是當(dāng)某個(gè)方法被繼承到子類(lèi)之后,子類(lèi)覺(jué)得父類(lèi)原來(lái)的實(shí)現(xiàn)不適合于子類(lèi),該怎么辦呢?我們可以進(jìn)行方法重寫(xiě) (Override)
1、方法重寫(xiě)
```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;
}
//方法的重寫(xiě)
public String getInfo(){
// return "姓名:" + name + ",年齡:" + age;
//在子類(lèi)中不能直接使用父類(lèi)私有的name和age
return "姓名:" + getName() + ",年齡:" + getAge() + ",成績(jī):" + 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、在子類(lèi)中如何調(diào)用父類(lèi)被重寫(xiě)的方法
在子類(lèi)中可以通過(guò)super關(guān)鍵字調(diào)用父類(lèi)被重寫(xiě)的方法
```java
super.被重寫(xiě)方法(【實(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;
}
//方法的重寫(xiě)
public String getInfo(){
// return "姓名:" + name + ",年齡:" + age;
//在子類(lèi)中不能直接使用父類(lèi)私有的name和age
// return "姓名:" + getName() + ",年齡:" + getAge() + ",成績(jī):" + score;
return super.getInfo() + ",成績(jī):" + score;
}
}
```
3、IDEA重寫(xiě)方法快捷鍵:Ctrl + O
![image-20211230104547719](images/image-20211230104547719.png)
```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() +",成績(jī):" + score;
}
}
```
> @Override:寫(xiě)在方法上面,用來(lái)檢測(cè)是不是滿(mǎn)足重寫(xiě)方法的要求。這個(gè)注解就算不寫(xiě),只要滿(mǎn)足要求,也是正確的方法覆蓋重寫(xiě)。建議保留,這樣編譯器可以幫助我們檢查格式,另外也可以讓閱讀源代碼的程序員清晰的知道這是一個(gè)重寫(xiě)的方法。
4、重寫(xiě)方法的要求
(1)必須保證父子類(lèi)之間重寫(xiě)方法的名稱(chēng)相同。
(2)必須保證父子類(lèi)之間重寫(xiě)方法的參數(shù)列表也完全相同。
(3)子類(lèi)方法的返回值類(lèi)型必須【小于等于】父類(lèi)方法的返回值類(lèi)型(小于其實(shí)就是是它的子類(lèi),例如:Student < Person)。
> 注意:如果返回值類(lèi)型是基本數(shù)據(jù)類(lèi)型和void,那么必須是相同
(4)子類(lèi)方法的權(quán)限必須【大于等于】父類(lèi)方法的權(quán)限修飾符。
> 注意:public > protected > 缺省 > private
>
> 父類(lèi)私有方法不能重寫(xiě)
>
> 跨包的父類(lèi)缺省的方法也不能重寫(xiě)
5、方法的重載和方法的重寫(xiě)
方法的重載:方法名相同,形參列表不同。不看返回值類(lèi)型。
方法的重寫(xiě):見(jiàn)上面。
(1)同一個(gè)類(lèi)中
```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)父子類(lèi)中
```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){//重寫(xiě)
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根父類(lèi)
#### 1、Object根父類(lèi)
**API(Application Programming Interface)**,應(yīng)用程序編程接口。Java API是一本程序員的`字典` ,是JDK中提供給我們使用的類(lèi)的說(shuō)明文檔。所以我們可以通過(guò)查詢(xún)API的方式,來(lái)學(xué)習(xí)Java提供的類(lèi),并得知如何使用它們。在API文檔中是無(wú)法得知這些類(lèi)具體是如何實(shí)現(xiàn)的,如果要查看具體實(shí)現(xiàn)代碼,那么我們需要查看**src源碼**。
類(lèi) `java.lang.Object`是類(lèi)層次結(jié)構(gòu)的根類(lèi),即所有類(lèi)的父類(lèi)。每個(gè)類(lèi)都使用 `Object` 作為超類(lèi)。所有對(duì)象(包括數(shù)組)都實(shí)現(xiàn)這個(gè)類(lèi)的方法。
* 如果一個(gè)類(lèi)沒(méi)有特別指定父類(lèi),那么默認(rèn)則繼承自O(shè)bject類(lèi)。例如:
```java
public class MyClass /*extends Object*/ {
// ...
}
```
- 所有對(duì)象(包括數(shù)組)都實(shí)現(xiàn)這個(gè)類(lèi)的方法。而且很多方法子類(lèi)都會(huì)重寫(xiě),通過(guò)子類(lèi)對(duì)象調(diào)用方法后執(zhí)行的是重寫(xiě)后的方法。
#### 2、Object類(lèi)的方法
Object類(lèi)有11個(gè)方法,先介紹如下3個(gè):
(1)public String toString():建議子類(lèi)重寫(xiě)。沒(méi)有重寫(xiě)的話,默認(rèn)返回的是 對(duì)象的運(yùn)行時(shí)類(lèi)型 @ 對(duì)象的哈希值的十六進(jìn)制值。所有對(duì)象的toString方法,在打印對(duì)象時(shí),或者對(duì)象與字符串進(jìn)行拼接時(shí),都會(huì)自動(dòng)調(diào)用。
(2)public boolean equals(Object obj):用于比較兩個(gè)對(duì)象是否“相等”。
- 絕對(duì)相等:同一個(gè)對(duì)象,地址值完全相同。沒(méi)有重寫(xiě),繼承Object里面的equals,默認(rèn)就是比較地址值,等價(jià)于==比較。
- 相對(duì)相等(邏輯相等):比較兩個(gè)對(duì)象的內(nèi)容。這個(gè)時(shí)候需要重寫(xiě)。核心類(lèi)庫(kù)中大部分類(lèi)都重寫(xiě)了equals方法,例如:String類(lèi)。
(3)public int hashCode():返回該對(duì)象的哈希碼值。支持此方法是為了提高哈希表的性能。 暫時(shí)可以認(rèn)為它相當(dāng)于這個(gè)對(duì)象的身份證號(hào)碼。如果設(shè)計(jì)的好,那么對(duì)象的哈希值相同的概率就會(huì)降低。y=f(x)。不同的x,都可能得到相同的y。后面學(xué)習(xí)哈希表時(shí),再來(lái)演示哈希值真正的作用是什么。
```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;
}
//重寫(xiě)hashCode,最快捷的方式 Alt + Insert
@Override
public boolean equals(Object o) {
if (this == o) {//如果當(dāng)前對(duì)象的地址值與o對(duì)象的地址值相同,直接返回true
return true;
}
//(1)非空對(duì)象與null比較
//(2)getClass() != o.getClass()
// this.getClass() != o.getClass() 兩個(gè)對(duì)象的類(lèi)型不同
if (o == null || getClass() != o.getClass()){
return false;
}
//向下轉(zhuǎn)型
//這里為什么沒(méi)有instanceof判斷呢?
//因?yàn)槿绻鹢對(duì)象的運(yùn)行時(shí)類(lèi)型不是Student類(lèi)型,上一個(gè)if就回去了
//this對(duì)象的類(lèi)型肯定是Student類(lèi)型。
Student student = (Student) o;
//為什么要向下轉(zhuǎn)型?
//如果不向下轉(zhuǎn)型,o的編譯時(shí)類(lèi)型是Object,只能調(diào)用Object里面定義的成員。
//必須向下轉(zhuǎn)型,才能調(diào)用o對(duì)象的Student類(lèi)聲明的成員
return score == student.score && Objects.equals(name, student.name);
}
@Override
public int hashCode() {
return Objects.hash(name, score);
//根據(jù)某個(gè)規(guī)則,把對(duì)象的所有屬性值 合起來(lái)算出一個(gè)int值
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", score=" + score +
'}';
}
}
```
#### 3、native關(guān)鍵字
native:本地的,原生的
native只能修飾方法,表示這個(gè)方法的方法體代碼不是用Java語(yǔ)言實(shí)現(xiàn)的,而是由C/C++語(yǔ)言編寫(xiě)的。但是對(duì)于Java程序員來(lái)說(shuō),可以當(dāng)做Java的方法一樣去正常調(diào)用它,或者子類(lèi)重寫(xiě)它。
![image-20240731152128788](images/image-20240731152128788.png)
### 6.3.9 final關(guān)鍵字
final:最終的,不可更改的
final修飾類(lèi):表示這個(gè)類(lèi)不能被繼承,沒(méi)有子類(lèi)
final修飾方法:表示這個(gè)方法不能被子類(lèi)重寫(xiě)
final修飾某個(gè)變量(成員變量或局部變量),表示它的值就不能被修改,稱(chēng)為常量。
- 其中static final的常量名建議使用大寫(xiě)字母,其余的常量名通常和變量名的命名規(guī)范一樣。
> 如果某個(gè)成員變量用final修飾后,沒(méi)有set方法,并且必須初始化(可以顯式賦值、或在初始化塊賦值、實(shí)例變量還可以在構(gòu)造器中賦值)
```java
final class Eunuch{//太監(jiān)類(lèi)
}
class Son extends Eunuch{//錯(cuò)誤
}
```
```java
class Father{
public final void method(){
System.out.println("father");
}
}
class Son extends Father{
public void method(){//錯(cuò)誤
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{
//沒(méi)有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 抽象類(lèi)
### 6.4.1 由來(lái)
抽象:即不具體、或無(wú)法具體
例如:當(dāng)我們聲明一個(gè)幾何圖形類(lèi):圓、矩形、三角形類(lèi)等,發(fā)現(xiàn)這些類(lèi)都有共同特征:求面積、求周長(zhǎng)、獲取圖形詳細(xì)信息。那么這些共同特征應(yīng)該抽取到一個(gè)公共父類(lèi)中。但是這些方法在父類(lèi)中又**無(wú)法給出具體的實(shí)現(xiàn)**,而是應(yīng)該交給子類(lèi)各自具體實(shí)現(xiàn)。那么父類(lèi)在聲明這些方法時(shí),**就只有方法簽名,沒(méi)有方法體**,我們把沒(méi)有方法體的方法稱(chēng)為**抽象方法**。Java語(yǔ)法規(guī)定,包含抽象方法的類(lèi)必須是**抽象類(lèi)**。
### 6.4.2 語(yǔ)法格式
* **抽象方法**:被abstract修飾沒(méi)有方法體的方法。
* **抽象類(lèi)**:被abstract修飾的類(lèi)。
抽象類(lèi)的語(yǔ)法格式
```java
【權(quán)限修飾符】 abstract class 類(lèi)名{
}
【權(quán)限修飾符】 abstract class 類(lèi)名 extends 父類(lèi){
}
```
抽象方法的語(yǔ)法格式
```java
【其他修飾符】 abstract 返回值類(lèi)型 方法名(【形參列表】);
```
> 注意:抽象方法沒(méi)有方法體
代碼舉例:
```java
public abstract class Animal {
public abstract void eat();
}
```
```java
public class Cat extends Animal {
public void run (){
System.out.println("小貓吃魚(yú)和貓糧");
}
}
```
```java
public class CatTest {
public static void main(String[] args) {
// 創(chuàng)建子類(lèi)對(duì)象
Cat c = new Cat();
// 調(diào)用eat方法
c.eat();
}
}
```
此時(shí)的方法重寫(xiě),是子類(lèi)對(duì)父類(lèi)抽象方法的完成實(shí)現(xiàn),我們將這種方法重寫(xiě)的操作,也叫做**實(shí)現(xiàn)方法**。
### 6.4.3 注意事項(xiàng)
關(guān)于抽象類(lèi)的使用,以下為語(yǔ)法上要注意的細(xì)節(jié),雖然條目較多,但若理解了抽象的本質(zhì),無(wú)需死記硬背。
1. 抽象類(lèi)**不能創(chuàng)建對(duì)象**,如果創(chuàng)建,編譯無(wú)法通過(guò)而報(bào)錯(cuò)。只能創(chuàng)建其非抽象子類(lèi)的對(duì)象。
> 理解:假設(shè)創(chuàng)建了抽象類(lèi)的對(duì)象,調(diào)用抽象的方法,而抽象方法沒(méi)有具體的方法體,沒(méi)有意義。
2. 抽象類(lèi)中,也有構(gòu)造方法,是供子類(lèi)創(chuàng)建對(duì)象時(shí),初始化父類(lèi)成員變量使用的。
> 理解:子類(lèi)的構(gòu)造方法中,有默認(rèn)的super()或手動(dòng)的super(實(shí)參列表),需要訪問(wèn)父類(lèi)構(gòu)造方法。
3. 抽象類(lèi)中,不一定包含抽象方法,但是有抽象方法的類(lèi)必定是抽象類(lèi)。
> 理解:未包含抽象方法的抽象類(lèi),目的就是不想讓調(diào)用者創(chuàng)建該類(lèi)對(duì)象,通常用于某些特殊的類(lèi)結(jié)構(gòu)設(shè)計(jì)。
4. 抽象類(lèi)的子類(lèi),必須重寫(xiě)抽象父類(lèi)中**所有的**抽象方法,否則,編譯無(wú)法通過(guò)而報(bào)錯(cuò)。除非該子類(lèi)也是抽象類(lèi)。
> 理解:假設(shè)不重寫(xiě)所有抽象方法,則類(lèi)中可能包含抽象方法。那么創(chuàng)建對(duì)象后,調(diào)用抽象的方法,沒(méi)有意義。
## 6.5 接口
### 6.5.1 為什么要使用接口?
多態(tài)的使用前提必須是“繼承”。而類(lèi)繼承有如下問(wèn)題:
(1)類(lèi)繼承有單繼承限制
(2)類(lèi)繼承表示的是事物之間is-a的關(guān)系,但是is-a的關(guān)系要求太嚴(yán)格了。
為了解決這兩個(gè)問(wèn)題,引入了接口,接口支持:
(1)多實(shí)現(xiàn)
(2)實(shí)現(xiàn)類(lèi)和接口是is-like-a關(guān)。只要A類(lèi)想要B接口聲明的行為功能,就可以讓A類(lèi)實(shí)現(xiàn)B接口,不用考慮邏輯關(guān)系。
```java
Bird is a Animal. 鳥(niǎo)是一種動(dòng)物。
Plane is not a Animal. 飛機(jī)不是一種動(dòng)物。
Plane is a Vehicle. 飛機(jī)是一種交通工具。
Bird is like a Flyable。 鳥(niǎo)具有飛的能力?;?鳥(niǎo)會(huì)飛。
Plane is like a Flyable。 飛機(jī)具有飛的功能。或飛機(jī)會(huì)飛。
is-a解決的是:是不是的問(wèn)題
is-like-a解決的是:要不要的問(wèn)題
```
生活中的USB接口等思想,也是接口的思想。
<img src="images/image-20220616183122869.png" alt="image-20220616183122869" style="zoom:50%;" />
### 6.5.2 定義和使用格式
接口的定義,它與定義類(lèi)方式相似,但是使用 `interface` 關(guān)鍵字。它也會(huì)被編譯成.class文件,但一定要明確它并不是類(lèi),而是另外一種引用數(shù)據(jù)類(lèi)型。
> 引用數(shù)據(jù)類(lèi)型:數(shù)組,類(lèi),枚舉,接口,注解。
1、接口的聲明格式
```java
【修飾符】 interface 接口名{
//接口的成員列表:
// 公共的靜態(tài)常量
// 公共的抽象方法
// 公共的默認(rèn)方法(JDK1.8以上)
// 公共的靜態(tài)方法(JDK1.8以上)
// 私有方法(JDK1.9以上)
}
```
2、接口的成員說(shuō)明
接口定義的是多個(gè)類(lèi)共同的公共行為規(guī)范,這些行為規(guī)范是與外部交流的通道,這就意味著接口里通常是定義一組公共方法。
在JDK8之前,接口中只允許出現(xiàn):
(1)公共的靜態(tài)的常量:其中public static final可以省略
(2)公共的抽象的方法:其中public abstract可以省略
> 理解:接口是從多個(gè)相似類(lèi)中抽象出來(lái)的規(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)除此之外,接口中不能有其他成員,沒(méi)有構(gòu)造器,沒(méi)有初始化塊,因?yàn)榻涌谥袥](méi)有成員變量需要?jiǎng)討B(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、其他說(shuō)明
為什么接口中只能聲明公共的靜態(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之后離開(kāi)等等。
為什么JDK1.8之后要允許接口定義靜態(tài)方法和默認(rèn)方法呢?因?yàn)樗`反了接口作為一個(gè)抽象標(biāo)準(zhǔn)定義的概念。
**靜態(tài)方法**:因?yàn)橹暗臉?biāo)準(zhǔn)類(lèi)庫(kù)設(shè)計(jì)中,有很多Collection/Colletions或者Path/Paths這樣成對(duì)的接口和類(lèi),后面的類(lèi)中都是靜態(tài)方法,而這些靜態(tài)方法都是為前面的接口服務(wù)的,那么這樣設(shè)計(jì)一對(duì)API,不如把靜態(tài)方法直接定義到接口中使用和維護(hù)更方便。
**默認(rèn)方法**:(1)我們要在已有的老版接口中提供新方法時(shí),如果添加抽象方法,就會(huì)涉及到原來(lái)使用這些接口的類(lèi)就會(huì)有問(wèn)題,那么為了保持與舊版本代碼的兼容性,只能允許在接口中定義默認(rèn)方法實(shí)現(xiàn)。比如:Java8中對(duì)Collection、List、Comparator等接口提供了豐富的默認(rèn)方法。(2)當(dāng)我們接口的某個(gè)抽象方法,在很多實(shí)現(xiàn)類(lèi)中的實(shí)現(xiàn)代碼是一樣的,此時(shí)將這個(gè)抽象方法設(shè)計(jì)為默認(rèn)方法更為合適,那么實(shí)現(xiàn)類(lèi)就可以選擇重寫(xiě),也可以選擇不重寫(xiě)。
為什么JDK1.9之后要允許接口定義私有的方法呢?
因?yàn)镴DK1.8增加的靜態(tài)方法和默認(rèn)方法都是有方法體的,多個(gè)方法之間就可能存在“重復(fù)的冗余”代碼,這些代碼可以抽取出來(lái)“內(nèi)部共用”。
### 6.5.3 接口的使用
#### 1、使用接口的靜態(tài)成員
接口不能直接創(chuàng)建對(duì)象,但是可以通過(guò)接口名直接調(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、類(lèi)實(shí)現(xiàn)接口(implements)
接口**不能創(chuàng)建對(duì)象**,但是可以被類(lèi)實(shí)現(xiàn)(`implements` ,類(lèi)似于被繼承)。
類(lèi)與接口的關(guān)系為實(shí)現(xiàn)關(guān)系,即**類(lèi)實(shí)現(xiàn)接口**,該類(lèi)可以稱(chēng)為接口的實(shí)現(xiàn)類(lèi),也可以稱(chēng)為接口的子類(lèi)。實(shí)現(xiàn)的動(dòng)作類(lèi)似繼承,格式相仿,只是關(guān)鍵字不同,實(shí)現(xiàn)使用 ` implements`關(guān)鍵字。
```java
【修飾符】 class 實(shí)現(xiàn)類(lèi) implements 接口{
// 重寫(xiě)接口中抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類(lèi)是抽象類(lèi),那么可以不重寫(xiě)
// 重寫(xiě)接口中默認(rèn)方法【可選】
}
【修飾符】 class 實(shí)現(xiàn)類(lèi) extends 父類(lèi) implements 接口{
// 重寫(xiě)接口中抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類(lèi)是抽象類(lèi),那么可以不重寫(xiě)
// 重寫(xiě)接口中默認(rèn)方法【可選】
}
```
注意:
1. 如果接口的實(shí)現(xiàn)類(lèi)是非抽象類(lèi),那么必須==重寫(xiě)接口中所有抽象方法==。
2. 默認(rèn)方法可以選擇保留,也可以重寫(xiě)。
> 重寫(xiě)時(shí),default單詞就不要再寫(xiě)了,它只用于在接口中表示默認(rèn)方法,到類(lèi)中就沒(méi)有默認(rèn)方法的概念了
3. **接口中的靜態(tài)方法不能被繼承也不能被重寫(xiě)**
示例代碼:
```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{
//重寫(xiě)父接口的抽象方法,【必選】
@Override
public void fly() {
System.out.println("我要飛的更高~~~");
}
//重寫(xiě)父接口的默認(rèn)方法,【可選】
@Override
public void end() {
System.out.println("輕輕落在樹(shù)枝上~~~");
}
}
```
#### 3、使用接口的非靜態(tài)方法
* 對(duì)于接口的靜態(tài)方法,直接使用“接口名.”進(jìn)行調(diào)用即可
* 也只能使用“接口名."進(jìn)行調(diào)用,不能通過(guò)實(shí)現(xiàn)類(lèi)的對(duì)象進(jìn)行調(diào)用
* 對(duì)于接口的抽象方法、默認(rèn)方法,只能通過(guò)實(shí)現(xiàn)類(lèi)對(duì)象才可以調(diào)用
* 接口不能直接創(chuàng)建對(duì)象,只能創(chuàng)建實(shí)現(xiàn)類(lèi)的對(duì)象
```java
package com.atguigu.interfacetype;
public class TestBirdFlyable {
public static void main(String[] args) {
Bird bird = new Bird();
Flyable.start();//調(diào)用接口的靜態(tài)方法,只能通過(guò) 接口名.
//必須依賴(lài)于實(shí)現(xiàn)類(lèi)的對(duì)象
bird.fly();//調(diào)用接口的抽象方法
bird.end();//調(diào)用接口的默認(rèn)方法
bird.eat();//調(diào)用父類(lèi)繼承的方法
}
}
```
#### 4、接口的多實(shí)現(xiàn)(implements)
之前學(xué)過(guò),在繼承體系中,一個(gè)類(lèi)只能繼承一個(gè)父類(lèi)。而對(duì)于接口而言,一個(gè)類(lèi)是可以實(shí)現(xiàn)多個(gè)接口的,這叫做接口的**多實(shí)現(xiàn)**。并且,一個(gè)類(lèi)能繼承一個(gè)父類(lèi),同時(shí)實(shí)現(xiàn)多個(gè)接口。
實(shí)現(xiàn)格式:
```java
【修飾符】 class 實(shí)現(xiàn)類(lèi) implements 接口1,接口2,接口3。。。{
// 重寫(xiě)接口中所有抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類(lèi)是抽象類(lèi),那么可以不重寫(xiě)
// 重寫(xiě)接口中默認(rèn)方法【可選】
}
【修飾符】 class 實(shí)現(xiàn)類(lèi) extends 父類(lèi) implements 接口1,接口2,接口3。。。{
// 重寫(xiě)接口中所有抽象方法【必須】,當(dāng)然如果實(shí)現(xiàn)類(lèi)是抽象類(lèi),那么可以不重寫(xiě)
// 重寫(xiě)接口中默認(rèn)方法【可選】
}
```
> 接口中,有多個(gè)抽象方法時(shí),實(shí)現(xiàn)類(lèi)必須重寫(xiě)所有抽象方法。**如果抽象方法有重名的,只需要重寫(xiě)一次**。
定義多個(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)類(lèi):
```java
package com.atguigu.interfacetype;
public class Bird implements Flyable,Jumpable,Runnable{
//重寫(xiě)父接口的抽象方法,【必選】
@Override
public void fly() {
System.out.println("我要飛的更高~~~");
}
//重寫(xiě)父接口的默認(rèn)方法,【可選】
@Override
public void end() {
System.out.println("輕輕落在樹(shù)枝上~~~");
}
@Override
public void jump() {
System.out.println("我會(huì)跳跳~~~");
}
@Override
public void run() {
System.out.println("我會(huì)跑~~");
}
}
```
測(cè)試類(lèi)
```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)類(lèi):
```java
package com.atguigu.interfacetype;
public class D implements C {
@Override
public void c() {
System.out.println("重寫(xiě)C接口的抽象方法c");
}
@Override
public void a() {
System.out.println("重寫(xiě)C接口的抽象方法a");
}
@Override
public void b() {
System.out.println("重寫(xiě)C接口的抽象方法b");
}
}
```
>所有父接口的抽象方法都有重寫(xiě)。
>
>方法簽名相同的抽象方法只需要實(shí)現(xiàn)一次。
### 6.5.4 接口的特點(diǎn)總結(jié)
- 接口本身不能創(chuàng)建對(duì)象,只能創(chuàng)建接口的實(shí)現(xiàn)類(lèi)對(duì)象,接口類(lèi)型的變量可以與實(shí)現(xiàn)類(lèi)對(duì)象構(gòu)成多態(tài)引用。
- 聲明接口用interface,接口的成員聲明有限制:(1)公共的靜態(tài)常量(2)公共的抽象方法(3)公共的默認(rèn)方法(4)公共的靜態(tài)方法(5)私有方法(JDK1.9以上)
- 類(lèi)可以實(shí)現(xiàn)接口,關(guān)鍵字是implements,而且支持多實(shí)現(xiàn)。如果實(shí)現(xiàn)類(lèi)不是抽象類(lèi),就必須實(shí)現(xiàn)接口中所有的抽象方法。如果實(shí)現(xiàn)類(lèi)既要繼承父類(lèi)又要實(shí)現(xiàn)父接口,那么繼承(extends)在前,實(shí)現(xiàn)(implements)在后。
- 接口可以繼承接口,關(guān)鍵字是extends,而且支持多繼承。
- 接口的默認(rèn)方法可以選擇重寫(xiě)或不重寫(xiě)。如果有沖突問(wèn)題,另行處理。子類(lèi)重寫(xiě)父接口的默認(rèn)方法,要去掉default,子接口重寫(xiě)父接口的默認(rèn)方法,不要去掉default。
- 接口的靜態(tài)方法不能被繼承,也不能被重寫(xiě)。接口的靜態(tài)方法只能通過(guò)“接口名.靜態(tài)方法名”進(jìn)行調(diào)用。
### 6.5.5 成員沖突問(wèn)題
#### 1、默認(rèn)方法兩種沖突情況
(1)親爹優(yōu)先原則
當(dāng)一個(gè)類(lèi),既繼承一個(gè)父類(lèi),又實(shí)現(xiàn)若干個(gè)接口時(shí),父類(lèi)中的成員方法與接口中的抽象方法重名,子類(lèi)就近選擇執(zhí)行父類(lèi)的成員方法。代碼如下:
定義接口:
```java
package com.atguigu.interfacetype;
public interface Friend {
default void date(){//約會(huì)
System.out.println("吃喝玩樂(lè)");
}
}
```
定義父類(lèi):
```java
package com.atguigu.interfacetype;
public class Father {
public void date(){//約會(huì)
System.out.println("爸爸約吃飯");
}
}
```
定義子類(lèi):
```java
package com.atguigu.interfacetype;
public class Son extends Father implements Friend {
@Override
public void date() {
//(1)不重寫(xiě)默認(rèn)保留父類(lèi)的
//(2)調(diào)用父類(lèi)被重寫(xiě)的
// super.date();
//(3)保留父接口的
// Friend.super.date();
//(4)完全重寫(xiě)
System.out.println("學(xué)Java");
}
}
```
定義測(cè)試類(lèi):
```java
package com.atguigu.interfacetype;
public class TestSon {
public static void main(String[] args) {
Son s = new Son();
s.date();
}
}
```
(2)左右為難
- 當(dāng)一個(gè)類(lèi)同時(shí)實(shí)現(xiàn)了多個(gè)父接口,而多個(gè)父接口中包含方法簽名相同的默認(rèn)方法時(shí),怎么辦呢?
![](images/選擇困難.jpg)
無(wú)論你多難抉擇,最終都是要做出選擇的。
聲明接口:
```java
package com.atguigu.interfacetype;
public interface BoyFriend {
default void date(){//約會(huì)
System.out.println("神秘約會(huì)");
}
}
```
選擇保留其中一個(gè),通過(guò)“接口名.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)完全重寫(xiě)
System.out.println("學(xué)Java");
}
}
```
測(cè)試類(lèi)
```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("開(kāi)始");
}
public default void stop(){
System.out.println("結(jié)束");
}
//靜態(tài)方法
public static void show(){
System.out.println("USB 2.0可以高速地進(jìn)行讀寫(xiě)操作");
}
}
```
子接口:
```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");
}
}
```
> 小貼士:
>
> 子接口重寫(xiě)默認(rèn)方法時(shí),default關(guān)鍵字可以保留。
>
> 子類(lèi)重寫(xiě)默認(rèn)方法時(shí),default關(guān)鍵字不可以保留。
#### 2、變量沖突問(wèn)題
- 當(dāng)子類(lèi)繼承父類(lèi)又實(shí)現(xiàn)父接口,而父類(lèi)中存在與父接口常量同名的成員變量,并且該成員變量名在子類(lèi)中仍然可見(jiàn)。
- 當(dāng)子類(lèi)同時(shí)繼承多個(gè)父接口,而多個(gè)父接口存在相同同名常量。
此時(shí)在子類(lèi)中想要引用父類(lèi)或父接口的同名的常量或成員變量時(shí),就會(huì)有沖突問(wèn)題。
父類(lèi)和父接口:
```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;
}
```
子類(lèi):
```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);//沒(méi)有重名問(wèn)題,可以直接訪問(wèn)
}
}
```