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
並新增doLast
和doFirst
回撥,而下面就開始仿寫一個類似的邏輯。關鍵點在於閉包和代理物件。
根據上一個例子,分為三步:
第一步:宣告代理委託物件
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
相關推薦
Java中Lambda表示式和Groovy閉包的相關解析
Lambda名詞釋義 Lambda表示式表示匿名函式,和匿名類對比,及不需要宣告函式的方法名和返回值,用表示式的形式完成函式的引數和相關邏輯。 Lambda表示式應用於Groovy和Kotlin中,作為實現函數語言程式設計的關鍵(函數語言程式設計是指一個函式
採用Java 8中Lambda表示式和預設方法的模板方法模式
原文連結 作者: 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
Java中Lambda表示式與方法引用和構造器引用
方法引用: 首先看 Timer t = new Timer(1000, System.out::println); 表示式 System.out::println 就是一個方法引用,等價於Lambda
Java中Lambda表示式
在Lambda表示式裡面第一個要介紹的是“語法糖” 語法糖(Syntactic sugar),也譯為糖衣語法,是由英國電腦科學家彼得·約翰·蘭達(Peter J
Java中lambda表示式詳解
原文地址:http://blog.laofu.online/2018/04/20/java-lambda/ 為什麼使用lambda 在java中我們很容易將一個變數賦值,比如int a =0;int b=a; 但是我們如何將一段程式碼和一個函式賦值給一個變數?這個變數應該是什麼的型別? 在java
java的lambda表示式 和stream介面使用
lambda格式 method(param->表示式); //或者 method(param->{ 語句塊; }); 示例 List<String> list=Lists.newArrayList(); list.add("jack"); list.add(
java中關於String和StringBuffer的問題與解析
構造 表達式 數據 str 字符數 stringbu 傳遞 數值 動態 問題一:String 和 StringBuffer 的區別JAVA 平臺提供了兩個類: String 和 StringBuf fer ,它們可以儲存和操作字符串,即包含多個字符的字符數據。這個 Stri
python中lambda表示式的使用以及兩個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 也稱作例項,請根據上下文理解不要混淆。接下來你會經常看到型別
Groovy閉包中return的坑
一直沉浸在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