在好久以前粗略的看了一遍《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方法为例子,详细解释方法引用的使用