jdk8之前,尤其是在寫GUI程式的事件監聽的時候,各種的匿名內部類,大把大把拖沓的程式碼,程式毫無美感可言!既然Java中一切皆為物件,那麼,就類似於某些動態語言一樣,函式也可以當成是物件啊!程式碼塊也可以當成是物件啊!隨著函數語言程式設計的概念越來越深入人心,java中CODE=OBJECT的這一天終於到來了!如果你認為lambda表示式僅僅是為了從語法上簡化匿名內部類,那就太小看jdk8的lambda了!

下面我們就來看下lambda表示式是如何亮瞎你的眼的!

lambda的定義
Funda-men-tally, a lambda expression is just a shorter way of writing an implementation of a method for later execution. 
(1)lambda是方法的實現
(2)lambda是延遲執行的

首先看一個用匿名內部類的例子:

  1. public class Test1{
  2. public static void main(String args[]){
  3. Runnable r = new Runnable(){
  4. public void run(){
  5. System.out.println("hello,lambda!");
  6. }
  7. };
  8. r.run();
  9. }
  10. }

要換成lambda是什麼樣的呢?

  1. public class Test2{
  2. public static void main(String args[]){
  3. Runnable r = ()->System.out.println("hello,lambda");
  4. r.run();
  5. }
  6. }

原先要5行程式碼,現在換成了僅僅1行!
這他媽的得省多少程式碼啊!
有木有很興奮啊!
下面還有更刺激的!

lambda是如何做到的呢?看一下反編譯之後的位元組碼:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=2, args_size=1
  6. 0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
  7. 5: astore_1
  8. 6: aload_1
  9. 7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
  10. 12: return
  11. LineNumberTable:
  12. line 3: 0
  13. line 4: 6
  14. line 5: 12
  15. }

注意:上面有一個叫做invokedynamic的指令。invokedynamic是從jdk7開始引入的,jdk8開始落地。
可以看出來lambda並不是語法糖,它不是像匿名內部類那樣生成那種帶有$的匿名類。
簡單的說,這裡只是定義了一個方法呼叫點,具體呼叫那個方法要到執行時才能決定,這就是前面所說的:延遲執行。
具體的細節請google:invokedynamic。

為了配合lambda,jdk8引入了一個新的定義叫做:函式式介面(Functional interfaces)
函式式介面:
it is an interface that requires exactly one method to be implemented in order to satisfy the requirements of the interface. 
(1)是一個介面
(2)只有一個待實現的方法
因為jdk8開始,介面可以有default方法,所以,函式式介面也是可以有default方法的,但是,只能有一個未實現的方法。

與此對應,新引入了一個註解: @FunctionalInterface
這個註解只是起文件的作用,說明這個介面是函式式介面,編譯器並不會使用這個註解來決定一個介面是不是函式式介面。
不管加不加@FunctionalInterface這個註解,下面的介面都是函式式介面:
interface Something {
  public String doit(Integer i);
}

lambda的語法
A lambda in Java essentially consists of three parts: a parenthesized set of parameters, an arrow, and then a body, 
which can either be a single expression or a block of Java code.
lambda包含3個部分:
(1)括弧包起來的引數
(2)一個箭頭
(3)方法體,可以是單個語句,也可以是語句塊
引數可以寫型別,也可以不寫,jvm很智慧的,它能自己推算出來

  1. public class Test3{
  2. public static void main(String... args) {
  3. Comparator<String> c = (String lhs, String rhs) -> lhs.compareTo(rhs);
  4. int result = c.compare("Hello", "World");
  5. System.out.println(result);
  6. }
  7. }

方法可以有返回,也可以無返回,如果有多個語句,還要返回值,需要加上return

  1. public class Test4{
  2. public static void main(String... args) {
  3. Comparator<String> c =(lhs, rhs) ->{
  4. System.out.println("I am comparing " +lhs + " to " + rhs);
  5. return lhs.compareTo(rhs);
  6. };
  7. int result = c.compare("Hello", "World");
  8. System.out.println(result);
  9. }
  10. }

一個很有意思的事情:
之前我們說Object是一切類的父類,然而在加入了lambda以後,這種大一統的局面將不復存在:

  1. public class Test5{
  2. public static void main(String args[]){
  3. Object r = ()->System.out.println("hello,lambda");
  4. }
  5. }

編譯報錯:
Test5.java:3: error: incompatible types: Object is not a functional interface
                Object r = ()->System.out.println("hello,lambda");
                           ^
1 error

