Java8如何使用Lambda表達式簡化代碼詳解
系統(tǒng)環(huán)境:
- Java JDK 版本:1.8
參考地址:
一、Lambda 表達式簡介
1、什么是 Lambda 表達式
Lambda 表達式是在 JDK 8 中引入的一個新特性,可用于取代大部分的匿名內(nèi)部類。使用 Lambda 表達式可以完成用少量的代碼實現(xiàn)復雜的功能,極大的簡化代碼代碼量和代碼結(jié)構。同時,JDK 中也增加了大量的內(nèi)置函數(shù)式接口供我們使用,使得在使用 Lambda 表達式時更加簡單、高效。
2、為什么需要 Lambda 表達式
談起為什么需要 Lambda 表達式,那得從函數(shù)式編程開始說起。函數(shù)式編程可以簡單說是一種編程規(guī)范,也就是如何編寫程序的方法論。它屬于結(jié)構化編程的一種,主要思想是把運算過程盡量寫成一系列嵌套的函數(shù)調(diào)用。函數(shù)式編程有很多優(yōu)點,其中包括:
- 易于并發(fā)編程;
- 代碼的熱升級;
- 更方便的代碼管理;
- 代碼簡潔,開發(fā)快速;
- 接近自然語言,易于理解;
函數(shù)式編程在 C#、Python、JavaScript中都得到充分體現(xiàn),在 Java 8 版本中也得到了支持。最明顯的就是對 Lambda 表達式的支持。很多種跡象表明未來編程語言將是逐漸融合各自的特性,而不是單純的聲明式語言函數(shù)編程語言。將來聲明式編程語言借鑒函數(shù)編程思想,函數(shù)編程語言融合聲明式編程特性,這幾乎是一種必然趨勢。
在 Java 中主要引入 Lambda 表達式的作用是對現(xiàn)有編碼語義的優(yōu)化,減少語法冗余。輕量級的將代碼封裝為數(shù)據(jù),使代碼簡潔,易于理解。
二、函數(shù)式接口和定義
1、什么是函數(shù)式接口
函數(shù)式接口(Functional Interface)是一個有且僅有一個抽象方法,但是可以有多個非抽象方法的接口。Java 中函數(shù)式接口被隱式轉(zhuǎn)換為 Lambda 表達式,只有保證接口類中有且只有一個抽象方法,Java 中的 Lambda 表達式才能對該方法進行推導。
2、函數(shù)式接口格式
在 Java 函數(shù)式接口類中,需要滿足接口類中只能有一個抽象方法??偨Y(jié)如下:
- 接口有且僅有一個抽象方法;
- 允許定義靜態(tài)方法;
- 允許定義默認方法;
- 允許 java.lang.Object 中的 public 方法;
在創(chuàng)建函數(shù)式接口時,可以在接口類上面加上@FunctionalInterface注解,這時編譯器就可以對接口結(jié)構進行強制檢查是否符合函數(shù)式接口規(guī)則,如果不符合規(guī)則就顯示錯誤。當然,這個注解只是用于檢查,即使不加上該注解,只要符合函數(shù)式接口規(guī)則一樣也是函數(shù)式接口。
下面創(chuàng)建個演示的函數(shù)式接口,如下:
// @FunctionalInterface 注解說明: // 使用該注解來定義接口,編譯器會強制檢查接口是否符合函數(shù)式接口規(guī)則(有且僅有一個抽象方法),如果不符合則會報錯。 @FunctionalInterface public interface MyInterface{ /** * 抽象方法(Jdk 8 以后接口類中方法可以省去 public abstract) */ public abstract [返回值類型] [方法名稱](參數(shù)列表); /** * 其它方法(Jdk 8 以后允許接口類中添加"默認方法"與"靜態(tài)方法" ) */ ...(略) }
按照上面函數(shù)式接口,定義一個示例的函數(shù)式接口類,代碼如下:
@FunctionalInterface public interface MyCollection { void push(List list); }
3、函數(shù)式接口和 Lambda 表達式的關系
函數(shù)式接口和 Lambda 表達式的關系可以總結(jié)為:
- 函數(shù)式接口只包含一個操作方法;
- Lambda 表達式只能操作一個方法;
- Java 中的 Lambda 表達式核心就是一個函數(shù)式編程接口的實現(xiàn)。
4、當前 JDK 8 中存在的函數(shù)式接口類
在 JDK 1.8 之前,已經(jīng)存在部分函數(shù)式接口,如下:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
在 JDK 8 中新增了函數(shù)接口 java.util.function 包,里面包含了很多用來支持 Java 的函數(shù)式編程的接口類,如下:
類名稱 | 描述信息 |
---|---|
BiConsumer<T,U> | 代表了一個接受兩個輸入?yún)?shù)的操作,并且不返回任何結(jié)果。 |
BiFunction<T,U,R> | 代表了一個接受兩個輸入?yún)?shù)的方法,并且返回一個結(jié)果。 |
BinaryOperator<T> | 代表了一個作用于于兩個同類型操作符的操作,并且返回了操作符同類型的結(jié)果。 |
BiPredicate<T,U> | 代表了一個兩個參數(shù)的 boolean 值方法。 |
BooleanSupplier | 代表了 boolean 值結(jié)果的提供方。 |
Consumer<T> | 代表了接受一個輸入?yún)?shù)并且無返回的操作。 |
DoubleBinaryOperator | 代表了作用于兩個 double 值操作符的操作,并且返回了一個 double 值的結(jié)果。 |
DoubleConsumer | 代表一個接受 double 值參數(shù)的操作,并且不返回結(jié)果。 |
DoubleFunction<R> | 代表接受一個 double 值參數(shù)的方法,并且返回結(jié)果。 |
DoublePredicate | 代表一個擁有 double 值參數(shù)的 boolean 值方法。 |
DoubleSupplier | 代表一個 double 值結(jié)構的提供方。 |
DoubleToIntFunction | 接受一個 double 類型輸入,返回一個 int 類型結(jié)果。 |
DoubleToLongFunction | 接受一個 double 類型輸入,返回一個 long 類型結(jié)果。 |
DoubleUnaryOperator | 接受一個參數(shù)同為類型 double,返回值類型也為 double。 |
Function<T,R> | 接受一個輸入?yún)?shù),返回一個結(jié)果。 |
IntBinaryOperator | 接受兩個參數(shù)同為類型 int,返回值類型也為 int。 |
IntConsumer | 接受一個 int 類型的輸入?yún)?shù),無返回值。 |
IntFunction<R> | 接受一個 int 類型輸入?yún)?shù),返回一個結(jié)果。 |
IntPredicate | 接受一個 int 輸入?yún)?shù),返回一個布爾值的結(jié)果。 |
IntSupplier | 無參數(shù),返回一個 int 類型結(jié)果。 |
IntToDoubleFunction | 接受一個 int 類型輸入,返回一個double類型結(jié)果。 |
IntToLongFunction | 接受一個 int 類型輸入,返回一個 long 類型結(jié)果。 |
IntUnaryOperator | 接受一個參數(shù)同為類型 int,返回值類型也為 int。 |
LongBinaryOperator | 接受兩個參數(shù)同為類型 long,返回值類型也為 long。 |
LongConsumer | 接受一個 long 類型的輸入?yún)?shù),無返回值。 |
LongFunction<R> | 接受一個 long 類型輸入?yún)?shù),返回一個結(jié)果。 |
LongPredicate | R接受一個 long 輸入?yún)?shù),返回一個布爾值類型結(jié)果。 |
LongSupplier | 無參數(shù),返回一個結(jié)果 long 類型的值。 |
LongToDoubleFunction | 接受一個 long 類型輸入,返回一個 double 類型結(jié)果。 |
LongToIntFunction | 接受一個 long 類型輸入,返回一個 int 類型結(jié)果。 |
LongUnaryOperator | 接受一個參數(shù)同為類型 long,返回值類型也為 long。 |
ObjDoubleConsumer<T> | 接受一個 object 類型和一個 double 類型的輸入?yún)?shù),無返回值。 |
ObjIntConsumer<T> | 接受一個 object 類型和一個 int 類型的輸入?yún)?shù),無返回值。 |
ObjLongConsumer<T> | 接受一個 object 類型和一個 long 類型的輸入?yún)?shù),無返回值。 |
Predicate<T> | 接受一個輸入?yún)?shù),返回一個布爾值結(jié)果。 |
Supplier<T> | 無參數(shù),返回一個結(jié)果。 |
ToDoubleBiFunction<T,U> | 接受兩個輸入?yún)?shù),返回一個 double 類型結(jié)果 |
ToDoubleFunction<T> | 接受一個輸入?yún)?shù),返回一個 double 類型結(jié)果。 |
ToIntBiFunction<T,U> | 接受兩個輸入?yún)?shù),返回一個 int 類型結(jié)果。 |
ToIntFunction<T> | 接受一個輸入?yún)?shù),返回一個 int 類型結(jié)果。 |
ToLongBiFunction<T,U> | 接受兩個輸入?yún)?shù),返回一個 long 類型結(jié)果。 |
ToLongFunction<T> | 接受一個輸入?yún)?shù),返回一個 long 類型結(jié)果。 |
UnaryOperator<T> | 接受一個參數(shù)為類型 T,返回值類型也為 T。 |
5、JDK 中常見的函數(shù)式接口類
上面 java.util.function 包提供了眾多的函數(shù)式接口,其中常用的有:
- java.util.function.Predicate<T>:接收參數(shù)對象 T,返回一個 boolean 類型結(jié)果。
- java.util.function.Comsumer<T>:接收參數(shù)對象 T,不返回結(jié)果。
- java.util.function.Function<T,R>:接收參數(shù)對象 T,返回結(jié)果對象 R。
- java.util.function.Supplier<T>:不接收參數(shù),提供 T 對象的創(chuàng)建工廠。
- java.util.function.UnaryOperator<T>:接收參數(shù)對象 T,返回結(jié)果對象 T。
- java.util.function.BinaryOperator<T>:接收兩個 T 對象,返回一個 T 對象結(jié)果。
注:為了使易懂,下面示例中 Lambda 表達式?jīng)]有使用的最簡易寫法,而是使用比較繁瑣的寫法。
(1)、java.util.function.Predicate<T>
接口類作用: 接收參數(shù)對象T,返回一個 boolean 類型結(jié)果。
接口類源碼:
@FunctionalInterface public interface Predicate<T> { /** abstract 方法,接收一個參數(shù), 判斷這個參數(shù)是否匹配某種規(guī)則, 然后返回布爾值結(jié)果 */ boolean test(T t); /** default 方法,接收另外一個 Predicate<T> 類型參數(shù)進行"邏輯與"操作,返回一個新的 Predicate */ default Predicate<T> and(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) && other.test(t); } /** default 方法,接收另外一個 Predicate<T> 類型參數(shù)進行"邏輯或"操作,返回一個新的 Predicate */ default Predicate<T> negate() { return (t) -> !test(t); } /** default 方法,返回當前 Predicate 取反操作之后的 Predicate */ default Predicate<T> or(Predicate<? super T> other) { Objects.requireNonNull(other); return (t) -> test(t) || other.test(t); } static <T> Predicate<T> isEqual(Object targetRef) { return (null == targetRef) ? Objects::isNull : object -> targetRef.equals(object); } }
使用示例:
public class PredicateExample { /** 這里創(chuàng)建一個 Prodicate,設置驗證秘鑰的一個邏輯,然后返回并輸出驗證結(jié)果 */ public static void main(String[] args) { // 創(chuàng)建 Predicate 及 Lambda 表達式與待實現(xiàn)的邏輯 Predicate<String> validation = (String secret) -> { return "123456".equals(secret); }; // 調(diào)用 Predicate 提供的 test 方法并輸出結(jié)果 System.out.println(validation.test("123")); System.out.println(validation.test("123456")); } }
日常開發(fā)中,需要對某個值進行判斷操作,并且返回判斷結(jié)果,這時可以考慮使用 Predicate 接口,以及它的 Lambda 表達式的實現(xiàn),能方便的實現(xiàn)我們業(yè)務上的一些功能。
(2)、java.util.function.Comsumer<T>
接口類作用: 接收參數(shù)對象 T,不返回結(jié)果。
接口類源碼:
@FunctionalInterface public interface Consumer<T> { /** abstract 方法,接收一個參數(shù),執(zhí)行消費邏輯 */ void accept(T t); /** default 方法,將兩個 Consumer 連接到一起,再進行消費 */ default Consumer<T> andThen(Consumer<? super T> after) { Objects.requireNonNull(after); return (T t) -> { accept(t); after.accept(t); }; } }
使用示例:
/** 這里創(chuàng)建一個 Consumer,模擬發(fā)送消息并打印內(nèi)容 */ public class ConsumerExample { public static void main(String[] args) { // 創(chuàng)建 Consumer 及 Lambda 表達式與待實現(xiàn)的邏輯 Consumer<String> consumer = (String message) -> { System.out.println("發(fā)送消息內(nèi)容:" + message); }; // 調(diào)用 Consumer 提供的 accept 方法 consumer.accept("測試消息"); } }
日常開發(fā)中,需要對某個類型進行公共處理,并且不需要任何返回值,這時可以考慮使用 Consumer 接口及它的 Lambda 表達式的實現(xiàn),能方便的實現(xiàn)我們業(yè)務上的一些功能。
(3)、java.util.function.Function<T,R>
接口類作用: 接收參數(shù)對象 T,返回結(jié)果對象 R。
接口類源碼:
@FunctionalInterface public interface Function<T, R> { /**abstract 方法,接收一個參數(shù)進行處理,然后返回處理結(jié)果 R */ R apply(T t); /** default 方法,先執(zhí)行參數(shù)(Function)的,再執(zhí)行調(diào)用者(Function) */ default <V> Function<V, R> compose(Function<? super V, ? extends T> before) { Objects.requireNonNull(before); return (V v) -> apply(before.apply(v)); } /** default 方法,先執(zhí)行調(diào)用者,再執(zhí)行參數(shù),和compose相反 */ default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) { Objects.requireNonNull(after); return (T t) -> after.apply(apply(t)); } /** 返回當前正在執(zhí)行的方法 */ static <T> Function<T, T> identity() { return t -> t; } }
使用示例:
/** 這里創(chuàng)建一個 Function,對傳入的參數(shù)進行驗證,如果包含 a 字符就返回1,否則返回0 */ public class FunctionExample { public static void main(String[] args) { // 創(chuàng)建 Function 及 Lambda 表達式與待實現(xiàn)的邏輯 Function<String, Integer> function = (String str) -> { return str.contains("a") ? 1 : 0; }; // 調(diào)用 Function 提供的 apply 方法 System.out.println(function.apply("abcd")); System.out.println(function.apply("efgh")); } }
日常開發(fā)中,需要對某個類型數(shù)據(jù)進行操作,經(jīng)過一系列邏輯后轉(zhuǎn)換為一個新的類型進行返回,這時可以考慮使用 Function 接口及它的 Lambda 表達式的實現(xiàn),能方便的實現(xiàn)我們業(yè)務上的一些功能。
(4)、java.util.function.Supplier<T>
接口類作用: 不接收參數(shù),提供 T 對象的創(chuàng)建工廠。
接口類源碼:
@FunctionalInterface public interface Supplier<T> { /** abstract 方法,設置業(yè)務邏輯,獲取邏輯中創(chuàng)建的對象 */ T get(); }
使用示例:
/** 這里創(chuàng)建一個 Supplier,用于生成隨機ID,通過 get 方法獲取生成的隨機ID值 */ public class SupplierExample { public static void main(String[] args) { // 創(chuàng)建 Supplier 及 Lambda 表達式與待實現(xiàn)的邏輯 Supplier<String> supplier = () -> { return UUID.randomUUID().toString(); }; // 調(diào)用 Supplier 提供的 get 方法 System.out.println(supplier.get()); } }
日常開發(fā)中,需要創(chuàng)建一個統(tǒng)一的工廠用于生成特定的產(chǎn)物完成特定的目標,這時就可以考慮使用 Supplier 接口及它的 Lambda 表達式的實現(xiàn),能方便的實現(xiàn)我們業(yè)務上的一些功能。
(5)、java.util.function.UnaryOperator<T>
接口類作用: 接收參數(shù)對象 T,返回結(jié)果對象 T。
接口類源碼:
// 可以看到 UnaryOperator 繼承了 Function 接口 @FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { /** static 方法,接收一個參數(shù),然后對其處理后再返回 */ static <T> UnaryOperator<T> identity() { return t -> t; } }
使用示例:
/** 這里創(chuàng)建一個 UnaryOperator,接收一個字符串進行加工處理后返回新字符串 */ public class UnaryOperatorExample { public static void main(String[] args) { // 創(chuàng)建 UnaryOperator 及 Lambda 表達式與待實現(xiàn)的邏輯 UnaryOperator<String> unaryOperator = (String str) -> { return "[" + str + "]"; }; // 調(diào)用 UnaryOperator 繼承的 Function 提供的 apply 方法 System.out.println(unaryOperator.apply("hello")); } }
日常開發(fā)中,我們經(jīng)常要對一個已有的對象進行操作修改,然后返回修改后的對象,這時就可以考慮使用 UnaryOperator 接口及它的 Lambda 表達式的實現(xiàn),能方便的實現(xiàn)我們業(yè)務上的一些功能。
(6)、java.util.function.BinaryOperator<T>
接口類作用: 接收兩個 T 對象,返回一個 T 對象結(jié)果。
接口類源碼:
@FunctionalInterface public interface BinaryOperator<T> extends BiFunction<T,T,T> { /** abstract 方法,通過比較器Comparator來比較兩個元素中較小的一個作為返回值返回 */ public static <T> BinaryOperator<T> minBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) <= 0 ? a : b; } /** 通過比較器Comparator來比較兩個元素中較大的一個作為返回值返回 */ public static <T> BinaryOperator<T> maxBy(Comparator<? super T> comparator) { Objects.requireNonNull(comparator); return (a, b) -> comparator.compare(a, b) >= 0 ? a : b; } }
使用示例:
/** 這里創(chuàng)建一個 BinaryOperator,比較傳入的兩個參數(shù)哪個值最大,返回最大值 */ public class BinaryOperatorExample { public static void main(String[] args) { // 創(chuàng)建 BinaryOperator 及 Lambda 表達式與待實現(xiàn)的邏輯 BinaryOperator<Integer> binaryOperator = (Integer t1, Integer t2) -> { return t1 > t2 ? t1 : t2; }; // 調(diào)用 BinaryOperator 繼承的 BiFunction 提供的 apply 方法 System.out.println(binaryOperator.apply(1, 2)); } }
在使用這幾種基本函數(shù)接口時傳入?yún)?shù) T 不能是基本類型,如 BinaryOperator 中 T 不能設置為 int,只能使用 Integer 包裝類,這也限制了 Lambda 表達式中設置參數(shù)時候,使用包裝類替換基本類型。
日常開發(fā)中,我們有時候會對兩個對象進行操作,執(zhí)行一些操作邏輯后返回結(jié)果,這時就可以考慮使用 BinaryOperator 接口及它的 Lambda 表達式的實現(xiàn),能方便的實現(xiàn)我們業(yè)務上的一些功能。
三、Lambda 表達式基本語法
1、Lambda 表達式的組成
Lambda 表達式的組成可以拆分為:
- 聲明: 與 Lambda 表達式綁定的接口類型。
- 參數(shù): 參數(shù)包含在一對 () 中,和綁定的接口中的抽象方法中的參數(shù)個數(shù)及順序一致。
- 操作符: ->
- 執(zhí)行代碼塊: 執(zhí)行代碼塊包含在一對 {} 中,出現(xiàn)在操作符的右側(cè)。
[接口聲明] = (參數(shù)) -> {執(zhí)行代碼塊}
2、Lambda 表達式的格式
Lambda 表達式可以分為下面幾種格式:
- 無參數(shù),無返回值;
- 有一個參數(shù),無返回值;
- 左側(cè)只有一個參數(shù),小括號可以省略不寫;
- 有兩個以上參數(shù),有返回值,并且Lambda 體中有多條語句;
- 若右側(cè)Lambda體中,只有一條語句,return 和大括號都可以省略不寫;
- Lambda 表達式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫,jvm編譯器會進行上下文推斷出,數(shù)據(jù)類型“類型推斷”;
(1)、無參數(shù),無返回值
() -> System.out.println("測試");
(2)、有一個參數(shù),無返回值
(x) -> System.out.println(x);
(3)、左側(cè)只有一個參數(shù),小括號可以省略不寫
x -> System.out.println(x);
(4)、有兩個以上參數(shù),有返回值,并且Lambda 體中有多條語句
Comparator<Integer> comparator = (x, y) -> { System.out.println("測試"); return Integer.compare(x,y); };
(5)、若右側(cè)Lambda體中,只有一條語句,return 和大括號都可以省略不寫
Comparator<Integer> Comparator = (x, y) -> Integer.compare(x, y);
(6)、Lambda 表達式的參數(shù)列表的數(shù)據(jù)類型可以省略不寫,JVM 在運行時,會自動根據(jù)綁定的抽象方法中的參數(shù),進行數(shù)據(jù)類型推導
(Integer x, Integer y) -> Integer.compare();
四、Lambda 表達式中變量作用域
Java 中的變量捕獲與變量隱藏:
- 變量捕獲: 局部類和匿名內(nèi)部類可以訪問被 final 修飾的封閉塊內(nèi)的局部變量。
- 變量隱藏: 在一個類中,子類中的成員變量如果和父類中的成員變量同名,那么即使他們類型不一樣,只要名字一樣,父類中的成員變量都會被隱藏。
在局部類和匿名內(nèi)部類都存在 變量捕獲 與 變量隱藏,而在 Lambda 表達式中則只支持 變量捕獲。
下面是對這作用域得演示示例:
(1)、匿名內(nèi)部類:
public class VariableExample { /** 成員變量 */ String str1 = "成員變量"; public void innerClass() { // 方法內(nèi)部變量 String str2 = "方法內(nèi)部變量"; // 使用匿名內(nèi)部類創(chuàng)建線程 new Thread(new Runnable() { // 匿名內(nèi)部類內(nèi)部變量 String str3 = "匿名內(nèi)部類內(nèi)部變量"; @Override public void run() { /* 訪問變量 */ System.out.println("匿名內(nèi)部類輸出:" + str1); System.out.println("匿名內(nèi)部類輸出:" + str2); System.out.println("匿名內(nèi)部類輸出:" + str3); /* 修改變量 */ str1 = "修改訪問成員變量"; // str2 = "修改訪問方法內(nèi)部變量"; // 不能進行修改,默認推導變量的修飾符 final str3 = "修改訪問匿名內(nèi)部類內(nèi)部變量"; /* 在匿名內(nèi)部類中定義和類外部變量一樣名稱的變量 */ String str1 = "重新命名成員變量"; String str2 = "重新命名方法內(nèi)部變量"; } }).start(); } /** Main 方法 */ public static void main(String[] args) { VariableExample variableExample = new VariableExample(); // 匿名內(nèi)部類 variableExample.innerClass(); } }
(2)、Lambda 表達式:
public class VariableExample { /** 成員變量 */ String str1 = "成員變量"; public void lambdaExpression() { // 方法內(nèi)部變量 String str2 = "方法內(nèi)部變量"; new Thread(()->{ // Lambda 內(nèi)部變量 String str3 = "Lambda 內(nèi)部變量"; /* 訪問變量 */ // 訪問成員變量 System.out.println("Lambda 表達式輸出:" + str1); // 訪問方法內(nèi)部變量 System.out.println("Lambda 表達式輸出:" + str2); // 訪問匿名內(nèi)部類內(nèi)部變量 System.out.println("Lambda 表達式輸出:" + str3); /* 修改變量 */ str1 = "修改訪問成員變量"; // str2 = "修改訪問方法內(nèi)部變量"; // 不能進行修改,默認推導變量的修飾符 final str3 = "修改訪問匿名內(nèi)部類內(nèi)部變量"; /* 在 Lambda 中定義和類外部變量一樣名稱的變量 */ String str1 = "重新命名成員變量"; // String str2 = "重新命名方法內(nèi)部變量"; // 不能命名,lambda 不支持變量隱藏 }).start(); } /** Main 方法 */ public static void main(String[] args) { VariableExample variableExample = new VariableExample(); // 匿名內(nèi)部類 variableExample.innerClass(); // Lambda 表達式 variableExample.lambdaExpression(); } }
五、Lambda 表達式方法重載問題
當使用 Lambda 表達式,調(diào)用一個類中的重載方法,且方法中的參數(shù)為都為函數(shù)接口,函數(shù)接口中定義的方法接收的參數(shù)類型相同,這時候 Lambda 是無法推斷出要調(diào)用哪個方法。
函數(shù)接口A:
@FunctionalInterface interface MyInterfaceA { void push(String param); }
函數(shù)接口B:
@FunctionalInterface interface MyInterfaceB { void pull(String param); }
示例,實現(xiàn)方法重載與測試的 Main 方法:
public class LambdaOverloadExample { // 重載方法A public static void method(MyInterfaceA myInterfaceA) { myInterfaceA.push("hello 1"); } // 重載方法B public static void method(MyInterfaceB myInterfaceB) { myInterfaceB.pull("Hello 2"); } /** Main 方法*/ public static void main(String[] args) { // 使用匿名內(nèi)部類 method(new MyInterfaceA() { @Override public void push(String param) { System.out.println(param); } }); method(new MyInterfaceB() { @Override public void pull(String param) { System.out.println(param); } }); // 使用 Lambda 表達式 //method(param -> System.out.println(param)); // 編譯器提示錯誤,表示無法推斷使用哪個參數(shù) } }
上面注掉的那部分代碼,在編輯器中直接提示錯誤,很顯然 Lambda 表達式無法直接推斷出使用哪個類中的重載方法。其實,只要明確告訴 Lambda 表達式使用哪個參數(shù),就可以很簡單的解決問題,比如以上面的例子,在 Lambda 表達式使用 method 方法時,將參數(shù)類型轉(zhuǎn)換為對應的要使用的類型就可以解決這個問題,代碼如下:
// 轉(zhuǎn)換參數(shù)為 MyInterfaceA method((MyInterfaceA)param -> System.out.println(param)); // 轉(zhuǎn)換參數(shù)為 MyInterfaceB method((MyInterfaceB)param -> System.out.println(param));
按上面進行修改后就可以正常使用 Lambda 表達式了,如果不習慣也可以使用匿名內(nèi)部類進行方法調(diào)用,內(nèi)名內(nèi)部類是沒有相關問題的。
六、Lambda 表達式方法引用
方法引用本質(zhì)上就是對方法調(diào)用的簡化,方法引用和函數(shù)式接口綁定,在使用過程中會創(chuàng)建函數(shù)式接口的實例,是結(jié)合 Lambda 表達式的一種特性。在應用過程中,方法引用常分為:
- 靜態(tài)方法引用
- 實例方法引用
- 構造方法引用
- 特定類型的任意對象實例方法引用
注意:在使用 Lmabda 方法引用時雖然能夠簡化代碼,但是在實際開發(fā)中不可因需要簡化代碼而過度使用方法引用,因為他會在很大程度上降低代碼可讀性。
1、創(chuàng)建示例的實體類
為了下面示例方便,我們首先創(chuàng)建一個 Person 實體類,如下:
public class Person { /** 姓名 */ private String name; /** 歲數(shù) */ private int age; public Person() { } public Person(String name, int age) { this.name = name; this.age = 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; } @Override public String toString() { return "Person{name=" + name + ",age=" + age + "}"; } }
2、靜態(tài)方法引用示例
靜態(tài)方法的引用的使用: 靜態(tài)方法所在類.方法名稱() –> 靜態(tài)方法所在類 :: 方法名稱
創(chuàng)建一個使用靜態(tài)方法引用的示例類:
public class StaticMethodExample { /** 測試的靜態(tài)方法 */ public static int compareAge(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } public static void main(String[] args) { // 創(chuàng)建 Person 集合 List<Person> personList = new ArrayList<>(); personList.add(new Person("Wangqin",26)); personList.add(new Person("Liming",22)); personList.add(new Person("Alisi",18)); personList.add(new Person("Jerry",31)); // 按歲數(shù)進行排序,使用靜態(tài)方法引用 personList.sort(StaticMethodExample::compareAge); } }
3、實例方法引用示例
實例方法的引用的使用:創(chuàng)建類型對應一個對象 –> 對應應用 :: 實例方法名稱
創(chuàng)建一個封裝實例方法的類:
public class PersonUtil{ /** 測試的實例方法 */ public int compareAge(Person p1, Person p2) { return p1.getAge() - p2.getAge(); } }
創(chuàng)建一個使用實例方法引用的示例類:
public class InstanceMethodExample { public static void main(String[] args) { // 創(chuàng)建 Person 集合 List<Person> personList = new ArrayList<>(); personList.add(new Person("Wangqin",26)); personList.add(new Person("Liming",22)); personList.add(new Person("Alisi",18)); personList.add(new Person("Jerry",31)); // 按歲數(shù)進行排序, PersonUtil personUtil = new PersonUtil(); // 引用實例方法 personList.sort(personUtil::compareAge); } }
4、構造方法引用示例
構造方法的引用的使用:綁定函數(shù)式接口
創(chuàng)建一個函數(shù)式接口,且設置接收參數(shù)和 Person 的構造方法相同,返回 Person 對象的方法定義:
@FunctionalInterface public interface PersonConstructor{ Person initInstance(String name, int age); }
創(chuàng)建一個使用構造方法引用的示例類
public class LambdaConstructorMethodExample { public static void main(String[] args) { // 創(chuàng)建 Person 集合 List<Person> personList = new ArrayList<>(); personList.add(new Person("Wangqin", 26)); personList.add(new Person("Liming", 22)); personList.add(new Person("Alisi", 18)); personList.add(new Person("Jerry", 31)); // 構造方法引用 PersonConstructor personConstructor = Person::new; Person person = personConstructor.initInstance("linda", 18); System.out.println(person); } }
5、特定類型的任意對象實例方法引用示例
特定類型的任意對象實例方法引用示例:特定類型 :: 特定類型的方法
以下是對特定類型的任意對象的實例方法的引用的示例:
public class LambdaExample { public static void main(String[] args) { // 創(chuàng)建字符串集合 List<String> strList = new ArrayList<>(); strList.add("Jerry"); strList.add("Mini"); strList.add("Kary"); strList.add("walls"); // 使用集合的sort方法,按照String的compareToIgnoreCase進行排序(比較字符串hash值) strList.sort(String::compareToIgnoreCase); System.out.println(strList); } }
這里根據(jù)定義的集合 strList 去推導目標類型參數(shù)值,如果不符合后面?zhèn)魅氲姆椒ㄒ盟鶎念愋?,將報錯。該方法參考等效 Lambda 表達式 String::compareToIgnoreCase 的參數(shù)列表 (String a, String b),其中 a 和 b 是用于更好地描述這個例子中的任意名稱。方法引用將調(diào)用該方法 a.compareToIgnoreCase(b)。
到此這篇關于Java8如何使用Lambda表達式簡化代碼的文章就介紹到這了,更多相關Java8用Lambda表達式簡化代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java求素數(shù)和最大公約數(shù)的簡單代碼示例
這篇文章主要介紹了Java求素數(shù)和最大公約數(shù)的簡單代碼示例,其中作者創(chuàng)建的Fraction類可以用來進行各種分數(shù)運算,需要的朋友可以參考下2015-09-09Java Date類常用示例_動力節(jié)點Java學院整理
在JDK1.0中,Date類是唯一的一個代表時間的類,但是由于Date類不便于實現(xiàn)國際化,所以從JDK1.1版本開始,推薦使用Calendar類進行時間和日期處理。這里簡單介紹一下Date類的使用,需要的朋友可以參考下2017-05-05Java實現(xiàn)讀取SFTP服務器指定目錄文件的方法
SFTP是一種在安全通道上傳輸文件的協(xié)議,它是基于SSH(Secure Shell)協(xié)議的擴展,用于在客戶端和服務器之間進行加密的文件傳輸,這篇文章主要介紹了Java實現(xiàn)讀取SFTP服務器指定目錄文件,感興趣的朋友跟隨小編一起看看吧2023-08-08簡單操作實現(xiàn)Java jsp servlet文件上傳過程解析
這篇文章主要介紹了簡單操作實現(xiàn)Java jsp servlet文件上傳過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-10-10SpringBoot+Jpa項目配置雙數(shù)據(jù)源的實現(xiàn)
本文主要介紹了SpringBoot+Jpa項目配置雙數(shù)據(jù)庫源的實現(xiàn),文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-12-12mybatis的insert語句插入數(shù)據(jù)時的返回值的實現(xiàn)
這篇文章主要介紹了mybatis的insert語句插入數(shù)據(jù)時的返回值的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-10-10JavaWeb項目中dll文件動態(tài)加載方法解析(詳細步驟)
這篇文章主要介紹了JavaWeb項目中dll文件動態(tài)加載方法,步驟詳細,在這里分享給大家,需要的朋友可以了解下。2017-09-09java實現(xiàn)統(tǒng)計字符串中大寫字母,小寫字母及數(shù)字出現(xiàn)次數(shù)的方法示例
這篇文章主要介紹了java實現(xiàn)統(tǒng)計字符串中大寫字母,小寫字母及數(shù)字出現(xiàn)次數(shù)的方法,涉及java針對字符串的遍歷、判斷、運算相關操作技巧,需要的朋友可以參考下2019-06-06