1. 程式人生 > 其它 >d13-介面和內部類

d13-介面和內部類

## 1.介面

### 1.1黑馬資訊管理系統集合改進 (應用)

+ 使用陣列容器的弊端

1. 容器長度是固定的,不能根據新增功能自動增長
2. 沒有提供用於贈刪改查的方法

+ 優化步驟

1. 建立新的StudentDao類,OtherStudentDao

2. 建立ArrayList集合容器物件

3. OtherStudentDao中的方法宣告,需要跟StudentDao保持一致

注意:如果不一致,StudentService中的程式碼就需要進行修改

4. 完善方法(新增、刪除、修改、檢視)

5. 替換StudentService中的Dao物件

+ 程式碼實現

OtherStudentDao類

```java
public class OtherStudentDao {
// 集合容器
private static ArrayList<Student> stus = new ArrayList<>();

static {
Student stu1 = new Student("heima001","張三","23","1999-11-11");
Student stu2 = new Student("heima002","李四","24","2000-11-11");

stus.add(stu1);
stus.add(stu2);
}

// 新增學生方法
public boolean addStudent(Student stu) {
stus.add(stu);
return true;
}

// 檢視學生方法
public Student[] findAllStudent() {

Student[] students = new Student[stus.size()];

for (int i = 0; i < students.length; i++) {
students[i] = stus.get(i);
}

return students;
}

public void deleteStudentById(String delId) {
// 1. 查詢id在容器中所在的索引位置
int index = getIndex(delId);
stus.remove(index);
}

public int getIndex(String id){
int index = -1;
for (int i = 0; i < stus.size(); i++) {
Student stu = stus.get(i);
if(stu != null && stu.getId().equals(id)){
index = i;
break;
}
}
return index;
}

public void updateStudent(String updateId, Student newStu) {
// 1. 查詢updateId, 在容器中的索引位置
int index = getIndex(updateId);
stus.set(index, newStu);
}
}
```

StudentService類

```java
public class StudentService {
// 建立StudentDao (庫管)
private OtherStudentDao studentDao = new OtherStudentDao();
// 其他方法沒有變化,此處省略...
}
```

### 1.2黑馬資訊管理系統抽取Dao (應用)

+ 優化步驟

1. 將方法向上抽取,抽取出一個父類 ( BaseStudentDao )
2. 方法的功能實現在父類中無法給出具體明確,定義為抽象方法
3. 讓兩個類分別繼承 BaseStudentDao ,重寫內部抽象方法

+ 程式碼實現

BaseStudentDao類

```java
public abstract class BaseStudentDao {
// 新增學生方法
public abstract boolean addStudent(Student stu);
// 檢視學生方法
public abstract Student[] findAllStudent();
// 刪除學生方法
public abstract void deleteStudentById(String delId);
// 根據id找索引方法
public abstract int getIndex(String id);
// 修改學生方法
public abstract void updateStudent(String updateId, Student newStu);
}
```

StudentDao類

```java
public class StudentDao extends BaseStudentDao {
// 其他內容不變,此處省略
}
```

OtherStudentDao類

```java
public class OtherStudentDao extends BaseStudentDao {
// 其他內容不變,此處省略
}
```

### 1.3介面的概述(理解)

+ 介面就是一種公共的規範標準,只要符合規範標準,大家都可以通用。
+ Java中介面存在的兩個意義
1. 用來定義規範
2. 用來做功能的拓展

### 1.4介面的特點(記憶)

- 介面用關鍵字interface修飾

```java
public interface 介面名 {}
```

- 類實現介面用implements表示

```java
public class 類名 implements 介面名 {}
```

- 介面不能例項化

​ 我們可以建立介面的實現類物件使用

- 介面的子類

​ 要麼重寫介面中的所有抽象方法

​ 要麼子類也是抽象類

### 1.5介面的成員特點(記憶)

- 成員特點

- 成員變數

​ 只能是常量
​ 預設修飾符:public static final

- 構造方法

​ 沒有,因為介面主要是擴充套件功能的,而沒有具體存在

- 成員方法

​ 只能是抽象方法

​ 預設修飾符:public abstract

