JAVA8給我帶了什麼——lambda表達

這此年來我一直從事.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實戰》這本書的例子爲例吧。假設我是一個農戶,家裏種蘋果的。今年大豐收,好多蘋果。我把每個蘋果都打標籤。並把相關蘋果的顏色,重量,大小,品種都記錄到數據庫中。
爲了方便往後的查看,筆者本身想一款軟件。在寫的過其中,筆者但願有這樣子功能——能以顏色來查看相關的蘋果。因此筆者設計一個農民類,他有一個功能——根據顏色查看蘋果。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類express

 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 }

運行結果:app

寫完以後,感受得很完美。過一段時間,忽然發現好像不行。這個功能很差,我須要大小來查看蘋果。因而修改一下,在Peasant類增長一個新的方法。根據大小來查看:ide

 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類:ui

 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類:this

 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.結果只有一個。

相關文章
相關標籤/搜索