JDK8 新特性
简介
JDK 8 是自JDK 5以来,Oracle对JDK做出的最重大的更新,这个版本中包含了语言、编译器、库、工具、JVM等多种新特性。针对我们平时常用的一些场景,接下来将介绍JDK 8中的新特性。
JAVA 语言的新特性
Lambda表达式和函数式接口
Lambda的设计者们为了让现有的功能与Lambda表达式良好兼容,考虑了很多方法,于是产生了函数接口这个概念,所以我们先讲函数式接口。
函数式接口
函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。
1 |
|
Java 8为函数式接口引入了一个新注解@FunctionalInterface,主要用于编译级错误检查,加上该注解,当你写的接口不符合函数式接口定义的时候,编译器会报错。
加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解只是提醒编译器去检查该接口是否仅包含一个抽象方法。
函数式接口里允许定义默认方法
1 |
|
函数式接口里允许定义静态方法
1 |
|
函数式接口里允许定义 java.lang.Object 里的 public 方法
1 |
|
默认方法和静态方法也是JDK 8的新特性,之后会详细的介绍。
JDK 1.8 之前已有的函数式接口:
- java.lang.Runnable
- java.util.concurrent.Callable
- java.security.PrivilegedAction
- java.util.Comparator
- java.io.FileFilter
- java.nio.file.PathMatcher
- java.lang.reflect.InvocationHandler
- java.beans.PropertyChangeListener
- java.awt.event.ActionListener
- javax.swing.event.ChangeListener
JDK 1.8 新增加的函数接口:
- java.util.function
Lambda表达式
Lambda表达式(也称为闭包)允许我们将函数当成参数传递给某个方法,或者把代码本身当作数据处理。之前的JDK中只能使用匿名内部类代替Lambda表达式。
之前的写法:
1 | interface Person { |
JDK 8的写法:
1 | interface Person { |
这个操作就像是将(s) -> System.out.println("eat something" + s)
这段代码赋值给了p这个实例。
我们对比一下:
1 | public void eat(String s) { |
Lambda表达式移除了多余的 public void eat
, 移除了传入参数的类型 String,一行代码所以可以移除大括号,在参数和函数之间加入了 ->
符号。传统的Java 7必须要求你定义一个“污染环境”的接口实现InterfaceImpl或者使用匿名内部类,而相较之下Java 8的Lambda, 就显得干净很多。
Lambda表达式并非匿名内部类的语法糖
实际上,匿名内部类存在着影响应用性能的问题。
首先,编译器会为每一个匿名内部类创建一个类文件。创建出来的类文件的名称通常按照这样的规则 ClassName 符合和数字。生成如此多的文件就会带来问题,因为类在使用之前需要加载类文件并进行验证,这个过程则会影响应用的启动性能。类文件的加载很有可能是一个耗时的操作,这其中包含了磁盘 IO 和解压 JAR 文件。
假设 Lambda 表达式翻译成匿名内部类,那么每一个 Lambda 表达式都会有一个对应的类文件。随着匿名内部类进行加载,其必然要占用 JVM 中的元空间(从 Java 8 开始永久代的一种替代实现)。如果匿名内部类的方法被 JIT 编译成机器代码,则会存储到代码缓存中。同时,匿名内部类都需要实例化成独立的对象。以上关于匿名内部类的种种会使得应用的内存占用增加。因此我们有必要引入新的缓存机制减少过多的内存占用,这也就意味着我们需要引入某种抽象层。
总的来说,lambda的大致思路如下:
- lamdba表达式被编译生成当前类的一个私有静态方法
- 在原调用Lamdba方法的地方编译成了一个invokedynamic指令(java7 JVM中增加了一个新的指令)调用,同时呢也生成了一个对应的BootstrapMethod
- 当lamdba表达式被JVM执行,也就是碰到2中说到的invokedynamic指令,该指令引导调用LambdaMetafactory.metafactory方法,该方法返回一个CallSite实例
- 这个CallSite实例中的target对象,也就是直接引用到一个MethodHandle实例,而这个MethodHandle实例会调用到1中生成的静态方法,在上面的例子就是lambda$main$0这个方法,完成整个lamdba表达式的使用
查看原文:Java 8 Lambdas - A Peek Under the Hood
接口的默认方法和静态方法
默认方法和抽象方法之间的区别在于抽象方法需要实现,而默认方法不需要。接口提供的默认方法会被接口的实现类继承或者覆写,例子代码如下:
1 | private interface Defaulable { |
方法引用
方法引用使得开发者可以直接引用现存的方法、Java类的构造方法或者实例对象,总的来说,一共有以下几种形式:
- 静态方法引用:ClassName::methodName;
- 实例上的实例方法引用:instanceName::methodName;
- 超类上的实例方法引用:supper::methodName;
- 类的实例方法引用:ClassName:methodName;
- 构造方法引用Class:new;
- 数组构造方法引用::TypeName[]::new
构造器引用,语法是Class::new,或者更一般的形式:Class
1 | final Test test = Test::new; |
静态方法引用,语法是Class::static_method
1 | list.forEach(System.out::println); |
重复注解
在Java 8中使用**@Repeatable**注解定义重复注解
1 |
|
Java官方库的新特性
Optional
Java应用中最常见的bug就是NullPointerException,JDK参考了Guava的Optionals类来解决NullPointerException,从而避免源码被各种null检查污染。
看一个简单样例:
1 | Optional< String > firstName = Optional.of( "Tom" ); |
更直接的例子,使用JPA访问数据库(最新的Mybatis已经支持,但是代码生成工具还没有支持):
1 |
|
Streams
有了函数式编程之后,配合StreamAPI,极大得简化了集合操作(后面我们会看到不止是集合)。
1 | private enum Status { |
传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理, 配合try-with-resource写法
1 | Path path = new File( filename ).toPath(); |
Date/Time API(JSR 310)
java.util.Date和后来的java.util.Calendar一直存在着诸多的问题,比如:
线程安全问题:java.util.Date是非线程安全的,所有的日期类都是可变的;
设计很差:在java.util和java.sql的包中都有日期类,此外,用于格式化和解析的类在java.text包中也有定义。而每个包将其合并在一起,也是不合理的;
时区处理麻烦:日期类不提供国际化,没有时区支持,因此Java中引入了java.util.Calendar和Java.util.TimeZone类;
从而催生了第三方库Joda-Time,Java 8中新的时间和日期管理API深受Joda-Time影响,并吸收了很多Joda-Time的精华。
在java.util.time包中常用的几个类有:
- 它通过指定一个时区,然后就可以获取到当前的时刻,日期与时间。Clock可以替换System.currentTimeMillis()与TimeZone.getDefault()
- Instant:一个instant对象表示时间轴上的一个时间点,Instant.now()方法会返回当前的瞬时点(格林威治时间);
- Duration:用于表示两个瞬时点相差的时间量;
- LocalDate:一个带有年份,月份和天数的日期,可以使用静态方法now或者of方法进行创建;
- LocalTime:表示一天中的某个时间,同样可以使用now和of进行创建;
- LocalDateTime:兼有日期和时间;
- ZonedDateTime:通过设置时间的id来创建一个带时区的时间;
- DateTimeFormatter:日期格式化类,提供了多种预定义的标准格式;
Spring 中 LocalDateTime格式处理
Controller接收LocalDateTime参数
@RequestParam @DateTimeFormat(pattern = “yyyy-MM-dd HH:mm:ss”) LocalDateTime date
ResponseBody格式化LocalDateTime
Spring默认使用jackson来进行json格式转换,我们只需要使用@Bean注解创建一个ObjectMapperbean,并将JavaTimeModule注册到ObjectMapper中即可,spring会使用该bean创建MappingJackson2HttpMessageConverter进行json格式转换。这里需要加入jackson
的jsr310
扩展包。
1 | <dependency> |
1 |
|
或者使用(缺点是每一个变量都需要加这个注解)
1 |
另外,如果持久层框架使用mybatis,同样需要加入
mybatis
的jsr310
扩展包。
1 | <dependency> |
Base64
对Base64编码的支持已经被加入到Java 8官方库中,这样不需要使用第三方库就可以进行Base64编码,例子代码如下:
1 | String text = "Base64 finally in Java 8!"; |