1. 程式人生 > >Java中Lambda表示式和Groovy閉包的相關解析

Java中Lambda表示式和Groovy閉包的相關解析

Lambda名詞釋義

Lambda表示式表示匿名函式,和匿名類對比,及不需要宣告函式的方法名和返回值,用表示式的形式完成函式的引數和相關邏輯。

Lambda表示式應用於Groovy和Kotlin中,作為實現函數語言程式設計的關鍵(函數語言程式設計是指一個函式能夠作為另一個函式的入參)。而在JDK8中支援了對Lambda表示式的應用。

基本使用

java中對lambda表示式的宣告不像Kotlin等,其實質是用一種簡單的方式完成一些簡單介面的宣告和初始化。舉例如下:

   Runnable runnable1 = new Runnable() {
            @Override
public void run() { System.out.println("Hello"); } }; new Thread(runnable1).start();

對於啟動一個執行緒並構造Runnable介面往往需要寫這麼多程式碼,而對於Runnable其實只關心run()方法的實現。而lambda便是為了解決這種問題,簡化之後如下:

// lambda表示式
Runnable runnable = () -> {System.out.println("Hello")};
new
Thread(runnable).start();

可以發現其中Runnable的實現發生了變化,在java中lambda表示式的宣告以->隔開,->之前宣告引數,之後的{}是方法的邏輯。該表示式就是對run()的相關宣告。因為run()方法沒有引數,所以->之前用()表示。而{}中表示方法邏輯,和普通的方法邏輯一樣,如果只有一行,則大括號可以忽略() -> System.out.println("Hello")

最後簡化如下:

new  Thread(() -> System.out.println("Hello")).start();

可以看到只需要一行就可以完成之前的程式碼邏輯。

lambda表示式的實質是實現介面或抽象類並且完成方法的宣告,而對於介面和抽象類要求必須僅有一個待實現的方法(特殊情況後面會提到)。

lambda表示式很容易將其方法體中的邏輯理解成立即執行,其實不是,具體什麼時候走,要根據介面中的方法什麼時候被呼叫。

不同用法

lambda需要一個引數

有一個類如下

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

例項該物件的方式如下:

 Consumer<String> consumer = (String t) -> System.out.println(t);
 Consumer<String> consumer = (t) -> System.out.println(t);

此時lambda就是對accept(T t)方法的實現。

引數的宣告可以省略,因為其實質是實現抽象或介面中的方法,那麼引數型別肯定是明確的,而且編譯器也能夠幫我們檢測出。

如上邏輯還有一種寫法:

Consumer<String> consumer2 = System.out::println;

該種表示叫做方法引用,以::作為操作符,後面會分析。

兩個引數和返回值

java中比較器Comparator的類宣告如下

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    //...
}

可以看到compare()既有引數又有返回值,那麼構造Comparator例項的具體程式碼如下:

 Comparator<Integer> comparator = ((num1, num2) -> {
            return num1 - num2;
        });

如果lambda表示式中有返回值並且方法邏輯只有一行,那麼return可以省略,簡化後如下:

Comparator<Integer> comparator2 = ((num1, num2) -> num1 - num2);

多個引數

以此類推。。。

函式式介面

在jdk8中提出了函式式介面的概念,並且添加了java.util.function中包含了一些基本的函式式介面。

上面對於lambda的使用,發現其實現依託於上下文,他必須有一個目標型別,該型別就是函式式介面。

函式式介面代表的一種契約, 一種對某個特定函式型別的契約。 在它出現的地方,實際期望一個符合契約要求的函式。

實現一個函式式介面很簡單,類似Runnable介面的宣告,裡面包含一個待實現的方法。同時推薦在類上加上@FunctionalInterface註解。

一個簡單的函式是介面宣告如下:


@FunctionalInterface
public interface FunctionalInterface {
    // 使用lambda實現的方法
    void count(int i);

    String toString(); //same to Object.toString
    int hashCode(); //same to Object.hashCode
    boolean equals(Object obj); //same to Object.equals
}

對於一個函式式介面,其中可以包含Object中的方法宣告,因為對於每一個介面的例項都繼承於Object。同時也可以包含靜態方法和預設方法(jdk8中介面可以有靜態方法和預設方法)

方法引用

這種寫法不知道你是還記得

Consumer<String> consumer2 = System.out::println;

