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位元組碼檔案。對應的位元組碼會在執行的時候動態生成