​ 關於介面中的方法,JDK8和JDK9中有一些新特性,後面再講解

- 程式碼演示

- 介面

```java
public interface Inter {
public static final int NUM = 10;

public abstract void show();
}
```

- 實現類

```java
class InterImpl implements Inter{

public void method(){
// NUM = 20;
System.out.println(NUM);
}

public void show(){

}
}
```

- 測試類

```java
public class TestInterface {
/*
成員變數: 只能是常量 系統會預設加入三個關鍵字
public static final
構造方法: 沒有
成員方法: 只能是抽象方法, 系統會預設加入兩個關鍵字
public abstract
*/
public static void main(String[] args) {
System.out.println(Inter.NUM);
}

}
```

### 1.6類和介面的關係(記憶)

- 類與類的關係

​ 繼承關係,只能單繼承,但是可以多層繼承

- 類與介面的關係

​ 實現關係,可以單實現,也可以多實現,還可以在繼承一個類的同時實現多個介面

- 介面與介面的關係

​ 繼承關係,可以單繼承,也可以多繼承

### 1.7黑馬資訊管理系統使用介面改進 (應用)

+ 實現步驟

1. 將 BaseStudentDao 改進為一個介面
2. 讓 StudentDao 和 OtherStudentDao 去實現這個介面

+ 程式碼實現

BaseStudentDao介面

```java
public interface BaseStudentDao {
// 新增學生方法
public abstract boolean addStudent(Student stu);
// 檢視學生方法
public abstract Student[] findAllStudent();
// 刪除學生方法
public abstract void deleteStudentById(String delId);
// 根據id找索引方法
public abstract int getIndex(String id);
// 修改學生方法
public abstract void updateStudent(String updateId, Student newStu);
}
```

StudentDao類

```java
public class StudentDao implements BaseStudentDao {
// 其他內容不變,此處省略
}
```

OtherStudentDao類

```java
public class OtherStudentDao implements BaseStudentDao {
// 其他內容不變,此處省略
}
```

### 1.8黑馬資訊管理系統解耦合改進 (應用)

+ 實現步驟

1. 建立factory包,建立 StudentDaoFactory(工廠類)
2. 提供 static 修改的 getStudentDao 方法,該方法用於建立StudentDao物件並返回

+ 程式碼實現

StudentDaoFactory類

```java
public class StudentDaoFactory {
public static OtherStudentDao getStudentDao(){
return new OtherStudentDao();
}
}
```

StudentService類

```java
public class StudentService {
// 建立StudentDao (庫管)
// private OtherStudentDao studentDao = new OtherStudentDao();

// 通過學生庫管工廠類, 獲取庫管物件
private OtherStudentDao studentDao = StudentDaoFactory.getStudentDao();
}
```

## 2.介面組成更新

### 2.1介面組成更新概述【理解】

- 常量

public static final

- 抽象方法

public abstract

- 預設方法(Java 8)

- 靜態方法(Java 8)

- 私有方法(Java 9)

### 2.2介面中預設方法【應用】

- 格式

public default 返回值型別 方法名(引數列表) { }

- 作用

解決介面升級的問題

- 範例

```java
public default void show3() {
}
```

- 注意事項

- 預設方法不是抽象方法,所以不強制被重寫。但是可以被重寫,重寫的時候去掉default關鍵字
- public可以省略,default不能省略
- 如果實現了多個介面,多個介面中存在相同的方法宣告,子類就必須對該方法進行重寫

### 2.3介面中靜態方法【應用】

- 格式

public static 返回值型別 方法名(引數列表) { }

- 範例

```java
public static void show() {
}
```

- 注意事項

- 靜態方法只能通過介面名呼叫,不能通過實現類名或者物件名呼叫
- public可以省略,static不能省略

### 2.4介面中私有方法【應用】

- 私有方法產生原因

Java 9中新增了帶方法體的私有方法,這其實在Java 8中就埋下了伏筆:Java 8允許在介面中定義帶方法體的預設方法和靜態方法。這樣可能就會引發一個問題:當兩個預設方法或者靜態方法中包含一段相同的程式碼實現時,程式必然考慮將這段實現程式碼抽取成一個共性方法,而這個共性方法是不需要讓別人使用的,因此用私有給隱藏起來,這就是Java 9增加私有方法的必然性