很顯然,編譯器會檢查變數的引用型別裡面是否真的是一個函式式介面。那麼如何讓這段程式碼通過編譯呢?
只需要加一個強制型別轉換就可以了:

  1. public class Test6{
  2. public static void main(String args[]){
  3. Object r = (Runnable)()->System.out.println("hello,lambda");
  4. }
  5. }

lambda的詞法作用域

我們知道,在匿名內部類中:

  1. class Hello {
  2. public Runnable r = new Runnable() {
  3. public void run() {
  4. System.out.println(this);
  5. System.out.println(toString());
  6. }
  7. };
  8. public String toString() {
  9. return "Hello's custom toString()";
  10. }
  11. }
  12. public class InnerClassExamples {
  13. public static void main(String... args) {
  14. Hello h = new Hello();
  15. h.r.run();
  16. }
  17. }

System.out.println(this);這裡的this指的是匿名類,而非Hello類。
想要引用Hello類需要Hello.this這樣:

  1. class Hello {
  2. public Runnable r = new Runnable() {
  3. public void run() {
  4. System.out.println(Hello.this);
  5. System.out.println(Hello.this.toString());
  6. }
  7. };
  8. }

這種做法非常的反人類反直覺!看上去很噁心!
下面我們就來看一下偉大的lambda是什麼樣子的:

  1. class Hello{
  2. public Runnable r = () -> {
  3. System.out.println(this);
  4. System.out.println(toString());
  5. };
  6. public String toString() {
  7. return "Hello's custom toString()";
  8. }
  9. }
  10. public class Test7{
  11. public static void main(String args[]){
  12. Hello h = new Hello();
  13. h.r.run();
  14. }
  15. }

輸出:
Hello's custom toString()
Hello's custom toString()
System.out.println(this);這裡的this指的是Hello,而非lambda表示式!
但是,如果我們想在lambda表示式中返回lambda本身該怎麼做呢?

變數捕獲
匿名內部類只能引用作用域外面的final的變數,在lambda中對這個限制做了削弱,只需要是“等價final”就可以,沒必要用final關鍵字來標識。

  1. public class Test8{
  2. public static void main(String args[]){
  3. String message = "Howdy, world!";//不需要是final的
  4. Runnable r = () -> System.out.println(message);//這裡也能訪問
  5. r.run();
  6. }
  7. }

“等效final”的意思是:事實上的final,所以,一旦賦值也是不可以改變的!比如:

  1. public class Test9{
  2. public static void main(String args[]){
  3. String message = "Howdy, world!";
  4. Runnable r = () -> System.out.println(message);
  5. r.run();
  6. message = "change it";
  7. }
  8. }

Test9.java:4: error: local variables referenced from a lambda expression must be final or effectively final
                Runnable r = () -> System.out.println(message);
                                                      ^
1 error

如果上面的內容看上去平淡無奇的話,真正的大殺器出現了:方法引用
我們有一個這樣的類:

  1. class Person {
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. }

