Lambda表達式、 函數式接口、接口默認方法、接口靜態方法、擴展註解、重複註解、Optional、Stream、時間/日期API、方法引用、 參數名字保留在字節碼中、並行數組、CompletableFuturejava
在JDK8以前,一個方法能接受的參數都是變量,例如:object.method(Object o)
那麼,若是須要傳入一個動做呢?好比回調。 那麼你可能會想到匿名內部類。 例如: 匿名內部類是須要依賴接口的,因此須要先定義個接口apache
@FunctionalInterface public interface PersonCallback { void callback(Person person); }
Person類:數組
public class Person { private int id; private String name; public Person(int id, String name) { this.id = id; this.name = name; } // 建立一個Person後,進行回調 public static void create(Integer id, String name, PersonCallback personCallback) { Person person = new Person(id, name); personCallback.callback(person); } }
public static void main(String[] args) { Person.create(1, "周瑜", new PersonCallback() { public void callback(Person person) { System.out.println("去註冊..."); } }); Person.create(2, "老師", new PersonCallback() { public void callback(Person person) { System.out.println("去登錄..."); } }); }
上面的PersonCallback其實就是一種動做,可是咱們真正關心的只有callback方法裏的內容而已,咱們用Lambda表示,能夠將上面的代碼就能夠優化成:異步
Person.create(1, "周瑜", (Person person) -> {System.out.println("去註冊...");})
有沒有發現特別簡單了...,可是咱們發現Person.create這個方法其實接收的對象依然是PersonCallback這個接口,可是如今傳的是一個Lambda表達式,那麼難道Lambda表達式也實現了這個接口?。問題也放這,咱們先看一下Lambda表達式的接口maven
Lambda容許把函數做爲一個方法的參數,一個lambda由用逗號分隔的參數列表、–>符號、函數體三部分表示。對於上面的表達式ide
- **(Person person)爲Lambda表達式的入參,{System.out.println("去註冊...");}**爲函數體
- 重點是這個表達式是沒有名字的。 咱們知道,當咱們實現一個接口的時候,確定要實現接口裏面的方法,那麼如今一個Lambda表達式應該也要遵循這一個基本準則,那麼一個Lambda表達式它實現了接口裏的什麼方法呢? 答案是:一個Lambda表達式實現了接口裏的有且僅有的惟一一個抽象方法。那麼對於這種接口就叫作函數式接口。 Lambda表達式其實完成了實現接口而且實現接口裏的方法這一功能,也能夠認爲Lambda表達式表明一種動做,咱們能夠直接把這種特殊的動做進行傳遞。
固然,對於上面的Lambda表達式你能夠簡化:函數
Person.create(1, "周瑜", person -> System.out.println("去註冊..."));
這歸功於Java8的類型推導機制。由於如今接口裏只有一個方法,那麼如今這個Lambda表達式確定是對應實現了這個方法,既然是惟一的對應關係,那麼入參確定是Person類,因此能夠簡寫,而且方法體只有惟一的一條語句,因此也能夠簡寫,以達到表達式簡潔的效果。優化
函數式接口是新增的一種接口定義。 用**@FunctionalInterface修飾的接口叫作函數式接口**,或者,函數式接口就是一個只具備一個抽象方法的普通接口,@FunctionalInterface能夠起到校驗的做用。 下面的接口只有一個抽象方法能編譯正確:this
@FunctionalInterface public interface TestFunctionalInterface { void test1(); }
下面的接口有多個抽象方法會編譯錯誤:線程
@FunctionalInterface public interface TestFunctionalInterface { void test1(); void test2(); }
在JDK7中其實就已經有一些函數式接口了,好比Runnable、Callable、FileFilter等等。 在JDK8中也增長了不少函數式接口,好比java.util.function包。 好比這四個經常使用的接口:
接口 | 描述 |
---|---|
Supplier<T> | 無參數,返回一個結果 |
Function<T,R> | 接受一個輸入參數,返回一個結果 |
Consumer<T> | 接受一個輸入參數,無返回結果 |
Predicate<T> | 接受一個輸入參數,返回一個布爾值結果。 |
那麼Java8中給咱們加了這麼多函數式接口有什麼做用?
上文咱們分析到,一個Lambda表達式其實也能夠理解爲一個函數式接口的實現者,可是做爲表達式,它的寫法實際上是多種多樣的,好比
Supplier<T>
,Function<T,R>
,Consumer<T>
,BiConsumer<T, U>
,BiFunction<T, U, R>
,BiPredicate<T, U>
。答案已經明顯了,Java8中提供給咱們這麼多函數式接口就是爲了讓咱們寫Lambda表達式更加方便,固然遇到特殊狀況,你仍是須要定義你本身的函數式接口而後才能寫對應的Lambda表達式。
總的來講,若是沒有函數式接口,就不能寫Lambda表達式.
在JDK7中,若是想對接口Collection新增一個方法,那麼你須要修改它全部的實現類源碼(這是很是恐怖的),在那麼Java8以前是怎麼設計來解決這個問題的呢,用的是抽象類,好比: 如今有一個接口PersonInterface接口,裏面有1個抽象方法:
public interface PersonInterface { void getName(); }
有三個實現類:
public class YellowPerson implements PersonInterface { @Override public void getName() { System.out.println("yellow"); } } public class WhitePerson implements PersonInterface { @Override public void getName() { System.out.println("white"); } } public class BlackPerson implements PersonInterface { @Override public void getName() { System.out.println("black"); } }
如今我須要在PersonInterface接口中新增一個方法,那麼勢必它的三個實現類都須要作相應改動才能編譯經過,這裏我就不進行演示了,那麼咱們在最開始設計的時候,其實能夠增長一個抽象類PersonAbstract,三個實現類改成繼承這個抽象類,按照這種設計方法,對PersonInterface接口中新增一個方法是,其實只須要改動PersonAbstract類去實現新增的方法就行了,其餘實現類不須要改動了:
public interface PersonInterface { void getName(); void walk(); } public abstract class PersonAbstract implements PersonInterface { @Override public void walk() { System.out.println("walk"); } } public class BlackPerson extends PersonAbstract { @Override public void getName() { System.out.println("black"); } } public class WhitePerson extends PersonAbstract { @Override public void getName() { System.out.println("white"); } } public class YellowPerson extends PersonAbstract { @Override public void getName() { System.out.println("yellow"); } }
那麼在Java8中支持直接在接口中添加已經實現了的方法,一種是Default方法(默認方法),一種Static方法(靜態方法)。
在接口中用default修飾的方法稱爲默認方法。 接口中的默認方法必定要有默認實現(方法體),接口實現者能夠繼承它,也能夠覆蓋它。
default void testDefault(){ System.out.println("default"); };
在接口中用static修飾的方法稱爲靜態方法。
static void testStatic(){ System.out.println("static"); };
調用方式:
TestInterface.testStatic();
由於有了默認方法和靜態方法,因此你不用去修改它的實現類了,能夠進行直接調用。
假設,如今有一個服務咱們須要定時運行,就像Linux中的cron同樣,假設咱們須要它在每週三的12點運行一次,那咱們可能會定義一個註解,有兩個表明時間的屬性。
public @interface Schedule { int dayOfWeek() default 1; // 周幾 int hour() default 0; // 幾點 }
因此咱們能夠給對應的服務方法上使用該註解,表明運行的時間:
public class ScheduleService { // 每週3的12點運行 @Schedule(dayOfWeek = 3, hour = 12) public void start() { // 執行服務 } }
那麼若是咱們須要這個服務在每週四的13點也須要運行一下,若是是JDK8以前,那麼...尷尬了!你不能像下面的代碼,會編譯錯誤
public class ScheduleService { // jdk中兩個相同的註解會編譯報錯 @Schedule(dayOfWeek = 3, hour = 12) @Schedule(dayOfWeek = 4, hour = 13) public void start() { // 執行服務 } }
那麼若是是JDK8,你能夠改一下註解的代碼,在自定義註解上加上@Repeatable元註解,而且指定重複註解的存儲註解(其實就是須要須要數組來存儲重複註解),這樣就能夠解決上面的編譯報錯問題。
@Repeatable(value = Schedule.Schedules.class) public @interface Schedule { int dayOfWeek() default 1; int hour() default 0; @interface Schedules { Schedule[] value(); } }
同時,反射相關的API提供了新的函數getAnnotationsByType()來返回重複註解的類型。 添加main方法:
public static void main(String[] args) { try { Method method = ScheduleService.class.getMethod("start"); for (Annotation annotation : method.getAnnotations()) { System.out.println(annotation); } for (Schedule s : method.getAnnotationsByType(Schedule.class)) { System.out.println(s.dayOfWeek() + "|" + s.hour()); } } catch (NoSuchMethodException e) { e.printStackTrace(); } }
輸出:
@repeatannotation.Schedule$Schedules(value=[@repeatannotation.Schedule(hour=12, dayOfWeek=3), @repeatannotation.Schedule(hour=13, dayOfWeek=4)]) 3|12 4|13
在Java8以前,咱們若是想獲取方法參數的名字是很是困難的,須要使用ASM、javassist等技術來實現,如今,在Java8中則能夠直接在Method對象中就能夠獲取了。
public class ParameterNames { public void test(String p1, String p2) { } public static void main(String[] args) throws Exception { Method method = ParameterNames.class.getMethod("test", String.class, String.class); for (Parameter parameter : method.getParameters()) { System.out.println(parameter.getName()); } System.out.println(method.getParameterCount()); } }
輸出:
arg0 arg1 2
從結果能夠看出輸出的參數個數正確,可是名字不正確!須要在編譯時增長**–parameters**參數後再運行。 在Maven中增長:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <compilerArgument>-parameters</compilerArgument> <source>1.8</source> <target>1.8</target> </configuration> </plugin>
輸出結果則變爲:
p1 p2 2
當咱們Javer說異步調用時,咱們天然會想到Future,好比:
public class FutureDemo { /** * 異步進行一個計算 * @param args */ public static void main(String[] args) { ExecutorService executor = Executors.newCachedThreadPool(); Future<Integer> result = executor.submit(new Callable<Integer>() { public Integer call() throws Exception { int sum=0; System.out.println("正在計算..."); for (int i=0; i<100; i++) { sum = sum + i; } Thread.sleep(TimeUnit.SECONDS.toSeconds(3)); System.out.println("算完了!"); return sum; } }); System.out.println("作其餘事情..."); try { System.out.println("result:" + result.get()); } catch (Exception e) { e.printStackTrace(); } System.out.println("事情都作完了..."); executor.shutdown(); } }
那麼如今若是想實現異步計算完成以後,立馬能拿到這個結果繼續異步作其餘事情呢?這個問題就是一個線程依賴另一個線程,這個時候Future就不方便,咱們來看一下CompletableFuture的實現:
public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(10); CompletableFuture result = CompletableFuture.supplyAsync(() -> { int sum=0; System.out.println("正在計算..."); for (int i=0; i<100; i++) { sum = sum + i; } try { Thread.sleep(TimeUnit.SECONDS.toSeconds(3)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"算完了!"); return sum; }, executor).thenApplyAsync(sum -> { System.out.println(Thread.currentThread().getName()+"打印"+sum); return sum; }, executor); System.out.println("作其餘事情..."); try { System.out.println("result:" + result.get()); } catch (Exception e) { e.printStackTrace(); } System.out.println("事情都作完了..."); executor.shutdown(); }
結果:
正在計算... 作其餘事情... pool-1-thread-1算完了! pool-1-thread-2打印4950 result:4950 事情都作完了...
只須要簡單的使用thenApplyAsync就能夠實現了。 固然CompletableFuture還有不少其餘的特性,咱們下次單獨開個專題來說解。
Java8的特性還有Stream和Optional,這兩個也是用的特別多的,相信不少同窗早有耳聞,而且已經對這兩個的特性有所瞭解,因此本片博客就不進行講解了,有機會再單獨講解,可講的內容仍是很是之多的 對於Java8,新增的特性仍是很是之多的,就是目前Java11已經出了,可是Java8中的特性確定會一直在後續的版本中保留的,至於這篇文章的這些新特性咱們估計用的比較少,因此特已此篇來進行一個普及,但願都有所收貨。