該宣告實際上就是使用了方法的引用。

對於一個介面的中的方法,我們可以使用lambda去實現,這樣大大簡便了程式碼的編寫。假如,我們待實現的方法其實和已有的某個方法(無論是類中的靜態還是成員方法)的邏輯一模一樣,那麼就可以使用上面的方法引用的方式。

對於Consumer<String>需要實現的方法是void accept(T t),仔細觀察這個方法,無返回值,一個String型別的引數,那麼只需要找一個符合這個的方法,就可以利用::將方法賦值給它。

對於上面的邏輯其具體的邏輯如下:

  Consumer<String> cc = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.print(s);
            }
        };

假如我們現在有如下類:

public class Method {

    @FunctionalInterface
    interface Concat {
        String accept(String a, String b);
    }

    // 靜態方法
    public static String concat(String a, String b) {
        return a + b;
    }

    // 成員方法
    public String concat1(String a, String b) {
        return a + b;
    }

    public static void main(String[] args){
        //待實現
    }
}

其中包含了一個函式式介面,定義了方法accept()接受兩個字串引數並返回一個字串。

該類中包含了兩個方法,一個靜態方法一個成員方法,引數和返回值和accept()的相同。

下面就使用這個類介紹下面幾種情況

引數匹配下的靜態方法引用

Concat concat = Method::concat;
 System.out.println(concat.accept("123", "456"));// 123456
​```java

將靜態方法的實現引用到了`accept()`上,當呼叫`accept()`時實際上呼叫的是`Method.concat()`方法。

**引數匹配下的成員方法引用**

​```java
Concat concat1 = new Method()::concat1;
System.out.println(concat.accept("123", "456"));// 123456

將成員方法引用到了accept()上,因為成員方法必須通過例項來獲取。

引數不匹配的方法引用

 Concat concat2 = String::concat;
 System.out.println(concat2.accept("123", "456"));// 123456

這個看著就比較詭異了,看一下String.concat()的方法宣告:

public String concat(String str)

成員方法,一個引數,這明顯不匹配啊。

這裡對於編譯器來說,雖然String.concat()只有一個引數,但他是成員方法,呼叫的時候也需要一個字串,這樣就湊夠了兩個字串,正好能夠和accept()的兩個引數匹配上。

和如下程式碼邏輯一樣

concat2 = new Concat() {
            @Override
            public String accept(String a, String b) {
                return a.concat(b);
            }
        };

構造方法引用

我們新宣告一個函式式介面如下:

  @FunctionalInterface
    interface StringM {
        String accept(String a);
    }

該方法的介面實現和String的構造方法類似,於是可以有如下操作:

StringM stringM = String::new;

Groovy中閉包的基本使用

在java中使用lambda表示式還要依託於一個上下文型別,及針對某一個函式式介面。而在groovy中,將他單獨抽出了一種型別及閉包。

對於一個閉包,不需要依託上下文去判斷他需要幾個引數返回值等等,我們可以先考慮閉包的編寫,再考慮怎麼用它,和java相反。

對於一個閉包的簡單使用如下:

    // 宣告一個閉包
        Closure listener = { e -> println "Clicked on $e" }
        // 兩種呼叫方式
        listener("str")
        listener.call("str")

呼叫方式兩種:以函式的方式呼叫或者是呼叫call()方法。

同時,閉包也可以作為方法的引數傳入,其類似於一個Callback介面,通過call()方法呼叫閉包中呼叫的邏輯。

Groovy中閉包的委託物件

Closure中有一個屬性為delegate,該屬性表示我們可以為閉包設定一個代理物件,那麼在閉包中可以直接使用這個代理物件的一些方法。

class DelegateDemo {

    static void main(String[] args) {
        Closure c = {
            // 呼叫代理物件的方法
            test()
        }
        // 設定代理物件
        c.delegate = new DelegateDemo()
        //執行閉包
        c.call()
    }

    void test() {
        println("this is delegate")
    }

}

利用Groovy模仿Gradle中的dependencies依賴宣告

關鍵點便在於委託

宣告一個類用於儲存所有的依賴,程式碼如下:

  static class Dependency {
    // 儲存所有的依賴
        Set<String> api = new HashSet<>();
        // 新增依賴的方法
        void api(String text){
            api.add(text)
        }
        // 執行方法,暫時只是列印所有依賴
        void exec(){
            println(api)
        }
    }

