前幾天電腦上的JDK自動更新到1.8,這兩天了解了一下JDK1.8的一些新特性。下面就來淺談一下我所了解的。
我們都知道,Java是不能多繼承的,但是可以多實現。它與C++不同,C++是可以多繼承的。盡管Java可以實現多個接口,但是接口中只能有抽象方法,不能有具體的實現。但是在JDK1.8中,接口里新增了默認方法。可以對默認方法進行具體的實現,但是這個方法必須是default。看下面這段代碼:
1 interface AA{ 2 public int add(int a,int b); //申明方法 3 default void sing(String name){ //默認方法 4 System.out.println("The song name is " + name); 5 } 6 } 7 8 public class CC implements AA{ 9 public static void main(String[] args){ 10 AA aa = new AA(){ //匿名內部類 11 public int add(int a,int b){ 12 return (a + b); 13 } 14 }; 15 aa.sing("The Bird And The Bee"); //方法調用 16 System.out.println(aa.add(5,3)); 17 } 18 }
在接口AA中,我們有兩個方法,並且有一個默認方法。在main函數中,我們對AA中的add方法進行實現,之后調用AA中的兩個方法,這段代碼是正確的。現在來想一個問題,既然現在接口中可以有方法的實現,而且接口又是可以多繼承的,那么如果兩個接口中有同樣的方法簽名的兩個方法,那么在實現這兩個接口的實現類中,會有沖突嗎?
接口C繼承了接口A和B,而在A,B中有相同方法簽名的函數。注:函數的方法簽名包括:函數名,參數個數,參數類型以及順序,不包括返回值。代碼如下:
1 interface A1{ 2 default int add(int a,int b){ 3 return (a + b); 4 } 5 default void say(){ 6 System.out.println("Hello,I am A1"); 7 } 8 } 9 10 interface B1{ 11 default int subtraction(int a,int b){ 12 return (a - b); 13 } 14 default void say(){ 15 System.out.println("Hello,I am B1"); 16 } 17 } 18 19 interface C1 extends A1,B1{}
上面這段代碼在編譯時候會出現錯誤。
之后我們稍稍修改一下我們的接口C1,讓它覆蓋父接口的say()方法。並完善我們代碼:
1 interface A1{ 2 default int add(int a,int b){ 3 return (a + b); 4 } 5 default void say(){ 6 System.out.println("Hello,I am A1"); 7 } 8 } 9 10 interface B1{ 11 default int subtraction(int a,int b){ 12 return (a - b); 13 } 14 default void say(){ 15 System.out.println("Hello,I am B1"); 16 } 17 } 18 19 interface C1 extends A1,B1{ 20 @Override 21 default void say(){ 22 //B1.super.say(); 指定調用父接口中的say() 23 System.out.println("Hello,I am C1"); //有自己的實現 24 } 25 } 26 27 public class Test implements C1{ 28 public static void main(String[] args){ 29 Test t = new Test(); 30 t.say(); 31 System.out.println(t.add(3,5)); 32 System.out.println(t.subtraction(5,2)); 33 } 34 }
我們使用覆蓋父類的方式來避免上面錯出現的沖突。在覆蓋方法中,我們可以有自己的實現,也可以指定實現某個父接口中的方法。好,解決了這個問題,我們來繼續思考另外一個問題,如果一個子類繼承一個基類並實現了一個接口,並且不幸的事,基類中有一個方法是接口中的默認方法,此時又會出現沖突嗎?
代碼如下:
1 interface A1{ 2 default int add(int a,int b){ 3 System.out.println("add() in interface A1 ---> "); 4 return (a + b); 5 } 6 default void say(){ 7 System.out.println("Hello,I am interface A1"); 8 } 9 } 10 11 class A2{ 12 public int add(int a,int b){ 13 System.out.print("add() in class A2 ---> "); 14 return (a + b); 15 } 16 public void say(){ 17 System.out.println("Hello,I am class A2"); 18 } 19 } 20 21 public class Test extends A2 implements A1{ 22 public static void main(String[] args){ 23 Test t = new Test(); 24 t.say(); 25 System.out.println(t.add(3,5)); 26 } 27 }
運行結果如下:
從結果可以看出,當基類和接口中的默認方法具有相同方法簽名的函數時,子類優先繼承基類中的函數實現,而不是父接口中的默認方法。如果父類沒有相同的方法簽名,子類才會繼承接口中的默認方法。說了這么多關於默認方法,想一想JDK1.8為什么要新增加這一特性呢?由於Collection庫需要為批處理操作添加新的方法,如forEach(),stream()等,但是不能修改現有的Collection接口——如果那樣做的話所有的實現類都要進行修改,包括很多客戶自制的實現類。拿forEach()方法來說,以前我們遍歷一個集合的時候,都是自己寫遍歷操作的代碼,相信下面這段代碼我們都是很熟悉的:
1 import java.util.*; 2 3 public class Newlist{ 4 public static void main(String[] args){ 5 List<String> strList = new ArrayList<String>(); 6 strList.add("123"); 7 strList.add("234"); 8 strList.add("345"); 9 strList.add("456"); 10 strList.add("567"); 11 12 for(String list : strList){ 13 System.out.println(list); 14 } 15 } 16 }
但是現在不必要這么麻煩了,我們可以直接使用現成的方法進行遍歷。使用下面這行代碼,代替上面的for循環,即可。
list.forEach(o->{System.out.println(o);});
forEach()方法在List接口中就是一個默認方法,所以所有實現了這個接口,繼承這接口的類或接口都具有這個方法,這就是新增默認方法的原因。
這行代碼又引出了一個新特性-->lambda表達式。lambda表達式就是一個匿名函數,主要用於替換以前廣泛使用的內部匿名類,各種回調,比如事件響應器、傳入Thread類的Runnable等。Java8有一個短期目標和一個長期目標。短期目標是:配合“集合類批處理操作”的內部迭代和並行處理(上面將已經講到);長期目標是將Java向函數式編程語言這個方向引導(並不是要完全變成一門函數式編程語言,只是讓它有更多的函數式編程語言的特性,關於函數式編程語言和命令式編程語言請參看這篇博客http://blog.jobbole.com/42178/),也正是由於這個原因,Oracle並沒有簡單地使用內部類去實現λ表達式,而是使用了一種更動態、更靈活、易於將來擴展和改變的策略(invokedynamic)。下面舉個簡單例子了解lambda表達式:
1 interface A{ //函數接口 2 public int add(int a,int b); 3 boolean equals(Object obj); 4 default void say(){ 5 System.out.println("Hello,I am A"); 6 } 7 } 8 public class Lambda implements A{ 9 public static void main(String[] args){ 10 /* A a = new A(){ 匿名內部類實現add() 11 public int add(int a,int b){ 12 return (a+b); 13 } 14 }; */ 15 A a = (x,y)->{return (x+y);}; 16 a.say(); 17 System.out.println(a.add(5,3)); 18 } 19 }
在上面的main中我們使用了lambda表示式對接口A中add()方法進行了實現,上面注釋是我們不使用表達式而是使用匿名內部類去實現接口中的非默認方法。可以看出,λ表達式是由(參數)、->、{}三部分構成的。左邊是接口定義方法的參數,可以省略參數類型,並且只有當方法僅唯一參數時,小括號才能省略,沒有參數應該寫成()。表達式右邊是代碼塊,也就是我們平時書寫函數體中的內容。請注意:能夠使用λ表達式的目標類型必須是函數接口,函數接口的定義是:一個接口,如果只有一個顯式聲明的抽象方法,那么它就是一個函數接口。一般用@FunctionalInterface標注出來(也可以不標)。說的簡單一點就是在一個接口中只有一個顯示申明的函數。在上面的例子中,我們的接口A就是函數接口,因為只有一個顯示申明的add()。你可能疑惑,那equals方法呢?equals方法是Object的,所有的接口都會聲明Object的public方法——它是隱式的。下面舉一個線程的例子:
1 public class Lambda{ 2 public static void main(String[] args){ 3 Thread tt = new Thread(new Runnable(){ //一般做法 4 public void run(){ 5 for(int i=0;i<100;i++){ 6 System.out.println("normal function!"); 7 } 8 } 9 }); 10 11 Thread tt = new Thread(()->{ //使用lambda表示式 12 for(int i=0;i<100;i++){ 13 System.out.println("lambda expression!"); 14 } 15 }); 16 tt.start(); 17 } 18 }
JDK1.8有很多新特性,我列出的只是很小一部分,作為Java語言忠實的粉絲,感覺學習Java越來越有趣。
參考博客:http://blog.csdn.net/ioriogami/article/details/12782141