JDK8新特性 Lambda表達式

1、接口的默認方法
2、Lambda 表達式
3、函數式接口
4、方法與構造函數引用
5、Lambda 做用域
6、訪問局部變量
7、訪問對象字段與靜態變量
8、訪問接口的默認方法
9、Date API
10、Annotation 註解:支持多重註解java

1、接口的默認方法

    Java 8容許咱們給接口添加一個非抽象的方法實現,只須要使用 default關鍵字便可,這個特徵又叫作擴展方法,示例以下:算法

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public interface Formula {  
  2.     double calculate(int a);  
  3.   
  4.     // jdk8能給接口添加一個非抽象的方法實現  
  5.     default double sqrt(int a){  
  6.         return Math.sqrt(a);  
  7.     }  
  8. }  

    Formula接口在擁有calculate方法以外同時還定義了sqrt方法,實現了Formula接口的子類只須要實現一個calculate方法,默認方法sqrt將在子類上能夠直接使用express

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test1() {  
  3.     Formula formula = new Formula() {  
  4.         //這裏只實現了一個方法,沒有實現用default修飾的方法  
  5.         @Override  
  6.         public double calculate(int a) {  
  7.             return sqrt(a * 100);  
  8.         }  
  9.     };  
  10.   
  11.     System.out.println(formula.calculate(100));  //100.0  
  12.     System.out.println(formula.sqrt(100));  //10.0  
  13. }  

    1).若是一個類實現兩個接口,這兩個接口同時有相同的抽象方法,在類中只須要重寫一次這個方法。
    2).若是接口中有default修飾的方法不須要重寫。
    3).若是兩個接口裏的方法名相同都是default方法,裏面的方法體不一樣,在類中須要重寫該方法
    4).若是兩個接口中方法名,參數都相同的方法,一個接口是抽象方法,另外一個是default修飾有方法體。這是該類也必須重寫該方法。
    5).Lambda表達式中是沒法訪問到默認方法的api

 

