目錄
一. 行為參數化
二. lambda表達式定義
三. 依託於函數式介面使用lambda表達式
自訂函數式介面
jdk自带的函数式接口
使用过程中需要注意的一些事情
四. 方法引用
首頁 Java java教程 Java8 新特性之 Lambda 表達式

Java8 新特性之 Lambda 表達式

Feb 23, 2017 am 10:34 AM
java8 lambda 新特性

摘要: lambda表達式是Java8帶給我們的幾個重量級新功能之一,借用lambda表達式,可以讓我們的Java程式設計更簡潔。本文是Java8新特性的第一篇,將探討行為參數化、lambda表達式,以及方法參考。

lambda表達式是java8帶給我們的幾個重量級新特性之一,借用lambda表達式,可以讓我們的java程式設計更加簡潔。最近新的專案摒棄了1.6的版本,全面基於java8進行開發,本文是java8新特性的第一篇,將探討行為參數化、lambda表達式,以及方法引用。

一. 行為參數化

行為參數化簡單的說就是函數的主體只包含模板類別通用程式碼,而一些會隨著業務場景而變化的邏輯則以參數的形式傳遞到函數之中,採用行為參數化可以讓程式更加的通用,以因應頻繁變更的需求。

考慮一個業務場景,假設我們需要透過程式對蘋果進行篩選,我們先定義一個蘋果的實體:

/**
 * 苹果实体
 *
 * @author zhenchao.wang 2016-09-17 12:49
 * @version 1.0.0
 */
public class Apple {
    /** 编号 */
    private long id;
    /** 颜色 */
    private Color color;
    /** 重量 */
    private float weight;
    /** 产地 */
    private String origin;
    public Apple() {
    }
    public Apple(long id, Color color, float weight, String origin) {
        this.id = id;
        this.color = color;
        this.weight = weight;
        this.origin = origin;
    }
    // 省略getter和setter
}
登入後複製

用戶最開始的需求可能只是簡單的希望能夠透過程序篩選出綠色的蘋果,於是我們可以很快的通過程序實現:

/**
 * 筛选绿苹果
 *
 * @param apples
 * @return
 */
public static List<Apple> filterGreenApples(List<Apple> apples) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (Color.GREEN.equals(apple.getColor())) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}
登入後複製

如果過了一段時間用戶提出了新的需求,希望能夠通過程序篩選出紅色的蘋果,於是我們又針對性的添加了篩選紅色蘋果的功能:

/**
 * 筛选红苹果
 *
 * @param apples
 * @return
 */
public static List<Apple> filterRedApples(List<Apple> apples) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (Color.RED.equals(apple.getColor())) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}
登入後複製

更好的實現是把顏色作為一個參數傳遞到函數中,這樣就可以應付以後用戶提出的各種顏色篩選請求了:

/**
 * 自定义筛选颜色
 *
 * @param apples
 * @param color
 * @return
 */
public static List<Apple> filterApplesByColor(List<Apple> apples, Color color) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (color.equals(apple.getColor())) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}
登入後複製

這樣設計了之後,再也不用擔心用戶的顏色篩選需求變化了,但是不幸的是,某一天使用者提了一個需求要求能夠選擇重量達到某一標準的蘋果,有了前面的教訓,我們也把重量的標準作為參數傳遞給篩選函數,於是得到:

/**
 * 筛选指定颜色,且重要符合要求
 *
 * @param apples
 * @param color
 * @param weight
 * @return
 */
public static List<Apple> filterApplesByColorAndWeight(List<Apple> apples, Color color, float weight) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (color.equals(apple.getColor()) && apple.getWeight() >= weight) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}
登入後複製

這樣透過傳遞參數的方式真的好嗎?如果篩選條件越來越多,組合模式越來越複雜,我們是不是需要考慮到所有的情況,並針對每一種情況都有相應的應對策略呢,並且這些函數僅僅是篩選條件的部分不一樣,其餘部分都是相同的模板程式碼(遍歷集合),這時候我們就可以將行為 參數化 ,讓函數只保留模板程式碼,而把篩選條件抽離出來當做參數傳遞進來,在在java8之前,我們透過定義一個過濾器介面來實現:

/**
 * 苹果过滤接口
 *
 * @author zhenchao.wang 2016-09-17 14:21
 * @version 1.0.0
 */
