Lambda可以讓你簡單的傳遞一個行為或者代碼肠套「停可以把lambda看作是匿名函數(shù)陵像,是沒有聲明名稱的方法,但和匿名類一樣寇壳,也可以作為參數(shù)傳遞給一個方法醒颖。
可以把Lambda表達(dá)式理解為:簡潔地表示可傳遞的匿名函數(shù)的一種方式:它沒有名稱,但它有參數(shù)列表壳炎、函數(shù)主體泞歉、返回類型,可能還有一個可以拋出的異常列表匿辩。
一腰耙、在哪里使用lambda?
1.1 函數(shù)式接口
函數(shù)式接口就是只定義一個抽象方法的接口铲球。
Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實現(xiàn)挺庞,并把整個表達(dá)式作為函數(shù)式接口的實例。具體來說稼病,Lambda表達(dá)式是函數(shù)是接口的具體實現(xiàn)的實例选侨。
通過匿名內(nèi)部類也可以完成同樣的事情,但是比較笨拙:需要提供一個實現(xiàn)溯饵,然后在直接內(nèi)聯(lián)將它的實現(xiàn)實例化侵俗。如下面的例子所示,可以清楚地看到lambda的簡潔:
public class TestLambdaAndAnonymity {
/**
* 執(zhí)行方法
* @param r
*/
public static void process(Runnable r){
r.run();
}
public static void main(String[] args) {
// 使用匿名類
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("this is r1");
}
};
// 使用Lambda
Runnable r2 = ()-> System.out.println("this is r2");
process(r1);
process(r2);
// 直接將lambda作為參數(shù)傳遞
process(()-> System.out.println("this is r3"));
}
}
1.2 函數(shù)描述符
函數(shù)式接口的抽象方法的簽名基本上就是Lambda表達(dá)式的簽名丰刊。我們將這種抽象方法叫作函數(shù)描述符隘谣。
舉個例子:
Runnable 接口可以看作一個什么也不接受什么也不返回( void )的函數(shù)的簽名,因為它只有一個叫作 run 的抽象方法啄巧,這個方法什么也不接受寻歧,什么也不返回( void )。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
如上個小節(jié)的例子:“()-> System.out.println("this is r3")”秩仆, () -> void就是這個函數(shù)的簽名码泛, 代表了參數(shù)列表為空,且返回 void 的函數(shù)澄耍。這正是 Runnable 接口所代表的噪珊。
舉另一個例子晌缘, (Apple,Apple) -> int 代表接受兩個 Apple 作為參數(shù)且返回 int 的函數(shù)。
1.3 @FunctionalInterface 又是怎么回事痢站?
如果你去看看新的Java API磷箕,會發(fā)現(xiàn)函數(shù)式接口帶有 @FunctionalInterface 的標(biāo)注。這個標(biāo)注用于表示該接口會設(shè)計成一個函數(shù)式接口阵难。如果你用 @FunctionalInterface 定義了一個接口岳枷,而它卻不是函數(shù)式接口的話,編譯器將返回一個提示原因的錯誤呜叫。如下:
@FunctionalInterface
private interface ITest{
void test();
void test1();
}
表示該接口存在多個抽象方法空繁。
@FunctionalInterface 不是必需的,但對于為此設(shè)計的接口而言朱庆,使用它是比較好的做法盛泡。
二、使用函數(shù)式接口
函數(shù)式接口定義且只定義了一個抽象方法椎工。函數(shù)式接口很有用饭于,因為抽象方法的簽名可以描述Lambda表達(dá)式的簽名蜀踏。函數(shù)式接口的抽象方法的簽名稱為函數(shù)描述符维蒙。所以為了應(yīng)用不同的Lambda表達(dá)式,你需要一套能夠描述常見函數(shù)描述符的函數(shù)式接口果覆。
Java 8的庫設(shè)計師幫你在 java.util.function 包中引入了幾個新的函數(shù)式接口颅痊。
2.1 Predicate
java.util.function.Predicate<T> 接口定義了一個名叫 test 的抽象方法,它接受泛型T 對象局待,并返回一個 boolean 斑响。
@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);
}
如下篩選華為手機(jī)的例子:
/**
* 手機(jī)過濾方法,參數(shù)是手機(jī)list和Predicate<T>函數(shù)
*/
public static List<Phone> phoneFilter(List<Phone> phones, Predicate<Phone> p) {
List<Phone> results = new ArrayList<>();
for (Phone phone : phones) {
if (p.test(phone)) {
results.add(phone);
}
}
return results;
}
// 調(diào)用篩選方法
List<Phone> huaweiPhones = phoneFilter(list, (Phone p) -> "華為".equals(p.brand));
關(guān)于其中的and钳榨、or等方法此小節(jié)先不考慮舰罚。
2.2 Consumer
java.util.function.Consumer<T> 定義了一個名叫 accept 的抽象方法,它接受泛型 T的對象薛耻,沒有返回( void )营罢。你如果需要訪問類型 T 的對象,并對其執(zhí)行某些操作饼齿,就可以使用這個接口饲漾。
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
如下例子,分別打印每個值:
public class TestConsumer {
public static void main(String[] args) {
forEach(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i));
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for (T t : list) {
c.accept(t);
}
}
}
2.3 Function
java.util.function.Function<T, R> 接口定義了一個叫作 apply 的方法缕溉,它接受一個泛型 T 的對象考传,并返回一個泛型 R 的對象。如果你需要定義一個Lambda证鸥,將輸入對象的信息映射到輸出僚楞,就可以使用這個接口勤晚。
@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);
如下所示,將String列表的長度映射到Integer列表:
public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
List<R> results = new ArrayList<>();
for (T t : list) {
results.add(function.apply(t));
}
return results;
}
public static void main(String[] args) {
List<Integer> map = map(Arrays.asList("Tom", "Jerry", "XiaoMing"), (String s) -> s.length());
System.out.println(map);
}
以上簡單介紹了三種函數(shù)式接口的使用方式泉褐。
2.4 帶有數(shù)據(jù)類型的函數(shù)式接口
Java類型要么是引用類型(比如 Byte 运翼、 Integer 、 Object 兴枯、 List )血淌,要么是原始類型(比如 int 、 double 财剖、 byte 悠夯、 char )。但是泛型(比如 Consumer<T> 中的 T )只能綁定到引用類型躺坟。這是由泛型內(nèi)部的實現(xiàn)方式造成的沦补。
因此,在Java里有一個將原始類型轉(zhuǎn)換為對應(yīng)的引用類型的機(jī)制咪橙。這個機(jī)制叫作裝箱(boxing)夕膀。相反的操作,也就是將引用類型轉(zhuǎn)換為對應(yīng)的原始類型美侦,叫作拆箱(unboxing)产舞。Java還有一個自動裝箱機(jī)制來幫助程序員執(zhí)行這一任務(wù):裝箱和拆箱操作是自動完成的。
如下所示就是一個裝箱過程菠剩,將int類型裝箱成Integer類型:
List<Integer> list = new ArrayList<>();
for (int i = 300; i < 400; i++){
list.add(i);
}
但這在性能方面是要付出代價的易猫。裝箱后的值本質(zhì)上就是把原始類型包裹起來,并保存在堆里具壮。因此准颓,裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來獲取被包裹的原始值棺妓。
減少裝箱拆箱是有必要的攘已!
java8為了減少這樣的問題,針對不同的接口函數(shù)提供專門的版本怜跑,用來避免自動裝箱操作样勃。
如下舉例1000萬次的循環(huán),分別比較裝箱和不裝箱的耗時有多大:
public static void main(String[] args) throws InterruptedException {
Long current = System.currentTimeMillis();
// 非裝箱
IntPredicate intPredicate = (int i) -> i % 3 == 0;
CompletableFuture.runAsync(() -> {
for (int i = 0; i < 10000000; i++) {
intPredicate.test(RandomUtil.randomInt(100000));
}
System.out.println("線程名稱:IntPredicate" + Thread.currentThread().getName()+"耗時:"+ (System.currentTimeMillis() - current));
});
// 裝箱
Predicate<Integer> predicate = (Integer i) -> i % 3 == 0;
CompletableFuture.runAsync(() -> {
for (int i = 0; i < 10000000; i++) {
predicate.test(RandomUtil.randomInt(100000));
}
System.out.println("線程名稱:Predicate" + Thread.currentThread().getName()+"耗時:"+ (System.currentTimeMillis() - current));
});
//等待線程執(zhí)行完成
Thread.sleep(1000);
}
結(jié)果顯示妆艘,使用IntPredicate比Predicate耗時差距在50ms左右:
線程名稱:IntPredicateForkJoinPool.commonPool-worker-1耗時:143
線程名稱:PredicateForkJoinPool.commonPool-worker-2耗時:194
一般來說彤灶,針對特殊類型的函數(shù)式接口的名稱都需要加上對應(yīng)的原始類型的前綴。
下面歸納常用函數(shù)式接口:
函數(shù)式接口 | 函數(shù)式描述符 | 原始類型特化 |
---|---|---|
Predicate<T> | T->boolean | IntPredicate LongPredicate DoublePredicate |
Consumer<T> | T->void | IntConsumer LongConsumer DoubleConsumer |
Function<T,R> | T->R | IntFunction<R> IntToDoubleFunction IntToLongFunction LongFunction<R> LongToDoubleFunction LongToIntFunction DoubleFunction<R> ToIntFunction<T> ToDoubleFunction<T> ToLongFunction<T> |
Supplier<T> | ()-T | BooleanSupplier,IntSupplier LongSupplier DoubleSupplier |
UnaryOperator<T> | T->T | IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
BinaryOperator<T> | (T,T)->T | IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | (T,U)->void | ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction<T,U> |
三批旺、使用局部變量
Lambda可以沒有限
制地捕獲(也就是在其主體中引用)實例變量和靜態(tài)變量幌陕。但局部變量必須顯式聲明為 final ,或事實上是 final 汽煮。換句話說搏熄,Lambda表達(dá)式只能捕獲指派給它們的局部變量一次棚唆。(注:捕獲實例變量可以被看作捕獲最終局部變量 this 。)
如下代碼將會出現(xiàn)變異錯誤心例,因為portBynber變了兩次:
int portNumber = 1337;
Runnable r = () -> System.out.println(portNumber);
portNumber = 31337;
3.1 限制局部變量的原因宵凌?
1)實例變量和局部變量背后的實現(xiàn)有一個關(guān)鍵不同。實例變量都存儲在堆中止后,而局部變量則保存在棧上瞎惫。
如果Lambda可以直接訪問局部變量,而且Lambda是在一個線程中使用的译株,則使用Lambda的線程瓜喇,可能會在分配該變量的線程將這個變量收回之后,去訪問該變量歉糜。
因此乘寒,Java在訪問自由局部變量時,實際上是在訪問它的副本匪补,而不是訪問原始變量伞辛。
如果局部變量僅僅賦值一次那就沒有什么區(qū)別了。
2)這一限制不鼓勵你使用改變外部變量的典型命令式編程模式夯缺。
這種模式會阻礙很容易做到的并行處理蚤氏,在java8當(dāng)中使用的函數(shù)式編程可以很輕易的做到并行處理,而傳統(tǒng)的命令式編程卻需要自己去實現(xiàn)多線程與數(shù)據(jù)的合并喳逛。
四瞧捌、方法引用
4.1 方法引用簡介
方法引用是某些Lambda的快捷寫法。讓你可以重復(fù)使用現(xiàn)有的方法定義润文,并像Lambda一樣傳遞它們。
當(dāng)你需要使用方法引用時殿怜,目標(biāo)引用放在分隔符 :: 前典蝌,方法的名稱放在后面。如下面的例子:
Apple::getWeight
Apple::getWeight 就是引用了 Apple 類中定義的方法 getWeight头谜,方法引用就是Lambda表達(dá)式 (Apple a) -> a.getWeight() 的快捷寫法骏掀。
你可以把方法引用看作針對僅僅涉及單一方法的Lambda的語法糖,因為你表達(dá)同樣的事情時要寫的代碼更少了柱告。
4.2 構(gòu)建方法引用
方法引用主要有三類:
1)指向靜態(tài)方法的方法引用
如下所示截驮,指向靜態(tài)方法parseInt。
public static void main(String[] args) {
//靜態(tài)方法引用际度,parseInt
Function<String, Integer> function = Integer::parseInt;
Integer apply = function.apply("100");
System.out.println(apply);
}
2)指向任意類型實例方法的方法引用
如下所示葵袭,指向String的length方法
//指向任意實例類型方法
Function<String,Integer> function1 = String::length;
Integer hello_world = function1.apply("hello world");
System.out.println(hello_world);
可以這樣理解這句話:你在引用一個對象的方法,而這個對象本身是Lambda的一個參數(shù)乖菱。
3)指向現(xiàn)有實例對象的方法的方法引用
如下所示:
@Data
static class Student{
private String name;
private Integer age;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
}
//指向現(xiàn)有對象的方法
Student student = new Student("Jack",10);
Supplier<String> supplier = student::getName;
String s = supplier.get();
System.out.println(s);
與第二條不同坡锡,這個方法引用的對象時外部已經(jīng)存在的對象蓬网,如上述例子中的student。
4.3 構(gòu)造函數(shù)引用
對于一個現(xiàn)有構(gòu)造函數(shù)鹉勒,你可以利用它的名稱和關(guān)鍵字 new 來創(chuàng)建它的一個引用:ClassName::new 帆锋。它的功能與指向靜態(tài)方法的引用類似。
有如下實體類:
@Data
static class Student {
private String name;
private Integer age;
private String address;
public Student() {
}
public Student(String name) {
this.name = name;
}
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public Student(String name, Integer age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
}
實例:
/**
* description: 自定義三個參數(shù)的接口
* @return:
* @author: weirx
* @time: 2021/10/19 18:03
*/
interface ThreeParamsFunction<T, U, P, R> {
R apply(T t, U u, P p);
}
public static void main(String[] args) {
// 無構(gòu)造參數(shù) ()->new Student() 轉(zhuǎn)化成 Student::new
Supplier<Student> supplier = Student::new;
Student student = supplier.get();
// 一個構(gòu)造參數(shù) (name)->new Student(name) 轉(zhuǎn)化成 Student::new
Function<String, Student> function = Student::new;
function.apply("JACK");
// 兩個構(gòu)造參數(shù) (name,age)->new Student(name,age) 轉(zhuǎn)化成 Student::new
BiFunction<String, Integer, Student> biFunction = Student::new;
biFunction.apply("JACK", 10);
// 三個構(gòu)造參數(shù),沒有提供三個構(gòu)造參數(shù)的禽额,需要自己寫個接口
// (name,age,address)->new Student(name,age,address) 轉(zhuǎn)化成 Student::new
ThreeParamsFunction<String, Integer, String, Student> threeParamsFunction = Student::new;
threeParamsFunction.apply("JACK", 10, "Haerbin");
}
五锯厢、實戰(zhàn)
使用List的sort方法對蘋果進(jìn)行排序:
void sort(Comparator<? super E> c)
5.1 傳遞代碼
根據(jù)上面的sort方法構(gòu)成,我們需要一個Comparator對象對蘋果進(jìn)行比較脯倒。在沒有java8之前哲鸳,我們需要這樣做,傳遞代碼的方式:
import lombok.Data;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @description: 使用List的sort方法盔憨,對蘋果進(jìn)行排序
* @author:weirx
* @date:2021/10/20 9:44
* @version:3.0
*/
public class TestLambda {
@Data
static class Apple {
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
}
/**
* 自定義一個比較類徙菠,實現(xiàn)Comparator接口
*/
public static class CompareApple implements Comparator<Apple> {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
}
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10), new Apple(19), new Apple(9), new Apple(22));
apples.sort(new CompareApple());
}
}
5.2 匿名類
匿名類實現(xiàn)接口,重寫compare方法郁岩,不需要單獨寫實現(xiàn)類了
import lombok.Data;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @description: 使用List的sort方法婿奔,對蘋果進(jìn)行排序
* @author:weirx
* @date:2021/10/20 9:44
* @version:3.0
*/
public class TestLambda {
@Data
static class Apple {
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
}
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10), new Apple(19), new Apple(9), new Apple(22));
// 匿名類實現(xiàn)接口,重寫compare方法
apples.sort(new Comparator<Apple>() {
@Override
public int compare(Apple o1, Apple o2) {
return o1.getWeight().compareTo(o2.getWeight());
}
});
}
}
5.3 使用lambda表達(dá)式
這一步將上面的匿名類轉(zhuǎn)換成了lambda表達(dá)式问慎,下面不寫多余代碼了:
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10), new Apple(19), new Apple(9), new Apple(22));
// lambda表達(dá)式萍摊,替換匿名類
apples.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight()));
}
在Comparator中有一個靜態(tài)輔助方法comparing,其參數(shù)是傳遞一個Funtion<T>如叼,在方法內(nèi)部進(jìn)行了比較:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
所以最終會變成如下的使用方式:
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10), new Apple(19), new Apple(9), new Apple(22));
// lambda表達(dá)式冰木,替換匿名類
apples.sort(Comparator.comparing((Apple a) -> a.getWeight()));
}
5.4 方法引用
最簡潔的實現(xiàn)方式如下所示:
import lombok.Data;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
/**
* @description: 使用List的sort方法,對蘋果進(jìn)行排序
* @author:weirx
* @date:2021/10/20 9:44
* @version:3.0
*/
public class TestLambda {
@Data
static class Apple {
private Integer weight;
public Apple(Integer weight) {
this.weight = weight;
}
}
public static void main(String[] args) {
List<Apple> apples = Arrays.asList(
new Apple(10), new Apple(19), new Apple(9), new Apple(22));
// 方法引用笼恰,替換lambda
apples.sort(Comparator.comparing((Apple::getWeight)));
}