- 定義格式

- 格式1

private 返回值型別 方法名(引數列表) { }

- 範例1

```java
private void show() {
}
```

- 格式2

private static 返回值型別 方法名(引數列表) { }

- 範例2

```java
private static void method() {
}
```

- 注意事項

- 預設方法可以呼叫私有的靜態方法和非靜態方法
- 靜態方法只能呼叫私有的靜態方法

## 3.多型

### 3.1多型的概述(記憶)

- 什麼是多型

​ 同一個物件,在不同時刻表現出來的不同形態

- 多型的前提

- 要有繼承或實現關係
- 要有方法的重寫
- 要有父類引用指向子類物件

- 程式碼演示

```java
class Animal {
public void eat(){
System.out.println("動物吃飯");
}
}

class Cat extends Animal {
@Override
public void eat() {
System.out.println("貓吃魚");
}
}

public class Test1Polymorphic {
/*
多型的前提:

1. 要有(繼承 \ 實現)關係
2. 要有方法重寫
3. 要有父類引用, 指向子類物件
*/
public static void main(String[] args) {
// 當前事物, 是一隻貓
Cat c = new Cat();
// 當前事物, 是一隻動物
Animal a = new Cat();
a.eat();

}
}
```

### 3.2多型中的成員訪問特點(記憶)

- 成員訪問特點

- 成員變數

​ 編譯看父類,執行看父類

- 成員方法

​ 編譯看父類,執行看子類

- 程式碼演示

```java
class Fu {
int num = 10;

public void method(){
System.out.println("Fu.. method");
}
}

class Zi extends Fu {
int num = 20;

public void method(){
System.out.println("Zi.. method");
}
}

public class Test2Polymorpic {
/*
多型的成員訪問特點:

成員變數: 編譯看左邊 (父類), 執行看左邊 (父類)

成員方法: 編譯看左邊 (父類), 執行看右邊 (子類)
*/
public static void main(String[] args) {
Fu f = new Zi();
System.out.println(f.num);
f.method();
}
}
```

### 3.3多型的好處和弊端(記憶)

- 好處

​ 提高程式的擴充套件性。定義方法時候,使用父型別作為引數,在使用的時候,使用具體的子型別參與操作

- 弊端

​ 不能使用子類的特有成員

### 3.4多型中的轉型(應用)

- 向上轉型

​ 父類引用指向子類物件就是向上轉型

- 向下轉型

​ 格式:子型別 物件名 = (子型別)父類引用;

- 程式碼演示

```java
class Fu {
public void show(){
System.out.println("Fu..show...");
}
}

class Zi extends Fu {
@Override
public void show() {
System.out.println("Zi..show...");
}

public void method(){
System.out.println("我是子類特有的方法, method");
}
}

public class Test3Polymorpic {
public static void main(String[] args) {
// 1. 向上轉型 : 父類引用指向子類物件
Fu f = new Zi();
f.show();
// 多型的弊端: 不能呼叫子類特有的成員
// f.method();

// A: 直接建立子類物件
// B: 向下轉型

// 2. 向下轉型 : 從父類型別, 轉換回子類型別
Zi z = (Zi) f;
z.method();
}
}
```

### 3.5多型中轉型存在的風險和解決方案 (應用)

+ 風險

如果被轉的引用型別變數,對應的實際型別和目標型別不是同一種類型,那麼在轉換的時候就會出現ClassCastException

+ 解決方案

+ 關鍵字

instanceof

+ 使用格式

變數名 instanceof 型別

通俗的理解:判斷關鍵字左邊的變數,是否是右邊的型別,返回boolean型別結果

+ 程式碼演示