2、Lambda 表達式

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test2() {  
  3.     // 若是用Lambda表達式,必定要寫明泛型  
  4.     List<String> list = Arrays.asList("peter","anna","make");  
  5.   
  6.     // ①.老版本的Java中是這樣排列字符串的  
  7.       Collections.sort(list, new Comparator<String>() {  
  8.         @Override  
  9.         public int compare(String a, String b) {  
  10.             return a.compareTo(b);  
  11.         }  
  12.     });  
  13.   
  14.   
  15.     // ②.Java 8提供了更簡潔的語法,lambda表達式:  
  16.     /*  
  17.     Collections.sort(list ,(String a,String b) -> {  
  18.         return a.compareTo(b);  
  19.     });  
  20.   
  21.     // ③.還能夠寫得更短  
  22.     Collections.sort(list, (String a, String b) -> a.compareTo(b));  
  23.   
  24.     // ④.還能夠這麼寫  
  25.       Collections.sort(list, String::compareTo);  
  26.   
  27.     System.out.println(Collections.singletonList(list));  //[[anna, make, peter]]  
  28. }  

1. 什麼是λ表達式
λ表達式本質上是一個匿名方法。讓咱們來看下面這個例子:
    public int add(int x, int y) {
        return x + y;
    }
轉成λ表達式後是這個樣子:
    (int x, int y) -> x + y;
參數類型也能夠省略,Java編譯器會根據上下文推斷出來:
    (x, y) -> x + y; //返回兩數之和
或者
    (x, y) -> { return x + y; } //顯式指明返回值
可見λ表達式有三部分組成:參數列表,箭頭(->),以及一個表達式或語句塊。
下面這個例子裏的λ表達式沒有參數,也沒有返回值(至關於一個方法接受0個參數,返回void,其實就是Runnable裏run方法的一個實現):
    () -> { System.out.println("Hello Lambda!"); }
若是隻有一個參數且能夠被Java推斷出類型,那麼參數列表的括號也能夠省略:
    c -> { return c.size(); }jvm

 

lambda包含3個部分:
(1)括弧包起來的參數
(2)一個箭頭
(3)方法體,能夠是單個語句,也能夠是語句塊
參數能夠寫類型,也能夠不寫,jvm很智能的,它能本身推算出來
方法能夠有返回,也能夠無返回,若是有多個語句,還要返回值,須要加上return 。
一個頗有意思的事情:
以前咱們說Object是一切類的父類,然而在加入了lambda之後,這種大一統的局面將不復存在:ide

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test33() {  
  3.     Object r = ()->System.out.println("hello,lambda");  
  4. }  

編譯報錯:incompatible types: Object is not a functional interface
    很顯然,編譯器會檢查變量的引用類型裏面是否真的是一個函數式接口。那麼如何讓這段代碼經過編譯呢?只須要加一個強制類型轉換就能夠了:函數

 

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test33() {  
  3.     Object r = (Runnable)()->System.out.println("hello,lambda");  
  4. }  

 

    若是你認爲lambda表達式僅僅是爲了從語法上簡化匿名內部類,那就過小看jdk8的lambda了!
lambda的定義:
 (1)lambda是方法的實現
 (2)lambda是延遲執行的 this

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public static void main(String[] args) {  
  2.     // 正常實現   
  3.     Runnable runnable = new Runnable() {  
  4.         @Override  
  5.         public void run() {  
  6.             System.out.println("hello lambda!");  
  7.         }  
  8.     };  
  9.   
  10.     runnable.run();  
  11.   
  12.     // 用lambda實現以下(若是run方法體裏只有一行數據能夠省去{)  
  13.     Runnable runnable1 = () -> {  
  14.         System.out.println("hello lambda2!");  
  15.         System.out.println("2");  
  16.     };  
  17.     runnable1.run();  
  18. }  

    lambda是如何作到的呢?能夠反編譯後查看字節碼。(裏面有一個叫作invokedynamic的指令。invokedynamic是從jdk7開始引入的,jdk8開始落地。)能夠看出來lambda並非語法糖,它不是像匿名內部類那樣生成那種帶有$的匿名類。簡單的說,這裏只是定義了一個方法調用點,具體調用那個方法要到運行時才能決定,這就是前面所說的:延遲執行。具體的細節請google:invokedynamic。
    爲了配合lambda,jdk8引入了一個新的定義叫作:函數式接口(Functional interfaces)

google

 

3、函數式接口

(1)是一個接口
(2)只有一個待實現的方法 !!!!
    由於jdk8開始,接口能夠有default方法,因此,函數式接口也是能夠有default方法的,可是,只能有一個未實現的方法。
    與此對應,新引入了一個註解: @FunctionalInterface。這個註解只是起文檔的做用,說明這個接口是函數式接口,編譯器並不會使用這個註解來決定一個接口是否是函數式接口。無論加不加@FunctionalInterface這個註解,下面的接口都是函數式接口:
interface Something {
  public String doit(Integer i);
人工智能

    Lambda表達式是如何在Java的類型系統中表示的呢?每個lambda表達式都對應一個類型,一般是接口類型。而「函數式接口」是指僅僅只包含一個抽象方法的接口,每個該類型的lambda表達式都會被匹配到這個抽象方法。由於默認方法 不算抽象方法,因此你也能夠給你的函數式接口添加默認方法。
    咱們能夠將lambda表達式看成任意只包含一個抽象方法的接口類型,確保你的接口必定達到這個要求,你只須要給你的接口添加 @FunctionalInterface 註解,編譯器若是發現你標註了這個註解的接口有多於一個抽象方法的時候會報錯的。

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @FunctionalInterface  
  2. public interface Converter<E,T> {  
  3.     // 如將字條串轉爲int類型  
  4.     T convert(E str);  
  5.   
  6.     // 函數式接口只能有 一個 抽象方法  
  7. //    T convert2(E str, E sre);  
  8. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1.     @Test  
  2.     public void test3() {  
  3.         Converter<String,Integer> converter = (str) ->Integer.valueOf(str);  
  4.         //上面還能夠經過靜態方法引用來表示:  
  5. //        Converter<String,Integer> converter = Integer::valueOf;  
  6.   
  7.         Integer integer = converter.convert("123");  
  8.         System.out.println(integer);  
  9.     }  

    須要注意若是@FunctionalInterface若是沒有指定,上面的代碼也是對的。
    譯者注 將lambda表達式映射到一個單方法的接口上,這種作法在Java 8以前就有別的語言實現,好比Rhino JavaScript解釋器,

 

5、Lambda 做用域

    在lambda表達式中訪問外層做用域和老版本的匿名對象中的方式很類似。你能夠直接訪問標記了final的外層局部變量,或者實例的字段以及靜態變量。

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class InnerClassExamples {  
  2.     public static void main(String... args) {  
  3.         Hello h = new Hello();  
  4.         h.r.run();  
  5.     }  
  6. }  
  7.   
  8. class Hello {  
  9.     public Runnable r = new Runnable() {  
  10.         public void run() {  
  11.             // 這裏的this指的是匿名類,而非Hello類。  
  12.             System.out.println("-->1 "+this);  
  13.             System.out.println("-->2 "+toString());  
  14.   
  15.             // 想要引用Hello類須要Hello.this這樣!!!  
  16.             System.out.println("++1  "+Hello.this);  
  17.             System.out.println("++2  "+Hello.this.toString());  
  18.         }  
  19.     };  
  20.   
  21.     public String toString() {  
  22.         return "Hello's custom toString()";  
  23.     }  
  24. }  
-->1 com.changwen.jdk8.Hello$1@6d6f6e28
-->2 com.changwen.jdk8.Hello$1@6d6f6e28
++1  Hello's custom toString()
++2  Hello's custom toString()

    System.out.println(this);這裏的this指的是匿名類,而非Hello類。
    想要引用Hello類須要Hello.this這樣:System.out.println(Hello.this);下面咱們就來看一下偉大的lambda是什麼樣子的:

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class Test7{  
  2.     public static void main(String args[]){  
  3.         Hello2 h = new Hello2();  
  4.         h.r.run();  
  5.     }  
  6. }  
  7.   
  8. class Hello2{  
  9.     public Runnable r = () -> {  
  10.         System.out.println(this);  
  11.         System.out.println(toString());  
  12.     };  
  13.   
  14.     public String toString() {  
  15.         return "Hello's custom toString()";  
  16.     }  
  17. }  
Hello's custom toString()
Hello's custom toString()

 

 

6、訪問局部變量

    匿名內部類只能引用做用域外面的final的變量,在lambda中對這個限制作了削弱,只須要是「等價final」就能夠,不必用final關鍵字來標識。
    咱們能夠直接在lambda表達式中訪問外層的局部變量:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public static void main(String args[]){  
  2.     String message = "Howdy, world!";//不須要是final的  
  3.     Runnable runnable = () -> System.out.println(message);  
  4.     runnable.run();  
  5. }  

    「等效final」的意思是:事實上的final,因此,一旦賦值也是不能夠改變的!好比

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public static void main(String args[]){  
  2.     String message = "Howdy, world!";//不須要是final的  
  3.     Runnable runnable = () -> System.out.println(message);  
  4.     runnable.run();  
  5.   
  6.     message = "change it";  
  7. }  

    error: local variables referenced from a lambda expression must be final or effectively final

 

7、方法引用與構造函數引用

真正的大殺器出現了:
    如今咱們要把多個Student對象進行排序,有時候是按照firstName來排,有時候是按照lastName或者是age來排,使用lambda能夠這樣來作:

 

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class Student {  
  2.     public String firstName;  
  3.     public String lastName;  
  4.     public int age;  
  5.   
  6.     public Student (String firstName, String lastName, int age){  
  7.         this.firstName = firstName;  
  8.         this.lastName = lastName;  
  9.         this.age = age;  
  10.     }  
  11.   
  12.     public String toString(){  
  13.         return firstName+","+lastName+","+age;  
  14.     }  
  15. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test12() {  
  3.     Student[] students = new Student[]{  
  4.             new Student("Ted", "Neward", 41),  
  5.             new Student("Charlotte", "Neward", 41),  
  6.             new Student("Michael", "Neward", 19),  
  7.             new Student("Matthew", "Neward", 13)  
  8.     };  
  9.     //sort by firstName  
  10.     Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));  
  11.   
  12.     System.out.println(Arrays.asList(students));  
  13. }  

    咱們能夠把Comparator抽取出來,變成是Student對象的成員變量:

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class Student {  
  2.     public String firstName;  
  3.     public String lastName;  
  4.     public int age;  
  5.   
  6.     public Student (String firstName, String lastName, int age){  
  7.         this.firstName = firstName;  
  8.         this.lastName = lastName;  
  9.         this.age = age;  
  10.     }  
  11.   
  12.     public final static Comparator<Student> compareFirstName =  
  13.             (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);  
  14.   
  15.     public final static Comparator<Student> compareLastName =  
  16.             (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);  
  17.   
  18.     public final static Comparator<Student> compareAge =  
  19.             (lhs, rhs) -> lhs.age - rhs.age;  
  20.   
  21.     public String toString(){  
  22.         return firstName+","+lastName+","+age;  
  23.     }  
  24. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test12() {  
  3.     Student[] students = new Student[]{  
  4.             new Student("Ted", "Neward", 41),  
  5.             new Student("Charlotte", "Neward", 41),  
  6.             new Student("Michael", "Neward", 19),  
  7.             new Student("Matthew", "Neward", 13)  
  8.     };  
  9.     //sort by firstName  
  10.       Arrays.sort(students, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));  
  11.     Arrays.sort(students, Student.compareFirstName);  //這裏直接引用lambda  
  12.     System.out.println(Arrays.asList(students));  
  13. }  

    能起到一樣的做用,可是語法看上去很奇怪,由於以前咱們都是建立一個知足Comparator簽名的方法,而後直接調用,而非定義一個變量,
    而後引用這個變量!因此,還有這麼一種調用方法: -----------------------------------

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class Student {  
  2.     public String firstName;  
  3.     public String lastName;  
  4.     public int age;  
  5.   
  6.     public Student (String firstName, String lastName, int age){  
  7.         this.firstName = firstName;  
  8.         this.lastName = lastName;  
  9.         this.age = age;  
  10.     }  
  11.   
  12.     public static int compareFirstName(Student lhs, Student rhs){  
  13.         return lhs.firstName.compareTo(rhs.firstName);  
  14.     }  
  15.     public static int compareLastName(Student lhs, Student rhs){  
  16.         return lhs.lastName.compareTo(rhs.lastName);  
  17.     }  
  18.   
  19.     public String toString(){  
  20.         return firstName+","+lastName+","+age;  
  21.     }  
  22. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test12() {  
  3.     Student[] students = new Student[]{  
  4.             new Student("Ted", "Neward", 41),  
  5.             new Student("Charlotte", "Neward", 41),  
  6.             new Student("Michael", "Neward", 19),  
  7.             new Student("Matthew", "Neward", 13)  
  8.     };  
  9.     //sort by firstName  
  10.     Arrays.sort(students, Student::compareFirstName);  
  11.     System.out.println(Arrays.asList(students));  
  12. }  

看Student::compareFirstName這種調用方式,
若是是static方法使用:類名::方法名
若是是instance方法:instance::方法名

    可是,上面的代碼仍是不是很美觀,由於Student只是一個數據對象,它不該該的對外提供compareFirstName或者是compareLastName這樣的方法,咱們須要的僅僅是根據某個字段排序而已!很幸運的是jdk的api幫咱們作了這件事:

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class Student {  
  2.     public String firstName;  
  3.     public String lastName;  
  4.     public int age;  
  5.   
  6.     public Student (String firstName, String lastName, int age){  
  7.         this.firstName = firstName;  
  8.         this.lastName = lastName;  
  9.         this.age = age;  
  10.     }  
  11. // get方法  
  12.     public String getFirstName() {  
  13.         return firstName;  
  14.     }  
  15.   
  16.     public String getLastName() {  
  17.         return lastName;  
  18.     }  
  19.   
  20.     public String toString(){  
  21.         return firstName+","+lastName+","+age;  
  22.     }  
  23. }  
[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test12() {  
  3.     Student[] students = new Student[]{  
  4.             new Student("Ted", "Neward", 41),  
  5.             new Student("Charlotte", "Neward", 41),  
  6.             new Student("Michael", "Neward", 19),  
  7.             new Student("Matthew", "Neward", 13)  
  8.     };  
  9.     //sort by firstName  
  10.     Arrays.sort(students, Comparator.comparing(Student::getFirstName));  
  11.     System.out.println(Arrays.asList(students));  
  12. }  

   注意這裏的Person::getFirstName,很顯然getFirstName()並非static的,這是jdk作了封裝的緣故!
    假如咱們的排序算法改成:先按照lastName,而後按照age排序呢?

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1.     @Test  
  2.     public void test12() {  
  3.         Student[] students = new Student[]{  
  4.                 new Student("Ted", "Neward", 41),  
  5.                 new Student("Charlotte", "Neward", 41),  
  6.                 new Student("Michael", "Neward", 19),  
  7.                 new Student("Matthew", "Neward", 13)  
  8.         };  
  9.         //sort by firstName  
  10. /*        Collections.sort(Arrays.asList(students), (lhs, rhs)->{ 
  11.             if(lhs.getLastName().equals(rhs.getLastName())){ 
  12.                 return lhs.getAge()-rhs.getAge(); 
  13.             }else{ 
  14.                 return lhs.getLastName().compareTo(rhs.getLastName()); 
  15.             } 
  16.         }); 
  17. */  
  18.         // 上面能夠改成這樣子  
  19.         Collections.sort(Arrays.asList(students),  
  20.                 Comparator.comparing(Student::getLastName).thenComparing(Student::getAge));  
  21.         System.out.println(Arrays.asList(students));  
  22.     }  

    還有更多的諸如:andThen()這樣的方法,能夠查api。
