這此年來我一直從事.NET的開發。對於JAVA我內心深處還是很向往的。當然這並不是說我不喜歡.NET。只是覺得JAVA也許才是筆者最后的歸處。
MK公司是以.NET起家的。而筆者也因為兄弟的原因轉行.NET。雖然有時候還是會拿起JAVA相關的知識回味一下。盡可能的不讓自己忘記。但是時代的進步卻把我狠狠甩到了后面去。
現在筆者終於離開了M公司。我想回去做JAVA,卻發現筆者已經跟不上JAVA時候。在筆者轉行.NET的時候,JAVA的版本才到 1.6。現在都1.8了。主要的是這個段時間發現很大的變化。所以就想看看JAVA8底能帶給我什么。
筆者回來做JAVA就是想知道的第一件事——JAVA8里面有什么。不知道Oracle公司收了Sun公司之后為什么一直沒有動作,在加上筆者忙着搞.NET開發。JAVA的事情就失去了信息,對於JAVA7筆者不是沒有感知,主要是JAVA8聽說變化很大。可以說是一個大版本的變化。所以筆者回來的時候就想知道——JAVA8里面有什么。同時筆者在這里聲明這一系列主要是記得筆者自身從JAVA8得到了什么,如果有要學JAVA8同學,本系列只能作參考。
筆者是從事.NET的開發,相對JAVA以前而言。.NET有一些功能真的不錯。lambda表達示可以說成為.NET開發人員不可能離開的一部分。以前的JAVA可是沒有這個功能的。.NET可以把一個方法當做一個參數和變量來賦值。JAVA在這一塊就弱了很多了。所以JAVA很多時候在設計模式上面做很大的體現。
把一個方法函數當前一個參數和變量來用的行為我們稱為行為參數化。那么他有什么好處呢?策略模式相信大家可能都聽過。不如筆者就以《JAVA實戰》這本書的例子為例吧。假設我是一個農戶,家里種蘋果的。今年大豐收,好多蘋果。我把每一個蘋果都打標簽。並把相關蘋果的顏色,重量,大小,品種都記錄到數據庫中。
為了方便日后的查看,筆者自己想一款軟件。在寫的過其中,筆者希望有這樣子功能——能以顏色來查看相關的蘋果。所以筆者設計一個農民類,他有一個功能——根據顏色查看蘋果。
Apple類:
1 package com.aomi; 2 3 public class Apple { 4 private String color; 5 private double weight; 6 private String typeName; 7 private int size; 8 9 public String getColor() { 10 return color; 11 } 12 13 public void setColor(String color) { 14 this.color = color; 15 } 16 17 public double getWeight() { 18 return weight; 19 } 20 21 public void setWeight(double weight) { 22 this.weight = weight; 23 } 24 25 public String getTypeName() { 26 return typeName; 27 } 28 29 public void setTypeName(String typeName) { 30 this.typeName = typeName; 31 } 32 33 public int getSize() { 34 return size; 35 } 36 37 public void setSize(int size) { 38 this.size = size; 39 } 40 41 @Override 42 public String toString() { 43 return "Apple [color=" + color + ", weight=" + weight + ", typeName=" + typeName + ", size=" + size + "]"; 44 } 45 46 47 48 }
Peasant類
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Peasant { 7 8 public List<Apple> GetApplesByColor(List<Apple> sources, String color) { 9 10 List<Apple> suiteApples = new ArrayList<>(); 11 12 for (Apple apple : sources) { 13 14 if (apple.getColor().equals(color)) 15 suiteApples.add(apple); 16 17 } 18 19 return suiteApples; 20 } 21 }
Main:
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Main { 7 8 public static void main(String[] args) { 9 // TODO Auto-generated method stub 10 11 // 查找紅色的蘋果 12 13 Peasant peasant = new Peasant(); 14 15 List<Apple> rApples = peasant.GetApplesByColor(getSources(), "red"); 16 17 for (Apple apple : rApples) { 18 System.out.println(apple.toString()); 19 } 20 21 } 22 23 public static List<Apple> getSources() { 24 25 List<Apple> sources = new ArrayList<>(); 26 Apple apple1 = new Apple(); 27 28 apple1.setColor("red"); 29 apple1.setTypeName("hot"); 30 apple1.setSize(12); 31 apple1.setWeight(34.2); 32 Apple apple2 = new Apple(); 33 34 apple2.setColor("grayred"); 35 apple2.setTypeName("hot"); 36 apple2.setSize(12); 37 apple2.setWeight(34.2); 38 39 Apple apple3 = new Apple(); 40 41 apple3.setColor("green"); 42 apple3.setTypeName("hot"); 43 apple3.setSize(12); 44 apple3.setWeight(34.2); 45 46 sources.add(apple1); 47 sources.add(apple2); 48 sources.add(apple3); 49 50 return sources; 51 } 52 53 }
運行結果:

寫完之后,感覺得很完美。過一段時間,突然發現好像不行。這個功能不好,我需要大小來查看蘋果。於是修改一下,在Peasant類增加一個新的方法。根據大小來查看:
1 public List<Apple> GetApplesBySize(List<Apple> sources, int size) { 2 3 List<Apple> suiteApples = new ArrayList<>(); 4 5 for (Apple apple : sources) { 6 7 if (apple.getSize() > size) 8 suiteApples.add(apple); 9 10 } 11 12 return suiteApples; 13 }
好吧。看起來也不錯,那么有沒有想過后面還有可能會以重量來查看蘋果。只能在加一個方法了。那么問題來了。一但功能多。整類會看起來一個點復雜。理解有一點難度。在沒有lambda表達的時候,JAVA會用一下有一點策略模式的方式實現。把相關的比較操作變成一個類。如下
Lookup接口類:
1 package com.aomi; 2 3 public interface Lookup { 4 boolean handle(Apple apple); 5 }
ColorRedLookup類:
1 package com.aomi; 2 3 public class ColorRedLookup implements Lookup { 4 5 @Override 6 public boolean handle(Apple apple) { 7 // TODO Auto-generated method stub 8 return apple.equals("red"); 9 } 10 11 }
SizeLookup類:
1 package com.aomi; 2 3 public class SizeLookup implements Lookup { 4 5 @Override 6 public boolean handle(Apple apple) { 7 // TODO Auto-generated method stub 8 return apple.getSize() > 120; 9 } 10 11 }
Peasant類:
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Peasant { 7 8 public List<Apple> LookupApple(List<Apple> sources, Lookup lookup) { 9 10 List<Apple> suiteApples = new ArrayList<>(); 11 12 for (Apple apple : sources) { 13 14 if (lookup.handle(apple)) 15 suiteApples.add(apple); 16 17 } 18 19 return suiteApples; 20 } 21 }
Main:
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Main { 7 8 public static void main(String[] args) { 9 // TODO Auto-generated method stub 10 11 // 查找紅色的蘋果 12 13 Peasant peasant = new Peasant(); 14 15 List<Apple> rApples = peasant.LookupApple(getSources(), new ColorRedLookup()); 16 17 for (Apple apple : rApples) { 18 System.out.println(apple.toString()); 19 } 20 21 } 22 23 public static List<Apple> getSources() { 24 25 List<Apple> sources = new ArrayList<>(); 26 Apple apple1 = new Apple(); 27 28 apple1.setColor("red"); 29 apple1.setTypeName("hot"); 30 apple1.setSize(12); 31 apple1.setWeight(34.2); 32 Apple apple2 = new Apple(); 33 34 apple2.setColor("grayred"); 35 apple2.setTypeName("hot"); 36 apple2.setSize(12); 37 apple2.setWeight(34.2); 38 39 Apple apple3 = new Apple(); 40 41 apple3.setColor("green"); 42 apple3.setTypeName("hot"); 43 apple3.setSize(12); 44 apple3.setWeight(34.2); 45 46 sources.add(apple1); 47 sources.add(apple2); 48 sources.add(apple3); 49 50 return sources; 51 } 52 53 }
上面的這種從某些方面來講筆者不是很喜歡。雖然這種方式看起來會比較人性化。但是相比筆者還是喜歡前面那一種增加方法的。這是個人的想法。
List<Apple> rApples = peasant.LookupApple(getSources(), new ColorRedLookup());
看完這段代碼之后我們就可以發現一個問題。是不是每一個條件查找我都要建一個類呢?好像不是很好玩了。所以還是試一下我們試一下lambda表達。上面的代碼不用修改太多。只要main方法里面就可以了。
1 public static void main(String[] args) { 2 // TODO Auto-generated method stub 3 4 // 查找紅色的蘋果 5 6 Peasant peasant = new Peasant(); 7 8 List<Apple> rApples = peasant.LookupApple(getSources(), (Apple apple) -> apple.getColor().equals("red")); 9 10 for (Apple apple : rApples) { 11 System.out.println(apple.toString()); 12 } 13 14 rApples = peasant.LookupApple(getSources(), (Apple apple) -> apple.getSize() > 120); 15 16 for (Apple apple : rApples) { 17 System.out.println(apple.toString()); 18 } 19 20 }
是不是非常簡單呢。不用在建什么類了。所以lambda表達的好處很明顯的。筆者想要什么查找規則只要改變一下規則就行了。如下用大小來查找。
List<Apple> rApples = peasant.LookupApple(getSources(), (Apple apple) -> apple.getSize() > 12);
lambda表達給人感覺就是一個縮小版本的方法。往后面看的話,這種感覺你們會變的更加。但是在學習ambda表達的時候,有一些細節點還是要注意的。
- 語法點:(parameters)->expression或是(parameters)->{statements;}
- 學習會查看lambda表達的簽名。即稱函數描述符
從語法點我們可以知道
左邊parameters:是表示參數,就好比如方法函數的參數是一樣子的。
中間->:是固定的。
右邊expression或是{statements;}:是表示只能接受表達式,或是加大括號的語句。稱為主體
舉一些例子來加強一下吧
1.()-> {}//有效
2.()->"aomi"//有效
3.()->{return "aomi";}//有效
4.()->return "aomi "+ 1;//無效,主體是語句,要加上{}
5.()->{"aomi";}//跟上面的相反,主體是表達式,去掉{}
說到lambda表達的簽名,這邊就不得不提到一個概念函數式接口。他的定義是這樣子,只要接口里面只有一個抽象方法都是可以算是函數式接口。舉一個JAVA是里面的函數式接口
1 package java.lang; 2 3 @FunctionalInterface 4 public interface Runnable { 5 /** 6 * When an object implementing interface <code>Runnable</code> is used 7 * to create a thread, starting the thread causes the object's 8 * <code>run</code> method to be called in that separately executing 9 * thread. 10 * <p> 11 * The general contract of the method <code>run</code> is that it may 12 * take any action whatsoever. 13 * 14 * @see java.lang.Thread#run() 15 */ 16 public abstract void run(); 17 }
Runnable就是一個函數式接口。他只有一個run抽象方法。上面有一個注解類@FunctionalInterface他就是用於說明當前類是一個函數式接口。不過好像事實上你可以不用加上他。
AomiRunnable類:
1 package com.aomi; 2 3 public interface AomiRunnable { 4 5 void run(); 6 7 }
Main:
1 public class Main { 2 3 public static void main(String[] args) { 4 Runnable run = () -> { 5 System.out.println("i am runnble"); 6 }; 7 8 AomiRunnable aRun = () -> { 9 System.out.println("i am aomirunnble"); 10 }; 11 12 run.run(); 13 14 aRun.run(); 15 16 } 17 }
運行結果:

看到上面的代碼不要奇怪。這個正好可以說明lambda表達的神奇之處。我們可以看到筆者定義了倆個函數式接口的變量。一個是JAVA里面自帶的,一個是筆者自己寫的。倆個都可以正常的運行。可是筆者自已寫的好像沒有加入@FunctionalInterface。那是不是@FunctionalInterface沒有用呢?那還是有的。看下面就知道了。當你寫錯了就會提示你寫的不是函數式接口。

所以還是加上吧。這樣子顯得也專業一點嗎?
有了函數式接口,就必須說一下函數描述符。他事實上就是lambda表達的簽名。他是從哪里來的呢?很簡單的,看接口的唯一方法就行了。就好例如上面Runnable類,他的方法就是無參數,無返回值。你可寫才這樣子表示一下:()->{}。為什么要有函數描述符呢?你們可以這樣子理解。JAVA里面有很多自己寫好的函數式接口。如果你沒有函數描述符的話,你又何如明白什么時候用到哪一個呢?如下
- Predicate類:T -> boolean
- Function類:T ->R
看到上面函數描述符的話,你是不是就可以知道他們的用法呢。所以了解函數描述符的話,你就可以很清楚的明白自己要用JAVA里面的哪個函數式接口。同時還可以提高你在寫代碼過程的速度。目前JAVA里面有哪一些函數式接呢?自己去看吧。在rt.jar里面的

還是讓筆者再舉個例子吧。筆者希望按蘋果的大小來非序。所以我們可一定要用到List類的sort方法了。sort方法里面以Comparator類作為參數。Comparator類是一個函數式接口。抽象方法如下
int compare(T o1, T o2);
所以函數描述符是(T,T)-> int。知道這些之后就好辦了。
1 public static void main(String[] args) { 2 3 List<Apple> apples = getSources(); 4 5 apples.sort((Apple a1, Apple a2) -> a2.getSize() - a1.getSize()); 6 7 for (Apple apple : apples) { 8 9 System.out.println(apple); 10 } 11 12 }
運行結果:

看起很方便吧。
lambda表達的確不錯。使得用JAVA8開發的同學代碼更加的人性化。但是JAVA8還加入另一種功能叫方法引用。看一下例子。
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class Main { 7 8 public static void main(String[] args) { 9 10 List<Apple> apples = getSources(); 11 12 apples.sort(Main::AppleComparator); 13 14 for (Apple apple : apples) { 15 16 System.out.println(apple); 17 } 18 19 } 20 21 public static int AppleComparator(Apple a1, Apple a2) { 22 return a2.getSize() - a1.getSize(); 23 } 24 25 public static List<Apple> getSources() { 26 27 List<Apple> sources = new ArrayList<>(); 28 Apple apple1 = new Apple(); 29 30 apple1.setColor("red"); 31 apple1.setTypeName("hot"); 32 apple1.setSize(13); 33 apple1.setWeight(34.2); 34 Apple apple2 = new Apple(); 35 36 apple2.setColor("grayred"); 37 apple2.setTypeName("hot"); 38 apple2.setSize(12); 39 apple2.setWeight(34.2); 40 41 Apple apple3 = new Apple(); 42 43 apple3.setColor("green"); 44 apple3.setTypeName("hot"); 45 apple3.setSize(14); 46 apple3.setWeight(34.2); 47 48 sources.add(apple1); 49 sources.add(apple2); 50 sources.add(apple3); 51 52 return sources; 53 } 54 55 }
主要修改的地方:
apples.sort(Main::AppleComparator);
增加的地方:
1 public static int AppleComparator(Apple a1, Apple a2) { 2 return a2.getSize() - a1.getSize(); 3 }
在使用方法引用的時候,要注要一點,好像要靜態方法才行。如果不的話。會報錯的。

讓筆者好好說明下吧。方法引用並不是可以隨便寫的。他是有依據的。總共有三種:
- 靜態方法,必須要符合(arg)->ClassName.staticMehtod(arg).的格式。
- 任意類型的實例方法,必須符合(object,rest)->object.instanceMethod(rest)的格式。
- 對象實例的實例方法,必須符合(args)->obj.instanceMethod(args)的格式。
我們可以看到方法引用就是針於單一方法的lambda表達的。 我們都知道Function函數接口的lambda表達的用法。好!假設筆者在Apple類中加入這樣子的方法
1 public int testApple(Apple a) { 2 return a.getSize() - 100; 3 }
然后在Main的代碼中是這樣子寫的。
Function<Apple, Integer> fun = Apple::testApple;
不好意思他會報錯。

讓我們看一下Function的函數描述符吧。(T)-> R.好像跟testApple方法是一樣子的話,那為什么不行呢? 讓我們把Function<Apple,Integer>變成為他等同的一個lambda表達的寫吧。
(Apple a) -> 123//123可以是任意的數字。
跟筆者上面說的三點都不符合,當然不行了。所以想要可行的話,必須把testApple方法變成靜態的。這樣子就合適第一種了。關於比較Comparator類,JAVA8提供了一個comparing靜態方法。他接受了一個Function參數。並返回一個Comparator類對象。修改一下。
apples.sort(comparing((Apple a) -> a.getSize()));
記得一個要引入
import static java.util.Comparator.comparing;
又因為方法引用的關系
(Apple a) -> a.getSize() 等於 Apple::getSize()
我們就可以把他修改為
apples.sort(comparing(Apple::getSize));
筆者用一個以前的例子吧。排序蘋果Main的類全部代碼
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import static java.util.Comparator.comparing; 6 7 public class Main { 8 9 public static void main(String[] args) { 10 11 12 List<Apple> apples = getSources(); 13 14 apples.sort(comparing(Apple::getSize)); 15 16 for (Apple apple : apples) { 17 18 System.out.println(apple); 19 } 20 21 } 22 23 public static int AppleComparator(Apple a1, Apple a2) { 24 return a2.getSize() - a1.getSize(); 25 } 26 27 public static List<Apple> getSources() { 28 29 List<Apple> sources = new ArrayList<>(); 30 Apple apple1 = new Apple(); 31 32 apple1.setColor("red"); 33 apple1.setTypeName("hot"); 34 apple1.setSize(13); 35 apple1.setWeight(34.2); 36 Apple apple2 = new Apple(); 37 38 apple2.setColor("grayred"); 39 apple2.setTypeName("hot"); 40 apple2.setSize(12); 41 apple2.setWeight(34.2); 42 43 Apple apple3 = new Apple(); 44 45 apple3.setColor("green"); 46 apple3.setTypeName("hot"); 47 apple3.setSize(14); 48 apple3.setWeight(34.2); 49 50 sources.add(apple1); 51 sources.add(apple2); 52 sources.add(apple3); 53 54 return sources; 55 } 56 57 }
運行結果:

有了方法引用之后,在有一種叫構造引用的話,相信大家都不會有什么吃驚的地方了。
Supplier<Apple> app = Apple::new;
等於
Supplier<Apple> app = ()->new Apple();
筆者就不多講了。
關於lambda表達的知識大部分是這樣子。筆者說實話吧。JAVA8加入lambda表達讓筆者一定也沒有感到興奮。因為.NET那邊都寫爛了。至少上面講到的知識讓筆者沒有什么新鮮感。到是方法引用有一點味。但是下面的知識點卻讓筆者提了一點興趣了。
復合lambda表達。什么意思!就是把多個lambda表達用or或and的概念放到一起使用。好比如上面的排序例子。可以反序的。修改下面的代碼
apples.sort(comparing(Apple::getSize).reversed());
加上.reversed()之后

沒有加之前

還有哦,還可以修改為
apples.sort(comparing(Apple::getSize).reversed().thenComparing(Apple::getWeight));
一個排序條件不夠,可以加哦。
讓我們換另外一些方式來看看吧。
Main類:
public static void main(String[] args) { Function<Integer, Integer> add = (Integer a) -> a + 2; Function<Integer, Integer> multiply = (Integer a) -> a * 4; Function<Integer, Integer> andThen = add.andThen(multiply); Function<Integer, Integer> compose = add.compose(multiply); System.out.println("andThen結果:" + andThen.apply(2)); System.out.println("compose結果:" + compose.apply(2)); }
運行結果:

這個結果說明一個問題
- andThen是multiply (add(x))。先執行了add,然后在multiply
- compose是add(multiply(x))。先執行了multiply ,然后在add
對於andThen比較好理解。筆者不喜歡的是compose。為什么?一般開發人員喜歡看其名知其意。compose的英文意思是構成,寫作,還有組成的意思。有一點難理解。
讓我們在看一個奇神的點吧。
1 package com.aomi; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 import java.util.function.Predicate; 6 7 public class Main { 8 9 public static void main(String[] args) { 10 11 Predicate<Apple> query = (Apple a) -> a.getSize() > 13; 12 13 query = query.and((Apple a) -> a.getWeight() < 20); 14 15 List<Apple> apples = fliter(query); 16 17 for (Apple apple : apples) { 18 19 System.out.println(apple); 20 } 21 22 } 23 24 public static List<Apple> fliter(Predicate<Apple> pred) { 25 List<Apple> nSources = new ArrayList<>(); 26 List<Apple> sources = getSources(); 27 for (Apple apple : sources) { 28 if (pred.test(apple)) 29 nSources.add(apple); 30 } 31 return nSources; 32 } 33 34 public static List<Apple> getSources() { 35 36 List<Apple> sources = new ArrayList<>(); 37 Apple apple1 = new Apple(); 38 39 apple1.setColor("red"); 40 apple1.setTypeName("hot"); 41 apple1.setSize(13); 42 apple1.setWeight(55.2); 43 Apple apple2 = new Apple(); 44 45 apple2.setColor("grayred"); 46 apple2.setTypeName("hot"); 47 apple2.setSize(12); 48 apple2.setWeight(34.2); 49 50 Apple apple3 = new Apple(); 51 52 apple3.setColor("green"); 53 apple3.setTypeName("hot"); 54 apple3.setSize(14); 55 apple3.setWeight(34.2); 56 57 Apple apple4 = new Apple(); 58 59 apple4.setColor("green"); 60 apple4.setTypeName("hot"); 61 apple4.setSize(19); 62 apple4.setWeight(12.2); 63 64 sources.add(apple1); 65 sources.add(apple2); 66 sources.add(apple3); 67 sources.add(apple4); 68 69 return sources; 70 } 71 72 }
本來集合時面有四個蘋果,大小值大於13的有倆個蘋果。所以當我們把條件用and在加上的時候—— 重量小於20.結果只有一個。