```java
abstract class Animal {
public abstract void eat();
}

class Dog extends Animal {
public void eat() {
System.out.println("狗吃肉");
}

public void watchHome(){
System.out.println("看家");
}
}

class Cat extends Animal {
public void eat() {
System.out.println("貓吃魚");
}
}

public class Test4Polymorpic {
public static void main(String[] args) {
useAnimal(new Dog());
useAnimal(new Cat());
}

public static void useAnimal(Animal a){ // Animal a = new Dog();
// Animal a = new Cat();
a.eat();
//a.watchHome();

// Dog dog = (Dog) a;
// dog.watchHome(); // ClassCastException 型別轉換異常

// 判斷a變數記錄的型別, 是否是Dog
if(a instanceof Dog){
Dog dog = (Dog) a;
dog.watchHome();
}
}

}
```

### 3.6黑馬資訊管理系統多型改進 (應用)

+ 實現步驟

1. StudentDaoFactory類中方法的返回值定義成父類型別BaseStudentDao
2. StudentService中接收方法返回值的型別定義成父類型別BaseStudentDao

+ 程式碼實現

StudentDaoFactory類

```java
public class StudentDaoFactory {
public static BaseStudentDao getStudentDao(){
return new OtherStudentDao();
}
}
```

StudentService類

```java
public class StudentService {
// 建立StudentDao (庫管)
// private OtherStudentDao studentDao = new OtherStudentDao();

// 通過學生庫管工廠類, 獲取庫管物件
private BaseStudentDao studentDao = StudentDaoFactory.getStudentDao();
}
```

## 4.內部類

### 4.1 內部類的基本使用(理解)

- 內部類概念

- 在一個類中定義一個類。舉例:在一個類A的內部定義一個類B,類B就被稱為內部類

- 內部類定義格式

- 格式&舉例:

```java
/*
格式:
class 外部類名{
修飾符 class 內部類名{

}
}
*/

class Outer {
public class Inner {

}
}
```

- 內部類的訪問特點

- 內部類可以直接訪問外部類的成員,包括私有
- 外部類要訪問內部類的成員,必須建立物件

- 示例程式碼:

```java
/*
內部類訪問特點:
內部類可以直接訪問外部類的成員,包括私有
外部類要訪問內部類的成員,必須建立物件
*/
public class Outer {
private int num = 10;
public class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
```

### 2.2 成員內部類(理解)

- 成員內部類的定義位置

- 在類中方法,跟成員變數是一個位置

- 外界建立成員內部類格式

- 格式:外部類名.內部類名 物件名 = 外部類物件.內部類物件;
- 舉例:Outer.Inner oi = new Outer().new Inner();

- 私有成員內部類

- 將一個類,設計為內部類的目的,大多數都是不想讓外界去訪問,所以內部類的定義應該私有化,私有化之後,再提供一個可以讓外界呼叫的方法,方法內部建立內部類物件並呼叫。

- 示例程式碼:

```java
class Outer {
private int num = 10;
private class Inner {
public void show() {
System.out.println(num);
}
}
public void method() {
Inner i = new Inner();
i.show();
}
}
public class InnerDemo {
public static void main(String[] args) {
//Outer.Inner oi = new Outer().new Inner();
//oi.show();
Outer o = new Outer();
o.method();
}
}
```

- 靜態成員內部類

+ 靜態成員內部類訪問格式:外部類名.內部類名 物件名 = new 外部類名.內部類名();

+ 靜態成員內部類中的靜態方法:外部類名.內部類名.方法名();

+ 示例程式碼

```java
class Outer {
static class Inner {// 多了static關鍵字
public void show(){
System.out.println("inner..show");
}

public static void method(){
System.out.println("inner..method");
}
}
}

public class Test3Innerclass {
/*
靜態成員內部類演示
*/
public static void main(String[] args) {
// 外部類名.內部類名 物件名 = new 外部類名.內部類名();
Outer.Inner oi = new Outer.Inner();
oi.show();

Outer.Inner.method();
}
}
```

### 2.3 區域性內部類(理解)

- 區域性內部類定義位置

- 區域性內部類是在方法中定義的類

- 區域性內部類方式方式

- 區域性內部類,外界是無法直接使用,需要在方法內部建立物件並使用
- 該類可以直接訪問外部類的成員,也可以訪問方法內的區域性變數

- 示例程式碼

```java
class Outer {
private int num = 10;
public void method() {
int num2 = 20;
class Inner {
public void show() {
System.out.println(num);
System.out.println(num2);
}
}
Inner i = new Inner();
i.show();
}
}
public class OuterDemo {
public static void main(String[] args) {
Outer o = new Outer();
o.method();
}
}

```