@FunctionalInterface
public interface AppleFilter {
    /**
     * 筛选条件抽象
     *
     * @param apple
     * @return
     */
    boolean accept(Apple apple);
}
/**
 * 将筛选条件封装成接口
 *
 * @param apples
 * @param filter
 * @return
 */
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
    List<Apple> filterApples = new ArrayList<>();
    for (final Apple apple : apples) {
        if (filter.accept(apple)) {
            filterApples.add(apple);
        }
    }
    return filterApples;
}
登入後複製

透過上面行為抽象化之後,我們可以在具體呼叫的地方設定篩選條件,並將條件作為參數傳遞到方法中:

public static void main(String[] args) {
    List<Apple> apples = new ArrayList<>();
    // 筛选苹果
    List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
        @Override
        public boolean accept(Apple apple) {
            // 筛选重量大于100g的红苹果
            return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
        }
    });
}
登入後複製

上面的行為參數化方式採用匿名類別來實現,這樣的設計在jdk內部也經常採用,例如

java.util.Comparator
登入後複製

java.util.concurrent.Callable
登入後複製

等,使用這一類別介面的時候,我們都可以在具體呼叫的地方用過匿名類別來指定函數的具體執行邏輯,不過從上面的程式碼區塊來看,雖然很極客,但是不夠簡潔,在java8中我們可以透過lambda來簡化:

// 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
登入後複製

透過lambda表達式極大的精簡了程式碼,下面來學習java的lambda表達式吧~

二. lambda表達式定義

我們可以將lambda表達式定義為一種 簡潔、可傳遞的匿名函數,首先我們需要明確lambda表達式本質上是一個函數,雖然它不屬於某個特定的類,但具備參數列表、函數主體、返回類型,以及能夠拋出異常;其次它是匿名的,lambda表達式沒有具體的函數名稱;lambda表達式可以像參數一樣進行傳遞,從而極大的簡化程式碼的編寫。格式定義如下:

格式一:參數列表-> 表達式
格式二:參數列表-> {表達式集合}
需要注意的是,lambda表達式隱含了return關鍵字,所以在單一的表達式中,我們不需要明確的寫return關鍵字,但是當表達式是一個語句集合的時候,則需要明確添加return,並用花括號

{ }
登入後複製

將多個表達式包圍起來,下面看幾個例子:

//返回给定字符串的长度,隐含return语句
(String s) -> s.length() 
// 始终返回42的无参方法
() -> 42 
// 包含多行表达式,则用花括号括起来
(int x, int y) -> {
    int z = x * y;
    return x + z;
}
登入後複製

三. 依託於函數式介面使用lambda表達式

lambda表達式的使用需要借助於函數式接口,也就是說只有函數式接口出現地方,我們才可以將其用lambda表達式進行簡化。

自訂函數式介面

函數式介面定義為只具備 一個抽象方法 的介面。 java8在介面定義上的改進就是引入了預設方法,使得我們可以在介面中對方法提供預設的實現,但是不管存在多少個預設方法,只要具備一個且只有一個抽象方法,那麼它就是函數式接口,如下(引用上面的AppleFilter):

/**
 * 苹果过滤接口
 *
 * @author zhenchao.wang 2016-09-17 14:21
 * @version 1.0.0
 */
@FunctionalInterface
public interface AppleFilter {
    /**
     * 筛选条件抽象
     *
     * @param apple
     * @return
     */
    boolean accept(Apple apple);
}
登入後複製
AppleFilter
登入後複製
登入後複製

僅包含一個抽象方法

accept(Apple apple)
登入後複製

,依照定義可以將其視為一個函數式接口,在定義時我們為該介面添加了

@FunctionalInterface
登入後複製

注解,用于标记该接口是函数式接口,不过这个接口是可选的,当添加了该接口之后,编译器就限制了该接口只允许有一个抽象方法,否则报错,所以推荐为函数式接口添加该注解。

jdk自带的函数式接口

jdk为lambda表达式已经内置了丰富的函数式接口,如下表所示(仅列出部分):




函数式接口函数描述符原始类型特化
Predicate<T>T -> booleanIntPredicate, LongPredicate, DoublePredicate
Consumer<T>T -> voidIntConsumer, LongConsumer, DoubleConsumer
FuncationT -> RIntFuncation, IntToDoubleFunction, IntToLongFunction, LongFuncation…
Supplier() -> TBooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier
UnaryOperatorT -> TIntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator
BinaryOperator(T, T) -> TIntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator
BiPredicate(L, R) -> boolean
BiConsumer(T, U) -> void
BiFunction(T, U) -> R

