java 8引入lambda迫切需求是因為lambda 表達式能簡化集合上數據的多線程或者多核的處理,提供更快的集合處理速度
函數式接口
Java 8 引入的一個核心概念是函數式接口(Functional Interfaces)。通過在接口里面添加一個抽象方法,這些方法可以直接從接口中運行。如果一個接口定義個唯一一個抽象方法,那么這個接口就成為函數式接口。同時,引入了一個新的注解:@FunctionalInterface。可以把他它放在一個接口前,表示這個接口是一個函數式接口。這個注解是非必須的,只要接口只包含一個方法的接口,虛擬機會自動判斷,不過最好在接口上使用注解 @FunctionalInterface 進行聲明。在接口中添加了 @FunctionalInterface 的接口,只允許有一個抽象方法,否則編譯器也會報錯,可以擁有若干個默認方法。
java.lang.Runnable 就是一個函數式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
lambda表達式
Java中的lambda無法單獨出現,它需要一個函數式接口來盛放,lambda表達式方法體其實就是函數接口的實現。
lambda語法包含三個部分:
1)一個括號內用逗號分隔的形式參數,參數是函數式接口里面方法的參數
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表達式的實現,這樣的好處是,當方法體過長影響代碼可讀性時,可以使用方法引用。
默認方法和靜態方法
默認方法:
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抽象類與接口對比
這一個功能特性出來后,很多同學都反應了,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表達式時,提供給我們一個平滑的過渡體驗。
JSR335
JSR是Java Specification Requests的縮寫,意思是Java 規范請求,Java 8 版本的主要改進是 Lambda 項目(JSR 335),其目的是使 Java 更易於為多核處理器編寫代碼。JSR 335=lambda表達式+接口改進(默認方法)+批量數據操作。前面我們已是完整的學習了JSR335的相關內容了。
外部VS內部迭代
外部迭代就是我們常用的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 API
流(Stream)僅僅代表着數據流,並沒有數據結構,所以他遍歷完一次之后便再也無法遍歷(這點在編程時候需要注意,不像Collection,遍歷多少次里面都還有數據),它的來源可以是Collection、array、io等等。
1)中間與終點方法:
流作用是提供了一種操作大數據接口,讓數據操作更容易和更快。它具有過濾、映射以及減少遍歷數等方法,這些方法分兩種:中間方法和終點方法,中間方法返回的是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():是否有匹配的元素等。
2)順序流與並行流
流有串行和並行兩種,串行流上的操作是在一個線程中依次完成,而並行流則是在多個線程上同時執行。並行與串行的流可以相互切換:通過 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以及集合框架的改進便是最好的證明。