該類同時將作為一個閉包的代理物件。

其次,宣告一個方法,該方法的引數為一個閉包。

 static void dependencies(Closure closure) {
    // 宣告一個代理物件
        def dependency = new Dependency()
        // 設定委託
        closure.delegate = dependency
        // 執行閉包
        closure.call()
        // 執行
        dependency.exec()
    }

最後看一下如何使用

 static void main(String[] args) {
        dependencies {
            api 'cn.jiguang.sdk.plugin:xiaomi:3.1.0'
            api 'cn.jiguang.sdk.plugin:huawei:3.1.0'
            api 'cn.jiguang.sdk.plugin:meizu:3.1.0'
        }
    }

是不是很眼熟。。。。

執行結果:

[cn.jiguang.sdk.plugin:meizu:3.1.0, cn.jiguang.sdk.plugin:xiaomi:3.1.0, cn.jiguang.sdk.plugin:huawei:3.1.0]

利用Groovy模仿一個Task任務

在使用Gradle中,可以通過宣告一個task並新增doLastdoFirst回撥,而下面就開始仿寫一個類似的邏輯。關鍵點在於閉包和代理物件。

根據上一個例子,分為三步:

第一步:宣告代理委託物件

 static class Task {
        String config = ""
        Closure doLast = {}
        Closure doFirst = {}

        void doLast(Closure c) {
            doLast = c
        }

        void doFirst(Closure c) {
            doFirst = c
        }
        // 執行任務,只是簡單列印一下配置
        void exec() {
            println(config)
        }
        // 提供一個配置選項
        void config(String text) {
            config = text
        }
    }

第二步:宣告一個以閉包作為引數的方法

    static void task(String name, Closure closure) {
        def task = new Task()
        closure.delegate = task
        // 執行閉包,實質是對task坐初始化
        closure()
        // 呼叫doFirst
        task.doFirst.call()
        // 呼叫執行方法
        task.exec()
        // 呼叫doLast()方法
        task.doLast.call()
    }

第三步:使用

 static void main(String[] args) {
        task("test") {
            config("123")
            doLast {
                println("doLast")
            }
            doFirst {
                println("doFirst")
            }
        }
    }

在這裡,有一個關鍵,如果閉包作為最後一個引數,那麼他可以寫到()外面。

同事,如果呼叫的方法的引數如果僅為一個,則可以使用空格的方式,最終如下:

  task("test") {
            config "123"
            doLast {
                println("doLast")
            }
            doFirst {
                println("doFirst")
            }
        }

列印結果

doFirst
123
doLast

相關推薦

JavaLambda表示式Groovy相關解析