下面分别就

Predicate<T>
登入後複製

Consumer<T>
登入後複製

Function<T, R>
登入後複製

的使用示例说明。

Predicate<T>

@FunctionalInterface
public interface Predicate<T> {
    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
}
登入後複製

Predicate的功能类似于上面的

AppleFilter
登入後複製
登入後複製

,利用我们在外部设定的条件对于传入的参数进行校验,并返回验证结果

boolean
登入後複製

,下面利用

Predicate
登入後複製

对List集合的元素进行过滤:

/**
 * 按照指定的条件对集合元素进行过滤
 *
 * @param list
 * @param predicate
 * @param 
 * @return
 */
public  List filter(List list, Predicate<T> predicate) {
    List newList = new ArrayList();
    for (final T t : list) {
        if (predicate.test(t)) {
            newList.add(t);
        }
    }
    return newList;
}
登入後複製

利用上面的函数式接口过滤字符串集合中的空字符串:

demo.filter(list, (String str) -> null != str && !str.isEmpty());
Consumer<T>
@FunctionalInterface
public interface Consumer<T> {
    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);
}
登入後複製

Consumer提供了一个accept抽象函数,该函数接收参数,但不返回值,下面利用

Consumer
登入後複製

遍历集合:

/**
 * 遍历集合,执行自定义行为
 *
 * @param list
 * @param consumer
 * @param 
 */
public  void filter(List list, Consumer<T> consumer) {
    for (final T t : list) {
        consumer.accept(t);
    }
}
登入後複製

利用上面的函数式接口,遍历字符串集合,并打印非空字符串:

demo.filter(list, (String str) -> {
        if (StringUtils.isNotBlank(str)) {
            System.out.println(str);
        }
    });
登入後複製

Function<T, R>

@FunctionalInterface
public interface Function<T, R> {
    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
}
登入後複製

Funcation执行转换操作,输入是类型T的数据,返回R类型的数据,下面利用

Function
登入後複製

对集合进行转换:

/**
 * 遍历集合,执行自定义转换操作
 *
 * @param list
 * @param function
 * @param 
 * @param 
 * @return
 */
public  List filter(List list, Function<T, R> function) {
    List newList = new ArrayList();
    for (final T t : list) {
        newList.add(function.apply(t));
    }
    return newList;
}
登入後複製

下面利用上面的函数式接口,将一个封装字符串(整型数字的字符串表示)的接口,转换成整型集合:

demo.filter(list, (String str) -> Integer.parseInt(str));
上面这些函数式接口还提供了一些逻辑操作的默认实现,留到后面介绍java8接口的默认方法时再讲吧~

使用过程中需要注意的一些事情

类型推断

在编码过程中,有时候可能会疑惑我们的调用代码会去具体匹配哪个函数式接口,实际上编译器会根据参数、返回类型、异常类型(如果存在)等做正确的判定。

在具体调用时,在一些时候可以省略参数的类型,从而进一步简化代码:

/

/ 筛选苹果
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        (Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// 某些情况下我们甚至可以省略参数类型,编译器会根据上下文正确判断
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
登入後複製

局部变量

上面所有例子我们的lambda表达式都是使用其主体参数,我们也可以在lambda中使用局部变量,如下:

int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
        apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);
登入後複製

该例子中我们在lambda中使用了局部变量weight,不过在lambda中使用局部变量必须要求该变量 显式声明为final或事实上的final ,这主要是因为局部变量存储在栈上,lambda表达式则在另一个线程中运行,当该线程视图访问该局部变量的时候,该变量存在被更改或回收的可能性,所以用final修饰之后就不会存在线程安全的问题。

四. 方法引用

采用方法引用可以更近一步的简化代码,有时候这种简化让代码看上去更加的直观,先看一个例子:

/* ... 省略apples的初始化操作 */
// 采用lambda表达式
apples.sort((Apple a, Apple b) -> Float.compare(a.getWeight(), b.getWeight()));
// 采用方法引用
apples.sort(Comparator.comparing(Apple::getWeight));
登入後複製

方法引用通过

::
登入後複製

将方法隶属和方法自身连接起来,主要分为三类:

静态方法

(args) -> ClassName.staticMethod(args)
登入後複製


转换成

ClassName::staticMethod
登入後複製


参数的实例方法

(args) -> args.instanceMethod()
登入後複製


转换成

ClassName::instanceMethod  // ClassName是args的类型
登入後複製


外部的实例方法