### 2.4 匿名內部類(應用)

- 匿名內部類的前提

- 存在一個類或者介面,這裡的類可以是具體類也可以是抽象類

- 匿名內部類的格式

- 格式:new 類名 ( ) { 重寫方法 } new 介面名 ( ) { 重寫方法 }

- 舉例:

```java
new Inter(){
@Override
public void method(){}
}
```

- 匿名內部類的本質

- 本質:是一個繼承了該類或者實現了該介面的子類匿名物件

- 匿名內部類的細節

- 匿名內部類可以通過多型的形式接受

```java
Inter i = new Inter(){
@Override
public void method(){

}
}
```

- 匿名內部類直接呼叫方法

```java
interface Inter{
void method();
}

class Test{
public static void main(String[] args){
new Inter(){
@Override
public void method(){
System.out.println("我是匿名內部類");
}
}.method(); // 直接呼叫方法
}
}
```

### 2.4 匿名內部類在開發中的使用(應用)

- 匿名內部類在開發中的使用

- 當發現某個方法需要,介面或抽象類的子類物件,我們就可以傳遞一個匿名內部類過去,來簡化傳統的程式碼

- 示例程式碼:

```java
/*
游泳介面
*/
interface Swimming {
void swim();
}

public class TestSwimming {
public static void main(String[] args) {
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("鐵汁, 我們去游泳吧");
}
});
}

/**
* 使用介面的方法
*/
public static void goSwimming(Swimming swimming){
/*
Swimming swim = new Swimming() {
@Override
public void swim() {
System.out.println("鐵汁, 我們去游泳吧");
}
}
*/
swimming.swim();
}
}
```

## 5.Lambda表示式

### 5.1體驗Lambda表示式【理解】

- 程式碼演示

```java
/*
游泳介面
*/
interface Swimming {
void swim();
}

public class TestSwimming {
public static void main(String[] args) {
// 通過匿名內部類實現
goSwimming(new Swimming() {
@Override
public void swim() {
System.out.println("鐵汁, 我們去游泳吧");
}
});

/* 通過Lambda表示式實現
理解: 對於Lambda表示式, 對匿名內部類進行了優化
*/
goSwimming(() -> System.out.println("鐵汁, 我們去游泳吧"));
}

/**
* 使用介面的方法
*/
public static void goSwimming(Swimming swimming) {
swimming.swim();
}
}
```

- **函數語言程式設計思想概述**

在數學中,函式就是有輸入量、輸出量的一套計算方案,也就是“拿資料做操作”

面向物件思想強調“必須通過物件的形式來做事情”

函式式思想則儘量忽略面向物件的複雜語法:“強調做什麼,而不是以什麼形式去做”

而我們要學習的Lambda表示式就是函式式思想的體現

+ 上述幾句多細讀幾遍,即可理解

### 5.2Lambda表示式的標準格式【理解】

- 格式:

​ (形式引數) -> {程式碼塊}

- 形式引數:如果有多個引數,引數之間用逗號隔開;如果沒有引數,留空即可
- ->:由英文中畫線和大於符號組成,固定寫法。代表指向動作
- 程式碼塊:是我們具體要做的事情,也就是以前我們寫的方法體內容

- 組成Lambda表示式的三要素:

- 形式引數,箭頭,程式碼塊

### 5.3Lambda表示式練習1【應用】

- Lambda表示式的使用前提

- 有一個介面
- 介面中有且僅有一個抽象方法

- 練習描述

​ 無參無返回值抽象方法的練習

- 操作步驟

- 定義一個介面(Eatable),裡面定義一個抽象方法:void eat();
- 定義一個測試類(EatableDemo),在測試類中提供兩個方法
- 一個方法是:useEatable(Eatable e)
- 一個方法是主方法,在主方法中呼叫useEatable方法

- 示例程式碼

