在好久以前粗略的看了一遍《Java8 實戰》。客觀的來,說這是一本寫的很是好的書,它由淺入深的講解了JAVA8的新特性以及這些新特性所解決的問題。最近從新拾起這本書而且對書中的內容進行深刻的挖掘和沉澱。接下來的一段時間將會結合這本書,以及我本身閱讀JDK8源碼的心路歷程,來深刻的分析JAVA8是如何支持這麼多新的特性的,以及這些特性是如何讓Java8成爲JAVA歷史上一個具備里程碑性質的版本。html
在這個系列博客的開篇,結合Java8實戰中的內容,先簡單列舉一下JAVA8中比較重要的幾個新特性:java
後面將針對每一個專題發博進行詳細的說明。程序員
函數式編程的概念並不是這兩年才涌現出來,這篇文章用一種通俗易懂的方式對函數式編程的理念進行講解。顧名思義,函數式編程的核心是函數。函數在編程語言中的映射爲方法,函數中的參數被映射爲傳入方法的參數,函數的返回結果被映射爲方法的返回值。可是函數式編程的思想中,對函數的定義更加嚴苛,好比參數只能被賦值一次,即參數必須爲final類型,在整個函數的聲明週期中不能對參數進行修改。這個思想在現在看來是不可理喻的,由於這意味着任何參數的狀態都不能發生變動。express
那麼函數式編程是如何解決狀態變動的問題呢?它是經過函數來實現的。下面給了一個例子:編程
String reverse(String arg) { if(arg.length == 0) { return arg; } else { return reverse(arg.substring(1, arg.length)) + arg.substring(0, 1); } }
對字符串arg進行倒置並不會修改arg自己,而是會返回一個全新的值。它徹底符合函數式編程的思想,由於在整個函數的生命週期中,函數中的每個變量都沒有發生修改。這種不變行在現在稱爲Immutable思想
,它極大的減小了函數的反作用。這一特性使得它對單元測試,調試以及編髮編程極度友好。所以在面向對象思想已經成爲共識的時代,被從新提上歷史的舞臺。多線程
可是,編程式思想並不僅是侷限於此,它強調的不是將全部的變量聲明爲final,而是將這種可重入的代碼塊在整個程序中自由的傳遞和複用。JAVA中是經過對象的傳遞來實現的。舉個例子,假如如今有一個篩選訂單的功能,須要對訂單從不一樣的維度進行篩選,好比選出全部已經支付完成的訂單,或是選出全部實付金額大於100的訂單。異步
簡化的訂單模型以下所示:編程語言
public class Order{ private String orderId; //實付金額 private long actualFee; //訂單建立時間 private Date createTime; private boolean isPaid }
接着寫兩段過濾邏輯分別實現選出已經支付完成的訂單,和全部實付金額大於100的訂單ide
//選出已經支付完成的訂單 public List<Order> filterPaidOrder(List<Order> orders) { List<Order> paidOrders = new ArrayList<>(); for(Order order : orders) { if(order.isPaid()) { paidOrders.add(order); } } return paidOrdres; } //選出實付金額大於100的訂單 public List<Order> filterByFee(List<Order> orders) { List<Order> resultOrders = new ArrayList<>(); for(Order order : orders) { if(order.getActualFee()>100) { resultOrders.add(order); } } return resultOrders; }
能夠看到,上面出現了大量的重複代碼,明顯的違背了DRY(Dont Repeat Yourself)原則,能夠先經過模板模式將判斷邏輯用抽象方法的形式抽取出來,交給具體的子類來實現。代碼以下:函數式編程
public abstract class OrderFilter{ public List<Order> filter(List<Order> orders) { List<Order> resultOrders = new ArrayList<>(); for(Order order : orders) { //調用抽象方法 if(isWantedOrder(order)) { resultOrders.add(order); } } return resultOrders; } abstract boolean isWantedOrder(Order o); } public abstract class PaidOrderFilter extends OrderFilter{ //重寫過濾的判斷邏輯 boolean isWantedOrder(Order o){ return o.isPaid(); } } public abstract class FeeOrderFilter extends OrderFilter{ //重寫過濾的判斷邏輯 boolean isWantedOrder(Order o){ return o.getActualFee() > 100; } }
可是,繼承自己會帶來類和類之間比較重的耦合,而可重入函數的傳遞則解決了這個問題。代碼以下:
public interface OrderFilter{ boolean isWantedOrder(Order o); } public List<Order> filter(List<Order> orders, OrderFilter orderFilter) { List<Order> resultOrders = new ArrayList<>(); for(Order order : orders) { if(orderFilter.isWantedOrder(o)) { resultOrders.add(order); } } return resultOrders; } //過濾出已經支付的訂單 filter(orders, new OrderFilter(){ @Override public boolean isWantedOrder(Order o){ return o.isPaid(); } })
經過這種方式,filter方法基本上處於穩定,只須要自定義傳入的訂單過濾器便可。可是,在當代對可讀性和減小重複代碼的極致追求下,重構到這種程度依然不能讓具備代碼潔癖的程序員們滿意,因而Lambda表達式應運而生。
Java8中的Lambda表達式和Lambda Calculus並非一個概念,所以全部被Lambda計算傷害過的小夥伴千萬不要恐懼。在Java8中,它更加相似於匿名類的代碼糖,從而極大的提升代碼的可讀性(大部分場景),靈活性和簡潔性。Lambda表達式的基本結構以下:
(parameters) -> expression (parameters) -> {expression}
它其實就是函數的一個簡化版本,括號中的parameters會填入這個函數的參數類型,在expression中會填入具體執行的語句。若是沒有大括號,則expression只容許填入一條語句,且會根據Lambda表達是的上下文,自動補全return語句。舉幾個具體的例子:
() -> "hello world" 相似於 String methodName(){return "hello world";} (int i, int j) -> i > j 相似於 Boolean compare(){ return i > j; }
所以Lambda表達式本質上就是對匿名函數的一種快捷展現。而上面的代碼使用lambda表達式還能夠繼續重構以下:
//標記該接口爲函數式接口,要求只能有一個待實現的函數聲明 @FuncationalInterface public interface OrderFilter{ boolean isWantedOrder(Order o); } public List<Order> filter(List<Order> orders, OrderFilter orderFilter) { List<Order> resultOrders = new ArrayList<>(); for(Order order : orders) { if(orderFilter.isWantedOrder(o)) { resultOrders.add(order); } } return resultOrders; } //過濾出已經支付的訂單 filter(orders, (Order o) -> o.isPaid()); filter(orders, (Order o) -> o.getActualFee() > 100);
Lambda表達式自己還有一些約定,以及進一步簡化的空間,這點各位筆者能夠經過這篇文章自行再去了解。
Lambda的靈活性還體如今一樣的Lambda表達式能夠賦值給不一樣的函數式接口,代碼以下:
@FuncationalInterface public interface Runnable{ void run(); } @FuncationalInterface public interface AnotherInterface{ void doSomething(); } Runnable r = () -> System.out.println("hello world"); AnotherInterface a = () -> System.out.println("hello world");
那麼編譯器是如何解析Lambda表達式的呢?它實際上是根據上下文推斷該Lambda表達式該映射到什麼函數式接口上的。就以上文的filter方法爲例子,它傳入的函數式接口爲OrderFilter,其中函數的定義爲傳入Order並返回Boolean值。編譯器就會根據這個上下文來判斷Lambda表達式是否符合函數式接口的要求,若是符合,則將其映射到該函數式接口上。
Lambda表達式做爲匿名類的語法糖,它的特性和匿名類保持一致。即若是Lambda表達式要拋出一個非檢查性異常(Unchecked Error), 則須要在函數式接口中顯示的聲明出來。以下:
@FuncationalInterface public interface AnotherInterface{ void doSomething() throws UncheckedException; }
除此之外,還有一個場景是須要在Lambda表達式中引用外部的變量。外部的變量包括局部變量,實例變量和靜態變量。其中,只容許對實例變量和靜態變量進行修改,全部的被引用的局部變量都必須顯性的或是隱形的聲明爲final。代碼以下:
//實例變量 int fieldVariable; public void someMethod() { //局部變量 int localVariable = 0; //不容許修改局部變量 Runnable r1 = () -> localVariable++; //能夠修改實例變量 Runnable r2 = () -> fieldVarialbe++; //不容許,由於被Lambda表達式引用的局部變量必須顯式或隱式的聲明爲局部變量 Runnable r3 = () -> System.out.println(localVariable); localVariable++; }
之因此有這樣的約定,是由於局部變量是保存於棧上的,保存於棧上意味着一旦該方法執行完畢,棧中的局部變量就會被彈出並回收。這裏也隱式的代表局部變量實際上是約束於當前線程使用的。此時若是Lambda表達式是傳遞到其它線程中執行的,好比上文中建立的Runnable對象傳遞給線程池執行,則會出現訪問的局部變量已經被回收的異常場景。而實例變量和靜態變量則不一樣,兩者是保存在堆中的,自己就具備多線程共享的特性。
方法的引用證實程序員對代碼的潔癖已經到了沒法搶救的程度。JAVA8中提出的方法引用的思想容許咱們將方法定義傳遞給各個函數。好比若是要使用System.out.print方法,則能夠傳入System.out::println
。方法的引用主要有三種場景:
list.sort((s1, s2)->s1.compareToIgnoreCase(s2));
, 能夠修改成list.sort(String::compareToIgnorecase)
,即知足arg0.someMethod(restArgs)
語法classA::someMethod
進行方法引用。ClassName::new
。對於有參數的構造函數,則須要結合已有的函數式接口進行引用。下一篇文章將會結合JAVA8中預約義的一些FunctionalInterface的源碼來介紹如何使用這些函數式接口幫助咱們編程。
而且會以JAVA8的comparing方法爲例子,詳細解釋方法引用的使用