(args) -> ext.instanceMethod(args)
登入後複製


转换成

ext::instanceMethod(args)
登入後複製

 以上就是Java8 新特性之 Lambda 表达式 的内容,更多相关内容请关注PHP中文网(www.php.cn)!



本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1665
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
lambda 表達式在 C++ 中如何處理異常? lambda 表達式在 C++ 中如何處理異常? Apr 17, 2024 pm 12:42 PM

在C++中,使用Lambda表達式處理異常有兩種方法:使用try-catch區塊捕獲異常,並在catch區塊中處理或重新拋出異常。使用std::function類型的包裝函數,其try_emplace方法可以捕獲Lambda表達式中的異常。

用 C++ lambda 表達式實作多執行緒程式設計的優點是什麼? 用 C++ lambda 表達式實作多執行緒程式設計的優點是什麼? Apr 17, 2024 pm 05:24 PM

lambda表達式在C++多執行緒程式設計中的優點包括:簡潔性、靈活性、易於傳參和並行性。實戰案例:使用lambda表達式建立多執行緒,在不同執行緒中列印執行緒ID,展示了該方法的簡潔和易用性。

C++ lambda 表達式中閉包的意思是什麼? C++ lambda 表達式中閉包的意思是什麼? Apr 17, 2024 pm 06:15 PM

在C++中,閉包是能夠存取外部變數的lambda表達式。若要建立閉包,請擷取lambda表達式中的外部變數。閉包提供可重複使用性、資訊隱藏和延遲求值等優點。它們在事件處理程序等實際情況中很有用,其中即使外部變數被銷毀,閉包仍然可以存取它們。

C++ Lambda 表達式如何實作閉包? C++ Lambda 表達式如何實作閉包? Jun 01, 2024 pm 05:50 PM

C++Lambda表達式支援閉包,即保存函數作用域變數並供函數存取。語法為[capture-list](parameters)->return-type{function-body}。 capture-list定義要捕獲的變量,可以使用[=]按值捕獲所有局部變量,[&]按引用捕獲所有局部變量,或[variable1,variable2,...]捕獲特定變量。 Lambda表達式只能存取捕獲的變量,但無法修改原始值。

C++ lambda 表達式如何捕捉外部變數? C++ lambda 表達式如何捕捉外部變數? Apr 17, 2024 pm 04:39 PM

在C++中捕捉外部變數的lambda表達式有三種方法:按值擷取:建立一個變數副本。按引用擷取:獲得變數引用。同時按值和引用捕獲:允許捕獲多個變量,按值或按引用。

C++ 函式呼叫 Lambda 表達式:參數傳遞與傳回值的回呼最佳化 C++ 函式呼叫 Lambda 表達式:參數傳遞與傳回值的回呼最佳化 May 03, 2024 pm 12:12 PM

在C++中,可以使用Lambda表達式作為函數參數,實現回呼函數的靈活性。具體而言:參數傳遞:透過std::function包裝Lambda表達式,以函數指標形式傳遞給函數。傳回值處理:使用std::function宣告回呼函數指標時指定傳回值類型。實戰案例:優化GUI事件處理中的回調,避免創建不必要的物件或函數指針,提高程式碼簡潔性和可維護性。

PHP 8.3發布:新功能一覽 PHP 8.3發布:新功能一覽 Nov 27, 2023 pm 12:52 PM

PHP8.3發布:新功能一覽隨著技術的不斷發展和需求的不斷變化,程式語言也不斷更新和改進。作為一種廣泛應用於網頁開發的腳本語言,PHP一直在不斷進步,為開發者提供更強大和高效的工具。最近發布的PHP8.3版本帶來了許多期待已久的新功能和改進,以下讓我們來看看這些新特性的一覽。非空屬性的初始化在過去的PHP版本中,如果一個類別的屬性沒有明確賦值,它的值

學習PHP8的新特性,深入理解最新技術的指南 學習PHP8的新特性,深入理解最新技術的指南 Dec 23, 2023 pm 01:16 PM

深入解析PHP8的新特性,幫助您掌握最新技術隨著時間的推移,PHP程式語言一直在不斷演進和改進。最近發布的PHP8版本為開發者提供了許多令人興奮的新功能和改進,為我們的開發工作帶來了更多便利和效率。在本文中,我們將深入解析PHP8的新特性,並提供具體的程式碼範例,旨在幫助您更好地掌握這些最新的技術。 JIT編譯器PHP8引進了JIT(Just-In-Time)編

See all articles