現在我們要把多個Person物件進行排序,有時候是按照firstName來排,有時候是按照lastName或者是age來排,使用lambda可以這樣來做:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. }
  14. public class Test10{
  15. public static void main(String args[]){
  16. Person people[] = new Person[]{
  17. new Person("Ted", "Neward", 41),
  18. new Person("Charlotte", "Neward", 41),
  19. new Person("Michael", "Neward", 19),
  20. new Person("Matthew", "Neward", 13)
  21. };
  22. //sort by firstName
  23. Arrays.sort(people, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
  24. for(Person p : people){
  25. System.out.println(p);
  26. }
  27. }
  28. }

我們可以把Comparator抽取出來,變成是Person物件的成員變數:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. public final static Comparator<Person> compareFirstName =
  14. (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
  15. public final static Comparator<Person> compareLastName =
  16. (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
  17. public final static Comparator<Person> compareAge =
  18. (lhs, rhs) -> lhs.age - rhs.age;
  19. }
  20. public class Test11{
  21. public static void main(String args[]){
  22. Person people[] = new Person[]{
  23. new Person("Ted", "Neward", 41),
  24. new Person("Charlotte", "Neward", 41),
  25. new Person("Michael", "Neward", 19),
  26. new Person("Matthew", "Neward", 13)
  27. };
  28. Arrays.sort(people, Person.compareFirstName);//這裡直接引用lambda
  29. for(Person p : people){
  30. System.out.println(p);
  31. }
  32. }
  33. }

能起到同樣的作用,但是語法看上去很奇怪,因為之前我們都是建立一個滿足Comparator簽名的方法,然後直接呼叫,而非定義一個變數,
然後引用這個變數!所以,還有這麼一種呼叫方法:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. public static int compareFirstName(Person lhs, Person rhs){
  14. return lhs.firstName.compareTo(rhs.firstName);
  15. }
  16. public static int compareLastName(Person lhs, Person rhs){
  17. return lhs.lastName.compareTo(rhs.lastName);
  18. }
  19. }
  20. public class Test12{
  21. public static void main(String args[]){
  22. Person people[] = new Person[]{
  23. new Person("Ted", "Neward", 41),
  24. new Person("Charlotte", "Neward", 41),
  25. new Person("Michael", "Neward", 19),
  26. new Person("Matthew", "Neward", 13)
  27. };
  28. Arrays.sort(people, Person::compareFirstName);
  29. for(Person p : people){
  30. System.out.println(p);
  31. }
  32. }
  33. }

看Person::compareFirstName這種呼叫方式,
如果是static方法使用:類名::方法名
如果是instance方法:instance::方法名

但是,上面的程式碼還是不是很美觀,因為Person只是一個數據物件,它不應該的對外提供compareFirstName或者是compareLastName這樣的方法,
我們需要的僅僅是根據某個欄位排序而已!很幸運的是jdk的api幫我們做了這件事:

  1. import java.util.*;
  2. class Person{
  3. public String firstName;
  4. public String lastName;
  5. public int age;
  6. public Person(String firstName, String lastName, int age){
  7. this.firstName = firstName;
  8. this.lastName = lastName;
  9. this.age = age;
  10. }
  11. public String getFirstName(){
  12. return this.firstName;
  13. }
  14. public String getLastName(){
  15. return this.lastName;
  16. }
  17. public String toString(){
  18. return firstName+","+lastName+","+age;
  19. }
  20. }
  21. public class Test13{
  22. public static void main(String args[]){
  23. Person people[] = new Person[]{
  24. new Person("Ted", "Neward", 41),
  25. new Person("Charlotte", "Neward", 41),
  26. new Person("Michael", "Neward", 19),
  27. new Person("Matthew", "Neward", 13)
  28. };
  29. Arrays.sort(people, Comparator.comparing(Person::getFirstName));
  30. for(Person p : people){
  31. System.out.println(p);
  32. }
  33. }
  34. }

Arrays.sort(people, Comparator.comparing(Person::getFirstName));這裡呼叫了Comparator.comparing方法,
但是注意這裡的Person::getFirstName,很顯然getFirstName()並不是static的,這是jdk做了封裝的緣故!
這樣做就非常完美了!

假如我們的排序演算法改為:先按照lastName,然後按照age排序呢?

  1. public class Test15{
  2. public static void main(String args[]){
  3. Person people[] = new Person[]{
  4. new Person("Ted", "Neward", 10),
  5. new Person("Charlotte", "Neward", 41),
  6. new Person("Michael", "Naward", 19),
  7. new Person("Matthew", "Nmward", 13)
  8. };
  9. Collections.sort(Arrays.asList(people), (lhs, rhs)->{
  10. if(lhs.getLastName().equals(rhs.getLastName())){
  11. return lhs.getAge()-rhs.getAge();
  12. }else{
  13. return lhs.getLastName().compareTo(rhs.getLastName());
  14. }
  15. });
  16. for(Person p : people){
  17. System.out.println(p);
  18. }
  19. }
  20. }

很顯然,應該還有更好的實現方式:

  1. public class Test16{
  2. public static void main(String args[]){
  3. Person people[] = new Person[]{
  4. new Person("Ted", "Neward", 10),
  5. new Person("Charlotte", "Neward", 41),
  6. new Person("Michael", "Naward", 19),
  7. new Person("Matthew", "Nmward", 13)
  8. };
  9. Collections.sort(Arrays.asList(people),Comparator.comparing(Person::getLastName).thenComparing(Person::getAge));
  10. for(Person p : people){
  11. System.out.println(p);
  12. }
  13. }
  14. }

Comparator.comparing(Person::getLastName).thenComparing(Person::getAge):簡直帥呆了!
還有更多的諸如:andThen()這樣的方法,可以查api。

虛方法擴充套件
因為介面可以有default方法,所以很多類庫都重寫了,加入了一些default的方法,比如:
interface Iterator<T> {
  boolean hasNext();
  T next();
  void remove();
  void skip(int i) default {
    for (; i > 0 && hasNext(); i--) next();
  }

skip(i)就是一個default方法,這樣所有的Iterator的子類都具有了一個叫skip的方法!
但是,大家對default方法的爭議還是比較大的,比如:

  1. interface I1 {
  2. public default void print(){
  3. System.out.println("I1");
  4. }
  5. public void hello();
  6. }
  7. interface I2{
  8. public default void print(){
  9. System.out.println("I2");
  10. }
  11. public void world();
  12. }
  13. class Impl implements I1,I2{
  14. public void hello(){
  15. }
  16. public void world(){
  17. }
  18. }

如果在Impl上呼叫print會怎樣呢?這不就是傳說中的多繼承麼?想知道結果的話,自己試一下就可以了,哈哈

Stream:
之前的文章已經有介紹,下面只據一些使用的例子:
過濾age>12的元素:

  1. people
  2. .stream()
  3. .filter(it -> it.getAge() >= 21) ;

過濾age>12的元素,並輸出:

  1. people.stream()
  2. .filter((it) -> it.getAge() >= 21)
  3. .forEach((it) ->
  4. System.out.println("Have a beer, " + it.getFirstName()));

jdk預定義的Predicate:

  1. Predicate<Person> drinkingAge = (it) -> it.getAge() >= 21;
  2. Predicate<Person> brown = (it) -> it.getLastName().equals("Brown");
  3. people.stream()
  4. .filter(drinkingAge.and(brown))
  5. .forEach((it) ->System.out.println("Have a beer, " + it.getFirstName()));

map:

  1. IntStream ages =
  2. people.stream()
  3. .mapToInt((it) -> it.getAge());
  4. //sum:
  5. int sum = people.stream()
  6. .mapToInt(Person::getAge)
  7. .sum();

重點說下reduce:

  1. public class Test17{
  2. public static void main(String args[]){
  3. List<Integer> values = Arrays.asList(1,2,3,4,5);
  4. int sum = values.stream().reduce(0, (l,r)->l+r);
  5. System.out.println(sum);
  6. }
  7. }

reduce(0, (l,r)->l+r)的工作原理是:第一個引數0作為後面lambda表示式的左運算元,然後從stream中取出一個元素作為右運算元,
二者運算的結果作為下一次運算的左運算元,依次迴圈。
最後看一個好玩的例子:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String getFirstName(){
  11. return this.firstName;
  12. }
  13. public String getLastName(){
  14. return this.lastName;
  15. }
  16. public int getAge(){
  17. return this.age;
  18. }
  19. public String toString(){
  20. return firstName+","+lastName+","+age;
  21. }
  22. public String toJson(){
  23. return "{"+
  24. "firstName:\""+firstName+"\","+
  25. "lastName:\""+lastName+"\","+
  26. "age:"+age +
  27. "}";
  28. }
  29. }
  30. public class Test18{
  31. public static void main(String args[]){
  32. Person people[] = new Person[]{
  33. new Person("Ted", "Neward", 10),
  34. new Person("Charlotte", "Neward", 41),
  35. new Person("Michael", "Naward", 19),
  36. new Person("Matthew", "Nmward", 13)
  37. };
  38. String json = Arrays.asList(people).stream().map(Person::toJson).reduce("[",(l,r)->l + (l.equals("[")?"":",") + r)+"]";
  39. System.out.println(json);
  40. }
  41. }

輸出結果:
[{firstName:"Ted",lastName:"Neward",age:10},{firstName:"Charlotte",lastName:"Neward",age:41},{firstName:"Michael",lastName:"Naward",age:19},{firstName:"Matthew",lastName:"Nmward",age:13}]
還可以這樣:

  1. public class Test19{
  2. public static void main(String args[]){
  3. Person people[] = new Person[]{
  4. new Person("Ted", "Neward", 10),
  5. new Person("Charlotte", "Neward", 41),
  6. new Person("Michael", "Naward", 19),
  7. new Person("Matthew", "Nmward", 13)
  8. };
  9. String joined = Arrays.asList(people).stream().map(Person::toJson).collect(Collectors.joining(", "));
  10. System.out.println("[" + joined + "]");
  11. }
  12. }
    1. 更進一步:
    2. public class Test20{
    3. public static void main(String args[]){
    4. Person people[] = new Person[]{
    5. new Person("Ted", "Neward", 10),
    6. new Person("Charlotte", "Neward", 41),
    7. new Person("Michael", "Naward", 19),
    8. new Person("Matthew", "Nmward", 13)
    9. };
    10. String json = Arrays.asList(people).stream().map(Person::toJson).collect(Collectors.joining(", ", "[", "]"));
    11. System.out.println(json);
    12. }
    13. }
    14. 如果只能用一個字來形容,那就是perfect!

      參考:

    15. http://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html
    16. http://www.oracle.com/technetwork/articles/java/architect-lambdas-part2-2081439.html