Java 8 引入的一個核心概念是函數式接口(Functional Interfaces)。經過在接口裏面添加一個抽象方法,這些方法能夠直接從接口中運行。若是一個接口定義個惟一一個抽象方法,那麼這個接口就成爲函數式接口。同時,引入了一個新的註解:@FunctionalInterface。能夠把他它放在一個接口前,表示這個接口是一個函數式接口。這個註解是非必須的,只要接口只包含一個方法的接口,虛擬機會自動判斷,不過最好在接口上使用註解 @FunctionalInterface 進行聲明。在接口中添加了 @FunctionalInterface 的接口,只容許有一個抽象方法,不然編譯器也會報錯,能夠擁有若干個默認方法。html
java.lang.Runnable 就是一個函數式接口。 @FunctionalInterface public interface Runnable { public abstract void run(); }
Java中的lambda沒法單獨出現,它須要一個函數式接口來盛放,lambda表達式方法體其實就是函數接口的實現。java
lambda語法包含三個部分:編程
1)一個括號內用逗號分隔的形式參數,參數是函數式接口裏面方法的參數api
2)一個箭頭符號: ->數組
3)方法體,能夠是表達式和代碼塊,方法體函數式接口裏面的方法實現,若是是代碼塊要用{ }包起來,而且須要 return 返回值,若是方法返回值是 void 則不須要 { }安全
/** * @description: lambda表達式 * @author: liuxin * @create: 2019-01-26 18:39 **/ public class Test { private static void runNew(){ /* * Runnable是一個函數接口,只包含一個無參數返回void的run方法 * 因此lambda表達式沒有參數也沒有return * */ new Thread(()-> System.out.println("lambda方法")).start(); } private static void runOld(){ new Thread(new Runnable() { @Override public void run() { System.out.println("內部類實現方法"); } }).start(); } public static void main(String[] args) { Test.runNew(); Test.runOld(); } }
使用lambda表達式可使代碼更加簡潔的同時保持可讀性。數據結構
方法引用是lambda表達式的一個簡化寫法,引用的方法實際上是表達式的方法體實現,語法很是簡單,左邊是容器(類名,實例名)中間是「」 :: 「」,右邊則是相應的方法名。多線程
ObjectReference::methodName
引用格式:
1)靜態方法:ClassName::methodName。例如 Object::equals
2)實例方法:Instance::menthodName。例如 Object obj =new Object();obj::equals
3)構造函數:ClassName::new
/** * @description: 方法引用 * @author: liuxin * @create: 2019-01-26 18:58 **/ public class TestMethod { public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); //這裏addActionListener方法的參數是ActionListener,是一個函數式接口 //使用lambda表達式方式 a.forEach(e -> { System.out.println("Lambda實現方式"); }); //使用方法引用方式 a.forEach(TestMethod::sys); } public static void sys(Integer e) { System.out.println("方法引用實現方式"); } }
這裏的sys()方法就是lambda表達式的實現,這樣的好處是,當方法體過長影響代碼可讀性時,可使用方法引用。框架
默認方法:ide
Java 8 還容許咱們給接口添加一個非抽象的方法實現,就是接口能夠有實現方法,並且不須要實現類去實現其方法,只須要使用 default 關鍵字便可,這個特徵又叫作擴展方法。在實現該接口時,該默認擴展方法在子類上能夠直接使用,它的使用方式相似於抽象類中非抽象成員方法。爲何要有這個特性?首先,以前的接口是個雙刃劍,好處是面向抽象而不是面向具體編程,缺陷是,當須要修改接口時候,須要修改所有實現該接口的類,目前的java 8以前的集合框架沒有foreach方法,一般能想到的解決辦法是在JDK裏給相關的接口添加新的方法及實現。然而,對於已經發布的版本,是無法在給接口添加新方法的同時不影響已有的實現。因此引進的默認方法。他們的目的是爲了解決接口的修改與現有的實現不兼容的問題。但擴展方法不可以重載 Object 中的方法。例如:toString、equals、 hashCode 不能在接口中被重載。
public interface TestA { default void testA(){ System.out.println("這是默認方法!!!"); } } /** * @description: 默認方法 * @author: liuxin * @create: 2019-01-27 10:30 **/ public class TestDefult implements TestA{ public static void main(String[] args) { TestDefult testDefult =new TestDefult(); //調用testA()方法 testDefult.testA(); } }
靜態方法:在接口中,還容許定義靜態的方法。接口中的靜態方法能夠直接用接口來調用。
public interface TestA { static void TestB(){ System.out.println("這是靜態方法!!!"); } } public class TestDefult { public static void main(String[] args) { TestA.TestB(); } }
這一個功能特性出來後,不少同窗都反應了,java 8的接口都有實現方法了,跟抽象類還有什麼區別?其實仍是有的,請看下錶對比。。
public interface TestA { default void testA(){ System.out.println("這是默認方法!!!"); } } public interface TestB extends TestA{ default void testA(){ System.out.println("這是默認方法B!!!"); } } public class TestDefult implements TestB,TestA{ public static void main(String[] args) { TestDefult testDefult =new TestDefult(); testDefult.testA(); } } 輸出結果爲:這是默認方法B!!!
若是想調用testA的默認函數,則要用X.super.m(。。。。。)
public class TestDefult implements TestA{ @Override public void testA(){ TestA.super.testA(); } public static void main(String[] args) { TestDefult testDefult =new TestDefult(); testDefult.testA(); } }
默認方法給予咱們修改接口而不破壞原來的實現類的結構提供了便利,目前java 8的集合框架已經大量使用了默認方法來改進了,當咱們最終開始使用Java 8的lambdas表達式時,提供給咱們一個平滑的過渡體驗。
JSR是Java Specification Requests的縮寫,意思是Java 規範請求,Java 8 版本的主要改進是 Lambda 項目(JSR 335),其目的是使 Java 更易於爲多核處理器編寫代碼。JSR 335=lambda表達式+接口改進(默認方法)+批量數據操做。前面咱們已經是完整的學習了JSR335的相關內容了。
外部迭代就是咱們經常使用的for循環和while循環 public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); for (Integer i :a){ System.out.println(i); } }
在如今多核的時代,若是咱們想並行循環,不得不修改以上代碼。效率能有多大提高還說定,且會帶來必定的風險(線程安全問題等等)。
要描述內部迭代,咱們須要用到Lambda這樣的類庫,下面利用lambda和Collection.forEach重寫上面的循環
public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2);
a.forEach(i-> System.out.println(i)); }
如今是由jdk 庫來控制循環了,庫能夠根據運行環境來決定怎麼作,並行,亂序或者懶加載方式。這就是內部迭代
流(Stream)僅僅表明着數據流,並無數據結構,因此他遍歷完一次以後便再也沒法遍歷(這點在編程時候須要注意,不像Collection,遍歷多少次裏面都還有數據),它的來源能夠是Collection、array、io等等。
流做用是提供了一種操做大數據接口,讓數據操做更容易和更快。它具備過濾、映射以及減小遍歷數等方法,這些方法分兩種:中間方法和終點方法,中間方法返回的是Stream,容許更多的鏈式操做,若是咱們要獲取最終結果的話,必須使用終點操做才能收集流產生的最終結果。區分這兩個方法是看他的返回值,若是是Stream則是中間方法,不然是終點方法。具體請參照Stream的api。
中間方法:
filter():對元素進行過濾;
sorted():對元素排序;
map():元素的映射;
distinct():去除重複元素;
subStream():獲取子 Stream 等。
//過濾大於1的結果 public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); a.add(2); a.add(2); a.stream().filter(i->i>1).forEach(System.out::println); } 輸出結果爲2 2 2
//去重 public static void main(String[] args) { ArrayList<Integer> a =new ArrayList<>(); a.add(1); a.add(2); a.add(2); a.add(2); a.stream().distinct().forEach(System.out::println); } 輸出結果爲 1 2
終點方法:
forEach():對每一個元素作處理;
toArray():把元素導出到數組;
findFirst():返回第一個匹配的元素;
anyMatch():是否有匹配的元素等。
流有串行和並行兩種,串行流上的操做是在一個線程中依次完成,而並行流則是在多個線程上同時執行。並行與串行的流能夠相互切換:經過 stream.sequential() ( .sequential() 能夠省略)返回串行的流,經過 stream.parallel() 返回並行的流。相比較串行的流,並行的流能夠很大程度上提升程序的執行效率。
public static void main(String[] args) { //串行流計算一個範圍100萬整數流,求能被2整除的數字 int a[]= IntStream.range(0, 1_000_000).sequential() .filter(p -> p % 2==0).toArray(); long time1 = System.nanoTime(); //並行流來計算 int b[]=IntStream.range(0, 1_000_000).parallel().filter(p -> p % 2==0).toArray(); long time2 = System.nanoTime(); System.out.printf("serial: %.2fs, parallel %.2fs%n", (time1 - time0) * 1e-9, (time2 - time1) * 1e-9); } 結果爲 0.07s 和 0.02s,可見,並行排序的時間相比較串行排序時間要少不少。
若是沒有lambda,Stream用起來至關彆扭,他會產生大量的匿名內部類,若是沒有default method,集合框架更改勢必會引發大量的改動,因此lambda+default method使得jdk庫更增強大,以及靈活,Stream以及集合框架的改進即是最好的證實。