Java8听说是Java诞生以来最大的一次演进,说实话,对我我的来讲没有什么特别大的感觉,由于我学Java也就最近一两年的事,Java8在2014年3月18日发布,新增的特性确实很是惊艳,在语言特性层面上新增了lambda,Optional,默认方法,Stream API等,在虚拟机层面上新增了G1收集器(不过在Java9以后才改成默认的垃圾收集器)......java
我我的认为Java8和语言相关的几个最重要的特性是以下几个:git
本系列文章的后面几篇文章会围绕这几个主题来展开,今天就先上个开胃菜,lambda表达式!github
lambda表达式也叫作匿名函数,其基于著名的λ演算得名,关于λ演算,推荐你们去找找关于“丘奇数”相关的资料。Java一直被人诟病的一点就是“啰嗦”,一般为了实现一个小功能,就不得不编写大量的代码,而用其余的语言例如Python等,也许寥寥几行代码就解决了,但支持lambda表达式以后,这一状况获得了大大的改善,如今只要使用得当,能够大大缩减代码里,使代码的目的更加清晰,易读,纯粹。shell
在Java中,不少时候在使用一些API的时候,必需要给出一些接口的实现,但由于该实现其实也就用一次,专门去建立一个新的实现类并不划算,因此通常大多数人采起的措施应该是建立一个匿名实现类,比较典型就是Collections.sort(List list, Comparator<? super T> c)方法,该方法接受一个Comparator类型的参数,Comparator是一个接口,表示“比较器”,若是要使用该方法对集合元素进行排序,就必须提供一个Comparator接口的实现,不然没法经过编译。以下所示:编程
Collections.sort(numbers, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
复制代码
其实这个实现类的核心只有一行,即return o1.compareTo(o2);但咱们却不得不编写其余“啰嗦”的代码,若是使用lambda表达式,会是怎么个样子呢?设计模式
Collections.sort(numbers, (n1, n2) -> n1.compareTo(n2));
复制代码
没错,就是那么简单粗暴,就是一行核心代码。其余的好比方法签名啥的通通能够省略了,不只简洁,并且语义也更加清晰,读起来就好像是说:“sort方法,帮我吧numbers这个序列排个序,排序规则就按照n1.compareTo(n2)的返回值来决定”。如今,是否是感受,写代码就像在和计算机对话同样简单?但(n1, n2) -> n1.compareTo(n2)这玩意是个什么鬼?还带个箭头?不用着急,下面立刻介绍lambda表达式的语法。app
若是函数主体仅仅包含一行代码,能够省略花括号{}和return关键字(若是有的话)。对于咱们的例子,能够改写成这样:ide
Collections.sort(numbers, (n1, n2) -> {return n1.compareTo(n2);});
复制代码
注意分号!由于此时return n1.compareTo(n2);就是一条普通的Java语句了,必须遵照Java的语法规则。好了,尽管咱们如今明白了lambda语句的语法规则,但还有一个关键的问题,就是为何要这样写,换句话说,为何要有俩参数,这return又是几个意思?还有到底哪里才可使用lambda表达式?说到这,就不得不说一下和lambda息息相关的东西了:函数式接口。函数式编程
函数式接口是这样的:只有一个抽象方法的接口就是函数式接口。为何要特别强调抽象方法呢?Java接口里声明的方法不都是抽象方法吗?在Java8以前,这么说确实没有任何问题,但Java8新增了接口的默认方法,能够在接口里给出方法的具体实现,这里先很少说,后面的文章会详细讨论这个东西。函数
lambda表达式仅能够用在函数式接口上,咱们在上面遇到的Comparator就是一个函数式接口,他只有一个抽象方法:compare(),其方法签名是这样的:
int compare(T o1, T o2);
复制代码
如今来看看 (n1, n2) -> n1.compareTo(n2)这个表达式,是否是发现了什么?没错,其实lambda表达式的参数列表就是对应的函数式接口的抽象方法的参数列表,而且类型能够省略(编译器自动推断),而后n1.compareTo(n2)的返回值是int类型,也符合compare()的方法描述。这样就算是把lambda表达式和接口的抽象方法签名匹配成功了,不会出现编译错误。
除此以外,Runnable也是一个函数式接口,它只有一个抽象方法,即run(),run()方法的方法签名以下所示:
public abstract void run();
复制代码
不接受任何参数,也没有返回值。那若是要编写对应的lambda表达式,该如何作呢?其实很是简单,下面是一个示例:
Runnable r = () -> {
System.out.println(Thread.currentThread().getName());
//do something
};
复制代码
若是观察仔细的话,会发现,示例代码中把这个lambda表达式赋值给了Runnable类型的变量r!通过上面的讨论,咱们知道,其实lambda就是一个方法实现(其实叫作函数会更加合适),这条赋值语句看起来就好像是再说:“把方法(函数)赋值给变量!”。若是没有接触过函数式编程,会以为这样很奇怪,怎么能把方法赋值给变量呢?计算机就是这样有意思,老是有各类各样奇奇怪怪的东西冲击咱们的思惟!那这有什么用呢?咱先不说什么高阶函数,科里化啥的(这些是函数式编程里的概念),就说一点:意味着咱们能够把方法(函数)当作变量来使用!即如今方法就是Java世界里的“一等公民”了!既能够将其做为参数传递给其余方法(函数),还能够将其做为其余方法(函数)的返回值(之后会讲到具体的案例)
策略模式是著名的23种设计模式中的一种,关于它的描述,我这里就很少说了。直接来看个例子吧。
例子是这样的,如今有一个表明汽车的Car类以及一个Car列表,如今咱们想要筛选列表中符合要求的汽车,为了应对多变的筛选方法,咱们打算用策略模式来实现功能。
下面是Car类的代码:
public class Car {
//品牌
private String brand;
//颜色
private Color color;
//车龄
private Integer age;
//三个参数的构造函数以及setter和getter
//颜色的枚举
public enum Color {
RED,WHITE,PINK,BLACK,BLUE;
}
}
//包含Car对象的列表
List<Car> cars = Arrays.asList(
new Car("BWM",Car.Color.BLACK, 2),
new Car("Tesla", Car.Color.WHITE, 1),
new Car("BENZ", Car.Color.RED, 3),
new Car("Maserati", Car.Color.BLACK,1),
new Car("Audi", Car.Color.PINK, 5));
复制代码
咱们但愿用一个方法来封装筛选的逻辑,其方法签名伪代码以下所示:
cars carFilter(cars, filterStrategy);
复制代码
接下来实现策略模式,下面是相关的代码:
public interface CarFilterStrategy {
boolean filter(Car car);
}
public class BWMCarFilterStrategy implements CarFilterStrategy {
@Override
public boolean filter(Car car) {
return "BWM".equals(car.getBrand());
}
}
public class RedColorCarFilterStrategy implements CarFilterStrategy {
@Override
public boolean filter(Car car) {
return Car.Color.RED.equals(car.getColor());
}
}
复制代码
为了简单,仅仅实现了两种筛选策略,第一种是删选出品牌是“BWM”的汽车,第二种是删选出颜色为红色的汽车。最后来实现carFilter方法,以下所示:
private static List<Car> carFilter(List<Car> cars, CarFilterStrategy strategy) {
List<Car> filteredCars = new ArrayList<>();
for (Car car : cars) {
if (strategy.filter(car)) {
filteredCars.add(car);
}
}
return filteredCars;
}
复制代码
最后的最后是测试代码:
public static void main(String[] args) {
System.out.println(carFilter(cars, new BWMCarFilterStrategy()));
System.out.println("----------------------------------------");
System.out.println(carFilter(cars, new RedColorCarFilterStrategy()));
}
复制代码
分别实例化两个策略,将其做为参数传递给carFilter()方法,最终的输出以下所示:
[Car{brand='BWM', color=BLACK, age=2}]
----------------------------------------
[Car{brand='BENZ', color=RED, age=3}]
复制代码
确实符合预期。是否是就到此为止了呢?固然不!咱们发现,其实BWMCarFilterStrategy以及RedColorCarFilterStrategy的实现代码都很是简单,仅仅寥寥几行代码,并且CarFilterStrategy接口仅仅有一个filter抽象方法,显然是一个函数式接口,那咱们能不能用lambda表达式来简化呢?答案是:彻底能够!并且更加推荐用lambda表达式来简化这种状况。
只要略微作一些修改就好了:
System.out.println(carFilter(cars, car -> "BWM".equals(car.getBrand())));
System.out.println("----------------------------------------");
System.out.println(carFilter(cars, car -> Car.Color.RED.equals(car.getColor())));
复制代码
这里再也不使用BWMCarFilterStrategy以及RedColorCarFilterStrategy两个类了,直接用lambda表达式就好了!最后把这俩实现删除掉!是否是顿时感受整个项目的代码清爽了许多?
其实本小节的例子有些过于特殊了,若是你项目中的策略模式的实现很是复杂,其策略不是简简单单的几行代码就能解决的,此时要么进一步封装代码,要么就最好不要用lambda表达式了,由于若是逻辑复杂的话,强行使用lambda不只仅不能简化代码,反而会使得代码更加晦涩。
最后简单讲一下方法引用吧,方法引用实际上是lambda表达式的一种特殊状况的表示,语法规则是:
<class name or instance name>:<method name>
复制代码
若是lambda表达式的主体逻辑仅仅是一个调用方法的语句的话,那么就能够将其转换为方法引用,以下所示:
//普通的lambda表达式
numbers.forEach(n -> System.out.println(n));
//转换成方法引用
numbers.forEach(System.out::println);
复制代码
他俩效果是彻底同样的,但显然方法引用更加简洁,语义也更加明确了,这一语法糖“真香!”。具体的我就很少说了,建议看看《Java8 实战》一书,里面有很是很是详细的介绍。
本文简单介绍了lambda表达式的语法以及使用。lambda表达式确实能大大简化本来复杂啰嗦的Java代码,并且更加灵活,语义也更加清晰明了,写代码的时候就好像用天然语言和计算机对话同样!但也不是哪里都能使用的,一个最基本的要求就是:其放置的位置要对应着一个函数式接口。函数式接口即只有一个抽象方法的接口,例如Comparator,Runnable等。除此以外,使用lambda表达式的时候,其主体逻辑最好不要超过10行,不然最好仍是换一种方式来实现,这里10行并非那么严格,具体状况还要具体分析。方法引用是一种特殊状况下的lambda表达式的表示方法,能够理解为是lambda的一个语法糖,其语义更加明确,语法也更加简洁,用起来仍是很是舒服的!
最后,做为一个补充,来简单看看JDK内置的一些通用性比较强的函数式接口,这些接口都在java.util.function包下,我没数过,咋一看估计得有40多个吧。经常使用的有Function,Predicate,Consumer,Supplier等。Function的抽象方法的方法签名以下所示:
R apply(T t); //T,R是泛型
复制代码
简单从语义上来看,就是传入一个T类型的值,而后apply函数将其转换成R类型的值,即一对一映射。其余的接口就不作介绍了。
《Java8 实战》