Lambda名詞釋義 Lambda表示式表示匿名函式,和匿名類對比,及不需要宣告函式的方法名和返回值,用表示式的形式完成函式的引數和相關邏輯。 Lambda表示式應用於Groovy和Kotlin中,作為實現函數語言程式設計的關鍵(函數語言程式設計是指一個函式

採用Java 8Lambda表示式預設方法的模板方法模式

原文連結 作者:   Mohamed Sanaulla  譯者: 李璟([email protected]) 模板方法模式是“四人幫”(譯者注:Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides)所著《Design 

lambda表示式應用在

def make_repeat(n): return lambda s : s * n a = make_repeat(2) print(a(8)) 等於 def make_repeat(n): def func(s): return s*n ret

JavaLambda表示式與方法引用構造器引用

方法引用: 首先看 Timer t = new Timer(1000, System.out::println); 表示式 System.out::println 就是一個方法引用,等價於Lambda

JavaLambda表示式

       在Lambda表示式裡面第一個要介紹的是“語法糖”          語法糖(Syntactic sugar),也譯為糖衣語法,是由英國電腦科學家彼得·約翰·蘭達(Peter J

Javalambda表示式詳解

原文地址:http://blog.laofu.online/2018/04/20/java-lambda/ 為什麼使用lambda 在java中我們很容易將一個變數賦值,比如int a =0;int b=a; 但是我們如何將一段程式碼和一個函式賦值給一個變數?這個變數應該是什麼的型別? 在java

javalambda表示式 stream介面使用

lambda格式 method(param->表示式); //或者 method(param->{ 語句塊; }); 示例 List<String> list=Lists.newArrayList(); list.add("jack"); list.add(

java關於StringStringBuffer的問題與解析

構造 表達式 數據 str 字符數 stringbu 傳遞 數值 動態 問題一:String 和 StringBuffer 的區別JAVA 平臺提供了兩個類: String 和 StringBuf fer ,它們可以儲存和操作字符串,即包含多個字符的字符數據。這個 Stri

pythonlambda表示式的使用以及兩個BIF即filter()map()的使用

1.lambda表示式 作用:在用python寫一些執行指令碼時,使用lambda就可以省下定義函式過程,比如我們只是需要寫一個簡單的指令碼來管理伺服器時間,我們就不需要專門定義一個函式然後再寫呼叫,使用lambda就可以使的程式碼更加簡潔 對於一些比較抽象並且整個程式使用下來只需要呼叫

【轉載】Lambda表示式Java集合框架

Java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數語言程式設計(Lambda表示式),學習和使用這些方法有助於我們寫出更加簡潔有效的程式碼.本文分別以ArrayList和HashMap為例,講解Java8集合框架(Java Collec

java Lambda表示式

https://colobu.com/2016/03/02/Java-Stream/ https://blog.csdn.net/IO_Field/article/details/54971761 http://www.runoob.com/java/java8-streams.

go 的變數捕獲 php的變數捕獲

go閉包中的變數捕獲 傳遞的是變數的引用 如下面示例: package main import "fmt" func main(){ a := 10 func(){ a = 20 fmt.Println("inside a is :

Lambda表示式Java集合框架

Java8為容器新增一些有用的方法,這些方法有些是為完善原有功能,有些是為引入函數語言程式設計(Lambda表示式),學習和使用這些方法有助於我們寫出更加簡潔有效的程式碼.本文分別以ArrayList和HashMap為例,講解Java8集合框架(Java Collections Framework)中新加入

C# 的 delegate, Lambda 表示式 event

在開始之前,先說一下文章的表達習慣。 Object a = new Object(); 在上面的例子裡,Object 是一種型別,a 是一個引用型別的變數,new Object() 構造了一個物件,構造物件也被稱為建立例項。有的文章習慣把 a 也稱作例項,請根據上下文理解不要混淆。接下來你會經常看到型別

Groovyreturn的坑

一直沉浸在Groovy的強大和方便中,稍微不注意就被坑了一把。 在each方法中return相當於Java迴圈中的continue,只會終止當前閉包中的程式碼,繼續下一次迴圈。並不會跳出迴圈外的方法。 實際應用中程式碼的邏輯往往比前面的兩段示例更復雜,如果測試不夠全面,結果

數據庫學習筆記_10_函數依賴詳解——函數依賴公理及其推得規律屬性

一個 說明 tro ans while 比較 接下來 子集 and 首先引入armstrong‘s axioms, 反射律(reflexivity rule)對於任何為LA(a)子集的LA(b)來說,LA(a)->LA(b)恒成立 增加律(argu

Java 的堆

同時 存在 堆棧 color 特殊性 垃圾回收器 速度 自動釋放 靈活  Java把內存劃分成兩種:一種是棧內存,一種是堆內存。 在Java中所有對象的存儲空間都是在堆中分配的,但是這個對象的引用卻是在堆棧中分配,也就是說在建立一個對象時從兩個地方都分配內存,在堆中

Java接口抽象類的比較

系列 分享 space 日誌信息 pub 指向 相關 最好的 就會 Java中接口和抽象類的比較-2013年5月寫的讀書筆記摘要 1. 概述 接口(Interface)和抽象類(abstract class)是 Java 語言中支持抽象類的兩種機制,是

JAVA堆棧內存分配詳解(摘抄)

如果 public china weight 所有 有道 動態 面試題 class 在Java中,有六個不同的地方可以存儲數據: 1.寄存器:最快的存儲區, 由編譯器根據需求進行分配,我們在程序中無法控制. 2. 棧:存放基本類型的變量數據和對象的引用,但對象本身不存放在棧

java的堆

ole table false 回收 jvm 是否 使用 char 編譯 棧與堆都是Java用來在Ram中存放數據的地方。與C++不同,Java自動管理棧和堆,程序員不能直接地設置棧或堆。 Java的堆是一個運行時數據區,類的對象從中分配空間。這些對象通過new、n