構造函數引用

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public class Person {  
  2.     String firstName;  
  3.     String lastName;  
  4.   
  5.     public Person() {}  
  6.   
  7.     public Person(String firstName, String lastName) {  
  8.         this.firstName = firstName;  
  9.         this.lastName = lastName;  
  10.     }  
  11. }  

    接下來咱們指定一個用來建立Person對象的對象工廠接口:

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. public interface PersonFactory<P extends Person> {  
  2.     P create(String firstName, String lastName);  
  3. }  

    這裏咱們使用構造函數引用來將他們關聯起來,而不是實現一個完整的工廠:

[java]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. @Test  
  2. public void test4() {  
  3.     //咱們只須要使用 Person::new 來獲取Person類構造函數的引用,  
  4.     // Java編譯器會自動根據PersonFactory.create方法的簽名來選擇合適的構造函數。  
  5.     PersonFactory<Person> personFactory = Person::new;  
  6.     Person person = personFactory.create("Peter", "Parker");  
  7. }  

    咱們只須要使用 Person::new 來獲取Person類構造函數的引用,Java編譯器會自動根據PersonFactory.create方法的簽名來選擇合適的構造函數。


8、訪問對象字段與靜態變量

    和本地變量不一樣的是,lambda內部對於實例的字段以及靜態變量是便可讀又可寫。該行爲和匿名對象是一致的:

[plain]  view plain  copy
 
 在CODE上查看代碼片派生到個人代碼片
  1. class Lambda4 {  
  2.     static int outerStaticNum;  
  3.     int outerNum;  
  4.   
  5.     void testScopes() {  
  6.         Converter<Integer, String> stringConverter1 = (from) -> {  
  7.             outerNum = 23;  
  8.             return String.valueOf(from);  
  9.         };  
  10.   
  11.         Converter<Integer, String> stringConverter2 = (from) -> {  
  12.             outerStaticNum = 72;  
  13.             return String.valueOf(from);  
  14.         };  
  15.     }  
  16. }  
相關文章
相關標籤/搜索