JDK8发布已经有三年的时间了,虽然每次都在用JDK8去开发应用程序,但是在工作中对JDK8的新特性真的是使用不多。不过,我相信JDK8的开发者们这么辛辛苦苦编写出来的新特性一定是有它非常好的一面的,只是我们目前还没有习惯这种编程方式,不熟悉它并不代表它不优秀!
接口中新增的default方法
以前我们会说接口只能声明抽象方法,而抽象类才能既声明抽象方法又能实现具体方法。但是从JDK8开始,我们再也没有这种说法啦!因为,JDK8中为我们带来了接口的default默认方法。也就是说我们可以给接口中声明的方法加上默认的实现啦~
1 | interface Formula { |
我们可以看到Formula
类有两个方法,一个是未实现的calculate
方法,另一个是具有默认实现的sqrt
方法。实现Formula
接口的具体类不必实现sqrt
就可以直接使用这个方法。
1 | Formula formula = new Formula() { |
是不是很Nice?但是我估计大家一时半会儿也很难使用这个特性,凡事得有一个过程。
Lambda表达式
Lambda表达式可能是JDK8与之前版本最大的一个区别了!Lambda表达式在Python3中也开始支持了,二者的使用方式几乎相同,说明Lambda表达式已经被越来越多的平台支持啦,也说明Lambda表达式被越来越多的人所知晓。好啦,我们先来看看没有Lambda表达式的时候我们怎么写代码?
1 | List<String> names = Arrays.asList("Richard", "Jack", "Pony"); |
这种方式够啦!!!代码好多都是可以省略的,其实真正起作用的就是b.compareTo(a)
不是吗?那我们直接用它!
1 | List<String> names = Arrays.asList("Richard", "Jack", "Pony"); |
还可以更简洁~
1 | Collections.sort(names, (String a, String b) -> (b.compareTo(a))); |
为什么还要加上类型?也可以去掉嘛!
1 | Collections.sort(names, (a, b) -> b.compareTo(a)); |
Java编译器自己会推断出a
和b
的类型,所以我们只需要写出它们需要做什么就行~现在继续看看Lambda表达式会发挥出什么样的威力?
函数式接口
Java8中引入了函数式编程,但是这里需要给大家说明的是Lambda表达式适用的场景,Lambda表达式的实质是什么?Lambda表达式就是一个匿名方法, 每一个Lambda表达式必须匹配一个只包含一个方法的接口。这样的方法就是函数式接口,这种接口在以前也叫SAM(Single Abstract Method)单抽象方法类型。
我们可以使用任意的接口来作为Lambda表达式,只要这个接口只有一个抽象方法就行。为了保证在生产中不出现一些没有声明但是却满足条件的接口,这种很有可能是误报,所以我们需要使用@FunctionalInterface
注解。这样编译器就会知道这个注解了,并且在你尝试向其中添加额外的抽象方法时就会报错。来看一个例子:
1 |
|
可以看到,converter
对象的创建是基于一个抽象方法的实现。(from) -> Integer.valueOf(from)
这个方法是T convert(F from)
的具体实现。如果这个时候你再添加一个抽象方法,这个Lambda表达式就没法写了,所以,函数式接口只能允许有一个抽象方法。注意一个事实就是如果你省略了这个@FunctionalInterface
注解这个代码依然是正确的。原因我们前面讲过。
方法和构造函数引用
利用静态方法引用上面的代码还可以写成这个样子:
1 | Converter<String, Integer> converter = Integer::valueOf; |
你可能会问这个::
是什么?没错,我们在C++里面看到过,它是作用域限定符。那么在Java8里面它的作用是什么呢?我们可以看到的是它可以被用来引用类的静态方法。其实在C++里面也有这样的作用,我估计Java8学习了这一点的。那除了引用静态方法,普通的实例方法能不能引用呢?答案当然是可以的!
1 | class Something { |
我们注意到类Something
的实例对象something
可以通过::
来调用它的startsWith
方法。以上是对方法的引用,我们来看看::
对构造函数是如何引用的?
先定义一个对象:
1 | public class Student { |
然后定义一个创建学生对象的地方:
1 | public interface School { |
现在就可以这么创建学生对象啦:
1 | School school = Student::new; |
其实如果你使用IDE的话,上面的Student::new
会自动提示出来。我们通过使用Student::new
来引用了Student
类的构造函数,而且Java的编译器会自己去匹配对应的构造函数。
Lambda访问限定域
访问局部变量
你可以在匿名对象中访问外部的变量,但是需要注意的是,这个被访问的变量默认是final的,因此你不可以在匿名对象中修改这个变量。在Lambda表达式中也是这样,Lambda表达式外部的变量默认是final的,如果你修改这个变量的值编译就没法通过。
1 | interface Calculator { |
这里就会报一个错误:Variables used in lambda expression should be final or effectively final
。如何去修改呢?将b = 0;
去掉即可。它就默认int b = 0;
声明的这个变量b
为final
的了。为什么这些变量要设置为final
不可变,那是因为考虑到多线程环境下更改这个变量的值是不安全的。
访问实例属性和静态变量
使用Lambda表达式访问实例属性和静态变量的时候,和访问局部变量不同,你不需要将这些属性或者变量设置为final类型。下面这样写也是合法的。
1 | class Scopes { |
这段代码执行完毕的时候,请问outerNum
和outerStaticNum
的值各是多少?23
和72
?当然不是,都是零。因为我们不能改变在Lambda表达式之外定义的值。(但是匿名函数可以哦!)
访问default方法
在Java8中,虽然我们新增了默认方法即default方法,但是我们确不能使用Lambda表达式访问它。像下面这种方式就是编译不通过的:
1 | interface Calculator { |
它会提示Cannot resolve method remove(a)
,说明Lambda表达式无法访问默认方法。
内置的函数式接口
Java8中的函数式接口有很多,其中有相当多的一部分来自于Google的开源项目Guava,如果对Guava不熟悉的同学可以看一下我的《Guava优美代码》系列博客。下面让我们看看Java8为我们提供了哪些函数式接口?
- Predicates
- Functions
- Suppliers
- Consumers
- Comparators
- Optionals
Predicates
中文翻译是什么,预言
对吧。它的作用就是用来判断一些条件成不成立,而这些条件都会提前在Predicate
对象中定义好。下面给出一个简单的例子:
1 | Predicate<String> predicate = s -> s.startsWith("f"); |
另外还有一些方法,比如and
和or
,这两个方法是用来创建复合预言
的。
1 | Predicate<String> predicate0 = s -> s.contains("k"); |
也可以使用::
来创建Predicate
对象:
1 | Predicate<Boolean> nonNull = Objects::nonNull; |
可以看到Java8中的Predicate使用还是很多样的。但是Guava中的Predicate使用方式更多。留一个悬念,自己到本博客Guava系列博客中寻找答案吧!
Functions
这个是啥意思大家都明白,函数
嘛!函数的功能是干什么用的,执行一些动作!所以我们这里有必要给大家再区(啰)分(嗦)一下,函数Function是用来执行一些操作的所以它有可能有返回值,有可能没有返回值;而预言Predicate主要是用来判断一些条件成不成立的,所以它的返回值只能是Boolean类型。下面来看一下Function具体怎么用?
1 | Function<String, Integer> function = Integer::valueOf; |
是不是很好玩?很灵活吧!可以非常自由地组合一些方法。
Suppliers
Suppliers又是啥?提供者
?一般我们更倾向于将其称之为生产者
。和Function
不同的是Supplier
不支持任何参数。
1 | Supplier<Student> supplier = Student::new; |
啥也不说了,它就这一个方法。。。类似于一种工厂。
Consumers
Consumers就是消费者
,消费者
的作用当然是消费从生产者
那里产出的产品啦。我们来看看消费者到底如何开始消费?
1 | Consumer<Student> consumer = p -> |
我们可以看到,现需要定义一个消费者,告诉它具体如何消费,然后通过accept
方法给它传递一个产品供它消费。
Comparators
比较器是JDK1.7就已经出现的函数式接口。JDK1.8中为这个函数式接口添加了需要默认方法。
1 | Comparator<Student> comparator = (s1, s2) |
Optional
Optional就是可选的
嘛!啥意思?也就是说返回的结构有可能是Null
也有可能Non-Null
。Null
有合适和正确的使用场景,如在性能和速度方面Null
是廉价的,而且在对象数组中,出现Null
也是无法避免的。但相对于底层库来说,在应用级别的代码中,Null
往往是导致混乱,疑难问题和模糊语义的元凶,就如同我们举过的Map.get(key)
的例子。最关键的是,Null
本身没有定义它表达的意思。
1 | Optional<String> optional = Optional.of("hello"); |
上面的例子是直接创建了一个Optional
对象的,但是我们也可以在函数中返回一个Optional
对象。
1 | public Optional<String> getSomething(String thing){ |
一般我们都是先需要判断结果是否为空,然后在执行相应的操作。而且它比较好的一点是如果返回的值为空我们还可以指定相关的替代值。
总结
以上就是JDK1.8中给我们带来的新特性的一部分。关于Stream流以及Map, Reduce等部分的内容我们在下一篇博文里再探讨。