作者:@Ryan-Miao
本文為作者原創(chuàng),轉(zhuǎn)載請(qǐng)注明出處:http://www.cnblogs.com/woshimrf/p/java8-lambda.html
目錄
1.1 Java8中有效的Lambda表達(dá)式
1.2 Lambda的基本語法
2.1 函數(shù)式接口可以做什么
3.1 第1步: 行為參數(shù)化
3.2 第2步:使用函數(shù)式接口來傳遞行為
3.3 第3步: 傳遞Lambda
4.1 Predicate
4.2 Consuer
4.3 Function
4.4 基本類型函數(shù)接口
9.1 原始方案
9.2 使用List內(nèi)置sort
9.3 Lambda表達(dá)式代替匿名內(nèi)部類
9.4 進(jìn)一步優(yōu)化Lambda
豬腳:以下內(nèi)容參考《Java 8 in Action》
本次學(xué)習(xí)內(nèi)容:
Lambda 基本模式
環(huán)繞執(zhí)行模式
函數(shù)式接口,類型推斷
方法引用
Lambda 復(fù)合
上一篇: Java8學(xué)習(xí)(2)- 通過行為參數(shù)化傳遞代碼--lambda代替策略模式
1. 結(jié)構(gòu)
初始化一個(gè)比較器:
Comparator<Apple> byWeight = new Comparator<Apple>() { public int copare(Apple a1, Apple a2){ return a1.getWeight().compareTo(a2.getWeight() ); } }
使用Lambda表達(dá)式:
Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight() );
參數(shù)列表--compare方法的的兩個(gè)參數(shù)
箭頭 --- 把參數(shù)列表與lambda主體分割開
Lambda主體 --- 表達(dá)式的值就是Lambda的返回值
1.1 Java8中有效的Lambda表達(dá)式
接收一個(gè)字符串,并返回字符串長(zhǎng)度int
(String a) -> s.length()
接收一個(gè)Apple類參數(shù),返回一個(gè)boolean值
(Apple a) -> a.getWeight() > 150
接收兩個(gè)參數(shù),沒有返回值(void),多行語句需要用大括號(hào)包圍
(int x, int y) -> { System.out.println("Result:"); System.out.println(x + y); }
不接收參數(shù),返回一個(gè)值
()-> 42
接收兩個(gè)參數(shù),返回一個(gè)值
(Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight() );
1.2 Lambda的基本語法
(parameters) -> expression 或 (parameters) -> {statements}
2. 函數(shù)式接口
在上次的學(xué)習(xí)中的通過行為參數(shù)化傳遞代碼, Predicate(T)
就是一個(gè)函數(shù)式接口:
public interface Predicate<T> { boolean test(T t); }
函數(shù)式接口就是只定義一個(gè)抽象方法的接口。
Java API中很多符合這個(gè)條件。比如:
public interface Comparable<T> { public int compareTo(T o); }public interface Runnable { public abstract void run(); }@FunctionalInterfacepublic interface Callable<V> { V call() throws Exception; }
2.1 函數(shù)式接口可以做什么
Lambda表達(dá)式允許你直接以內(nèi)聯(lián)的形式為函數(shù)式接口的抽象方法提供實(shí)現(xiàn),并把表達(dá)式作為函數(shù)式接口的實(shí)例(函數(shù)式接口一個(gè)具體實(shí)現(xiàn)的實(shí)例)。就像內(nèi)部類一樣,但看起來比內(nèi)部類簡(jiǎn)潔。
Runnable r1 = () -> System.out.println("1"); Runnable r2 = new Runnable(){ public void run(){ System.out.println("2"); } };public static void process(Runnable r) { r.run(); }process(r1);process(r2);process(() -> System.out.println(3));
@FunctionalInterface
是一個(gè)標(biāo)注,用來告訴編譯器這是一個(gè)函數(shù)式接口,如果不滿足函數(shù)式接口的條件,編譯器就會(huì)報(bào)錯(cuò)。當(dāng)然,這不是必須的。好處是編譯器幫助檢查問題。
3. 一步步修改為L(zhǎng)ambda表達(dá)式
Lambda式提供了傳遞方法的能力。這種能力首先可以用來處理樣板代碼。比如JDBC連接,比如file讀寫。這些操作會(huì)有try-catcha-finally,但我們更關(guān)心的是中間的部分。那么,是不是可以將中間的部分提取出來,當(dāng)做參數(shù)傳遞進(jìn)來?
3.1 第1步: 行為參數(shù)化
下面是讀一行:
public String read(){ try (BufferedReader br = new BufferedReader(new FileReader("data.txt"))) { return br.readLine(); } catch (IOException e) { e.printStackTrace(); } return null; }
行為參數(shù)化就是把一個(gè)過程行為轉(zhuǎn)換成參數(shù)。在這里就是將br.readLine()
提取成參數(shù)。
3.2 第2步:使用函數(shù)式接口來傳遞行為
定義一個(gè)接口來執(zhí)行上述的行為:
public interface BufferedReaderProcessor{ String process(BufferedReader b) throws IOException; }
然后把這個(gè)接口當(dāng)作參數(shù):
public String read(BufferedReaderProcessor p) throws IOException{ try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){ return p.process(br); } }
3.3 第3步: 傳遞Lambda
@Testpublic void readFile() throws IOException { String oneLine = read(BufferedReader::readLine); String twoLine = read((BufferedReader b) -> b.readLine() + b.readLine()); }
如此,我們就把中間的邏輯抽出來了。把行為抽象成一個(gè)接口調(diào)用,然后通過Lambda來實(shí)現(xiàn)接口的行為。傳遞參數(shù)。完畢。
4. Java API中內(nèi)置的一些函數(shù)式接口
Java API中內(nèi)置了一些很有用的Function接口。
4.1 Predicate
java.util.function.Predicate<T>
定義了一個(gè)抽象方法,返回一個(gè)boolean
。
使用demo如下:
private <T> List<T> filter(List<T> list, Predicate<T> p){ List<T> results = new ArrayList<>(); for (T t : list) { if (p.test(t)){ results.add(t); } } return results; }@Testpublic void testPredicate(){ List<String> list = Arrays.asList("aa","bbb","ccc"); List<String> noEmpty = filter(list, (String s) -> !s.isEmpty()); }
4.2 Consuer
java.util.function.Consumer<T>
定義了一個(gè)抽象方法,接收一個(gè)參數(shù)。
private <T> void forEach(List<T> list, Consumer<T> c){ for (T t : list) { c.accept(t); } }@Testpublic void testConsumer() { List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5); forEach(integers, System.out::println); }
4.3 Function
java.util.function.Function<T,R>
定義了一個(gè)抽象方法,接收一個(gè)參數(shù)T
,返回一個(gè)對(duì)象R
private <T,R> List<R> map(List<T> list, Function<T,R> f){ List<R> result = new ArrayList<>(); for (T t : list) { result.add(f.apply(t)); } return result; }@Testpublic void testFunction(){ List<String> strings = Arrays.asList("a", "bb", "ccc"); List<Integer> lengths = map(strings, String::length); }
4.4 基本類型函數(shù)接口
前面三個(gè)泛型函數(shù)式接口Predicate<T>
、Consumer<T>
、Function<T,R>
,這些接口是專門為引用類型設(shè)計(jì)的。那么基本類型怎么辦?我們知道可以自動(dòng)裝箱嘛。但裝箱是有損耗的。裝箱(boxing)的本質(zhì)是把原始類型包裹起來,并保存在堆里。因此裝箱后的值需要更多的內(nèi)存,并需要額外的內(nèi)存搜索來獲取包裹的原始值。
Java8為函數(shù)式接口帶來了專門的版本。
@Testpublic void testIntPredicate() { //無裝箱 IntPredicate intPredicate = (int t) -> t%2 == 0; boolean isEven = intPredicate.test(100); Assert.assertTrue(isEven); //裝箱 Predicate<Integer> integerPredicate = (Integer i) -> i%2 == 0; boolean isEven2 = integerPredicate.test(100); Assert.assertTrue(isEven2); }
類似的還有:
Java 8中的常用函數(shù)式接口
5. Lambda原理
編譯器可以推斷出方法的參數(shù)類型,由此可以省略一些樣板代碼。
void和其他返回值做了兼容性處理
6. Lambda的局部變量
在Lambda中可以使用局部變量,但要求必須是final的。因?yàn)長(zhǎng)ambda可能在另一個(gè)線程中運(yùn)行,而局部變量是在棧上的,Lambda作為額外的線程會(huì)拷貝一份變量副本。這樣可能會(huì)出現(xiàn)同步問題,因?yàn)橹骶€程的局部變量或許已經(jīng)被回收了?;诖?,必須要求final的。
而實(shí)例變量則沒問題,因?yàn)閷?shí)例變量存儲(chǔ)于堆中,堆是共享的。
7. 方法引用
Lambda表達(dá)式可以用方法引用來表示。比如
(String s) -> s.length() == String::length
這是因?yàn)榭梢酝ㄟ^Lambda表達(dá)式的參數(shù)以及方法來確定一個(gè)方法。在這里,每個(gè)方法都叫做方法簽名
。方法簽名由方法名+參數(shù)列表唯一確定。其實(shí)就是重載的判斷方式。
當(dāng)Lambda的主體只是一個(gè)簡(jiǎn)單的方法調(diào)用的時(shí)候,我們可以直接使用一個(gè)方法引用來代替。方法引用可以知道要接受的參數(shù)類型,以及方法體的邏輯。
方法引用結(jié)構(gòu):類名::方法名
什么可以使用方法引用?
靜態(tài)方法。
指向任意類型實(shí)例方法的方法引用。
指向現(xiàn)有對(duì)象的實(shí)例方法。
8. 構(gòu)造函數(shù)引用
構(gòu)造函數(shù)可以通過類名::new
的方式引用。
9. Lambda實(shí)戰(zhàn)
目標(biāo): 用不同的排序策略給apple排序。
過程: 把一個(gè)原始粗暴的解決方案變得更加簡(jiǎn)單。
資料: 行為參數(shù)化
, 匿名類
,Lambda
, 方法引用
.
最終: inventory.sort(comparing(Apple::getWeight) );
9.1 原始方案
/** * Created by ryan on 7/20/17. */public class AppleSort { private List<Apple> inventory; @Before public void setUp() { inventory = new ArrayList<>(); inventory.add(new Apple("red", 1)); inventory.add(new Apple("red", 3)); inventory.add(new Apple("red", 2)); inventory.add(new Apple("red", 21)); } @Test public void sort_old() { Collections.sort(inventory, new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight() - o2.getWeight(); } }); printApples(); } private void printApples() { inventory.forEach(System.out::println); } }
排序首先要注意的一點(diǎn)就是排序的標(biāo)準(zhǔn)。那么要搞清楚為什么這樣寫?
Comparator定義的其實(shí)就是一個(gè)方法,此處就是將排序的原則抽取出來。特別符合Lambda的思想!這里先不說Lambda,先說這個(gè)方法的作用:定義什么時(shí)候發(fā)生交換。
跟蹤源碼可以發(fā)現(xiàn)這樣一段代碼:
//java.util.Arrays#mergeSort(java.lang.Object[], java.lang.Object[], int, int, int, java.util.Comparator)if (length < INSERTIONSORT_THRESHOLD) { for (int i=low; i<high; i++) for (int j=i; j>low && c.compare(dest[j-1], dest[j])>0; j--) swap(dest, j, j-1); return; }
假設(shè)比較的兩個(gè)數(shù)為o1
和o2
,并且o1
在o2
前一位(left>right)。如下:
....o1,o2...
compare(o1,o2)
的結(jié)果大于0則,o1
和o2
交換。那么,顯然,如果
compare(o1,o2) = o1-o2
則說明,前一個(gè)值比后一個(gè)值大的時(shí)候,發(fā)生交換。也即大的往后冒泡。就是升序了。
所以:
o1-o2
升序o2-o1
降序
9.2 使用List內(nèi)置sort
好消息是Java8提供了sort方法給list:java.util.List#sort
:
則原始方案轉(zhuǎn)換為:
@Testpublic void sort1(){ inventory.sort(new Comparator<Apple>() { @Override public int compare(Apple o1, Apple o2) { return o1.getWeight() - o2.getWeight(); } }); printApples(); }
9.3 Lambda表達(dá)式代替匿名內(nèi)部類
從之前的學(xué)習(xí)可以得到,幾乎所有的匿名內(nèi)部類都可以用Lambda表達(dá)式替代!
inventory.sort((o1, o2) -> o1.getWeight() - o2.getWeight());
9.4 進(jìn)一步優(yōu)化Lambda
Comparator
提供了一個(gè)生成Comparator的方法:
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)); }
其中,Function<T,R>
已經(jīng)在前面學(xué)習(xí)過了,就是一個(gè)接受一個(gè)參數(shù)并返回另一個(gè)參數(shù)的函數(shù)式接口。在本例中,apple.getWeight()
符合接受一個(gè)參數(shù)apple
返回一個(gè)int
。那么,就可以使用這個(gè)方法:
inventory.sort(Comparator.comparing((Apple a)->a.getWeight()));
進(jìn)一步,將Lambda改為方法引用:
inventory.sort(Comparator.comparing(Apple::getWeight));
這里有個(gè)問題,記得之前講的基本類型的自動(dòng)裝箱嗎。Apple::getWeight
的返回值是int
。而comparing
的返回值是一個(gè)對(duì)象。那么,必然要經(jīng)過自動(dòng)裝箱的過程。所以,應(yīng)該使用基本類型的函數(shù)式接口:
inventory.sort(Comparator.comparingInt(Apple::getWeight));
至此,基本已經(jīng)改造完畢了。最多就是靜態(tài)引入comparingInt
方法:
inventory.sort(comparingInt(Apple::getWeight));
目標(biāo)達(dá)到。相比原始方法,不要太簡(jiǎn)潔!
話說,這種是不是只能默認(rèn)升序?因此沒有任何一個(gè)單詞可以看出排序規(guī)則。
是的,想要降序?
inventory.sort(comparingInt(Apple::getWeight).reversed());
So do it,and change it,no regret!
http://www.cnblogs.com/woshimrf/p/java8-lambda.html