1. 程式人生 > >JAVA基礎之介面與內部類

JAVA基礎之介面與內部類

# 介面與內部類 [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 other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } default Predicate negate() { return (t) -> !test(t); } default Predicate or(Predicate 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啟動失敗的