JAVA基礎之介面與內部類
阿新 • • 發佈:2020-11-17
# 介面與內部類
[toc]
*本文主要整理了一些作者看JAVA核心技術卷第六章遇到的難點以及其思考, 歡迎小夥伴及時指出錯誤!*
## 1. Lambda表示式
### 1. 關於懶計算
在JAVA8中, 提供了 Supplier這個介面實現懶計算
原理是這樣的, 主要依據是以下三個原理
* **在JAVA8的新特性中, 只要一個介面只有一個抽象方法(不包括default和static), 那麼這個介面就會被被認為是一個函式式介面, 可以使用lambda表示式, 而註解 @FunctionalInterface 和我們的 @Override 一樣, 用於提示, 不寫也可以, 但是建議寫**
* **lambda表示式在被呼叫時才執行**
* **lambda表示式可以做型別推斷(不是太重要的原理)**
我們可以觀察Objects.requireNoNull 方法, 在引數為 null 會丟擲一個異常, 異常的內容與我們傳遞的第二個引數有關
這個方法有三個過載, 我們主要關注的是有兩個方法的過載
* 首先是傳統的過載
```java
public static T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
```
這裡直接傳入了一個String型別的message, 這樣看雖然沒什麼不妥, 但是設想一下, 如果我們的 null 不是一個經常出現的結果, 同時我們的String是通過呼叫某個方法得到的, 這樣每次執行非空判斷, 都會呼叫我們對message寫的方法, 比如我們傳入一個時間
```java
new LocalDate(1970, 1, 1);
```
這樣如果有大量的程序呼叫這個判斷, 同時並沒有那麼多null, 會造成效能浪費
* 基於懶計算的優化
```java
public static T requireNonNull(T obj, Supplier messageSupplier) {
if (obj == null)
throw new NullPointerException(messageSupplier.get());
return obj;
}
```
與上面不同, 這裡的第二個引數是一個 Supplier 介面, 我們從第一點可以得知, 由於該介面只有一個抽象方法, 因此它是一個函式式介面, 我們可以使用Lambda表示式; 又根據我們第二點, lambda表示式只有在被呼叫的時候才會執行, 那麼如果我們沒有那麼多的空判斷, 這個方法就不會執行, 當我們的第二個引數很複雜(比如要向資料庫查詢資料), 這樣就可以節省了大量的效能, 第二個引數的lambda表示式我們可以這樣寫
```java
() -> new LocalDate(1970, 1, 1)
```
類似的, 與懶計算設計思路相似的優化方法還有懶載入, 即頁面的元素(比如圖片或者視訊等)只有在被呼叫(比如我們往下翻頁的時候)才載入, 這樣大大緩解了伺服器的壓力與網路的壓力, 畢竟不是所有人都會看到底的
### 2. Predicate介面
```java
@FunctionalInterface
public interface Predicate {
boolean test(T t);
default Predicate and(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate negate() {
return (t) -> !test(t);
}
default Predicate or(Predicate super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static Predicate isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
```
可以看出, 這個介面同樣只有一個抽象方法, 因此他也是一個函式式介面, 這個函式式介面很有用, 因為它可以返回一個布林值, 在我們傳入一個方法可以做判斷
### 3. 關於方法引用
* 方法引用主要有三種情況
* object :: instanceMethod 等價於向方法傳遞引數的lambda表示式
* Class :: instanceMethod 等價於第一個引數作為方法的隱式引數(即this, 表示該引數自己, 方法包括定義自己的屬性或者呼叫自己的一些方法, 最後的結果會返回到這個引數上), 其餘的引數會傳遞到方法
* Class :: staticMethod 等價於所有的引數都傳遞到靜態方法中, 與上面的區別是有沒有隱式引數(即改變了自己的值)
* 雖然我們可以用lambda表示式來等價方法引用, **但是兩者最重要的區別是 方法引用會立即執行, 而lambda表示式只有在呼叫的時候才會執行**
* **只有當lambda表示式的方法體只調用一個方法而不做其他操作時, 我們才可以將lambda表示式重寫為方法引用**, 比如下面的就不可以, 因為它除了方法呼叫, 還進行了比較
```java
s -> s.length() == 0
```
### 4. 關於構造器引用
* 構造器引用的基本結構
* Class :: new
* 表示 Class 構造器的一個引用, 引用的構造器取決於上下文, **編譯器會自動推導**
* 陣列型別的構造器引用
* Class[] :: new
* 等價於
```java
x -> new Class[x]
```
即建立了一個指定型別的物件陣列
### 5. 關於變數的作用域
* **lambda可以捕獲外圍作用域中的變數的值**
* **lambda表示式中捕獲的值必須實際上是 事實最終變數, 即初始化後就不再為其賦新值,** 這是由於lambda表示式在呼叫後才執行, 如果改變的話會造成不安全
* l**ambda表示式的體與巢狀塊有相同的作用域**, 我們可以理解為, 在lambda表示式左側傳入的變數和上下文的變數的作用域是一致的
* 在lambda表示式中, 沒與引數也要寫括號 () -> xxx
* **在lambda表示式中, 會自動推斷變數型別, 可以不寫, (String first) -> xxx 和 (first) -> xxx是一樣的, 因此如果上文有first這個變數, 這裡就會報變數衝突的錯誤**
## 2. 內部類
### 1. 區域性內部類
* 在一個方法中區域性定義的類叫做區域性內部類
* **宣告區域性類時不能有訪問說明符**(即 public private protected), **作用域僅限於宣告這個區域性類的類中 ==> 可以訪問類的全部屬性, 包括私有屬性**
* **優點: 對外部世界完全遮蔽**
### 2. 匿名內部類
* 如果只想建立區域性內部類的一個物件而不需要給其指定名字, 可以使用匿名內部類
* ```java
new SuperType(construction parameters)
{
inner class methods and data
}
```
* SuperType可以是介面 ==> 匿名內部類實現這個介面
* SuperType可以使一個類 ==> 匿名內部類拓展這個類
* 如果引數列表的結束小括號後面跟著一個開始大括號, 就是在定義匿名內部類
* 與lambda表示式最大的區別
* lambda編譯後不會生成class檔案,那麼也就略過了類的載入、驗證、解析等。相當於是在執行時再進行相應的操作
* 這裡主要體現在對Spring的影響中, 在spring注入過程中,無法注入含確定型別的入參和出參方法的實現類,所以,才會出現無法確定型別,導致注入失敗,從而springboot啟動失敗的