```java
//介面
public interface Eatable {
void eat();
}
//實現類
public class EatableImpl implements Eatable {
@Override
public void eat() {
System.out.println("一天一蘋果,醫生遠離我");
}
}
//測試類
public class EatableDemo {
public static void main(String[] args) {
//在主方法中呼叫useEatable方法
Eatable e = new EatableImpl();
useEatable(e);

//匿名內部類
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("一天一蘋果,醫生遠離我");
}
});

//Lambda表示式
useEatable(() -> {
System.out.println("一天一蘋果,醫生遠離我");
});
}

private static void useEatable(Eatable e) {
e.eat();
}
}
```

### 5.4Lambda表示式練習2【應用】

- 練習描述

有參無返回值抽象方法的練習

- 操作步驟

- 定義一個介面(Flyable),裡面定義一個抽象方法:void fly(String s);
- 定義一個測試類(FlyableDemo),在測試類中提供兩個方法
- 一個方法是:useFlyable(Flyable f)
- 一個方法是主方法,在主方法中呼叫useFlyable方法

- 示例程式碼

```java
public interface Flyable {
void fly(String s);
}

public class FlyableDemo {
public static void main(String[] args) {
//在主方法中呼叫useFlyable方法
//匿名內部類
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("飛機自駕遊");
}
});
System.out.println("--------");

//Lambda
useFlyable((String s) -> {
System.out.println(s);
System.out.println("飛機自駕遊");
});

}

private static void useFlyable(Flyable f) {
f.fly("風和日麗,晴空萬里");
}
}
```

### 5.5Lambda表示式練習3【應用】

- 練習描述

有參有返回值抽象方法的練習

- 操作步驟

- 定義一個介面(Addable),裡面定義一個抽象方法:int add(int x,int y);
- 定義一個測試類(AddableDemo),在測試類中提供兩個方法
- 一個方法是:useAddable(Addable a)
- 一個方法是主方法,在主方法中呼叫useAddable方法

- 示例程式碼

```java
public interface Addable {
int add(int x,int y);
}

public class AddableDemo {
public static void main(String[] args) {
//在主方法中呼叫useAddable方法
useAddable((int x,int y) -> {
return x + y;
});

}

private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
```

### 5.6Lambda表示式的省略模式【應用】

- 省略的規則

- 引數型別可以省略。但是有多個引數的情況下,不能只省略一個
- 如果引數有且僅有一個,那麼小括號可以省略
- 如果程式碼塊的語句只有一條,可以省略大括號和分號,和return關鍵字

- 程式碼演示

```java
public interface Addable {
int add(int x, int y);
}

public interface Flyable {
void fly(String s);
}

public class LambdaDemo {
public static void main(String[] args) {
// useAddable((int x,int y) -> {
// return x + y;
// });
//引數的型別可以省略
useAddable((x, y) -> {
return x + y;
});

// useFlyable((String s) -> {
// System.out.println(s);
// });
//如果引數有且僅有一個,那麼小括號可以省略
// useFlyable(s -> {
// System.out.println(s);
// });

//如果程式碼塊的語句只有一條,可以省略大括號和分號
useFlyable(s -> System.out.println(s));

//如果程式碼塊的語句只有一條,可以省略大括號和分號,如果有return,return也要省略掉
useAddable((x, y) -> x + y);
}

private static void useFlyable(Flyable f) {
f.fly("風和日麗,晴空萬里");
}

private static void useAddable(Addable a) {
int sum = a.add(10, 20);
System.out.println(sum);
}
}
```

### 5.7Lambda表示式的使用前提【理解】

- 使用Lambda必須要有介面
- 並且要求介面中有且僅有一個抽象方法

### 5.8Lambda表示式和匿名內部類的區別【理解】

- 所需型別不同
- 匿名內部類:可以是介面,也可以是抽象類,還可以是具體類
- Lambda表示式:只能是介面
- 使用限制不同
- 如果介面中有且僅有一個抽象方法,可以使用Lambda表示式,也可以使用匿名內部類
- 如果介面中多於一個抽象方法,只能使用匿名內部類,而不能使用Lambda表示式
- 實現原理不同
- 匿名內部類:編譯之後,產生一個單獨的.class位元組碼檔案
- Lambda表示式:編譯之後,沒有一個單獨的.class位元組碼檔案。對應的位元組碼會在執行的時候動態生成