JDK各版本新特性

CY 2018年05月04日 23次浏览

之前在网上看了很多很多的新特性相关的东西,每次看完都深深的感觉JDK变强了,一定要用上,但是没有用到的特性过一段时间又会完美的忘掉,所以打算总结一下,定期过来补充补充,供自己今后复习使用。

JDK8的新特性

Oracle 公司于 2014318 日发布 Java 8,其中包含了一部分JavaFX的增强,不列入文章。

官方对JDK8新特性的说明

简书·【译】Java 8的新特性—终极版

函数式接口

JDK8提供了函数式接口,用@FunctionalInterface注解来标明,标明为函数式接口后,接口中就只能写一个抽象方法,并且可以使用lambda表达式来创建这个接口的实现类。这也就意味着lambda其实是匿名内部类的简写形式。

JDK提供了四种函数式接口,这四种是(注意是四种,不是四个):

// 消费型接口,只吃不吐
Consumer<String> consumer = x -> System.out.println(x);
// 补给型接口,只吐不吃
Supplier<String> supplier = () -> "Hello";
// 函数型接口,又吃又吐
Function<String, Integer> function = x -> x.length();
// 断言型接口,只做裁判
Predicate<String> predicate = x -> "Hello".equals(x);

Lambda 表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递到方法中)。

// 完整的使用方式
(String x) -> {
    System.out.println(x);
}

// 括号中的参数数据类型可以不写
(x) -> {
    System.out.println(x);
}

// 括号中只有一个参数可以省略括号
x -> {
    System.out.println(x);
}

// 函数体只有一个语句,可以省略{},但是要注意{}, return,分号必须一起省略
x -> System.out.println(x)

接口的默认方法和静态方法

默认方法就是一个在接口里面用default修饰的方法,静态方法和普通的静态方法一样。

区别:

  • 默认方法通过实例调用,静态方法通过接口名调用(好像是理所应当)

  • 默认方法可以被继承,实现类可以直接使用接口默认方法,也可以覆写接口的默认方法。

  • 静态方法不能被继承,实现类不能覆写接口的静态方法,只能使用接口名调用。

为什么要产生默认方法和静态方法,看了看网上的解释,记录一下吧:

  • 默认方法的产生是为了避免突然产生了一个需求,并且每个子类都实现一样的功能,连代码都一样,工作量大,并且子类自己去实现也不能保证每个子类都能实现正确。不过我感觉增加了默认方法,这倒解决了Java中抽象类不能多继承的问题。
  • 静态方法,常用于做静态工厂方法。

方法引用

方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。

方法引用的几种形式:

// 基本的四种方式
对象名::成员方法名 // containingObject::instanceMethodName
类名::静态方法名 // ContainingClass::staticMethodName
类名::实例方法名 // ContainingType::methodName
类名::new // ClassName::new
// 四种方式衍生的用法
数组::new
super::父类成员方法名
this::本类成员方法名

重复注解

重复注解在开发中十分常用,而且也迫切的使用到,比如说要为一个表建立索引,写成下面的形式:

@Table(name = "ccb_user_info",
        indexes = {
                @Index(name = "longitudeIndex", columnList = "longitude"),
                @Index(name = "latitudeIndex", columnList = "latitude")
        }
)

期望中想写成下面的形式:

@Table(name = "ccb_user_info")
@Index(name = "longitudeIndex", columnList = "longitude")
@Index(name = "latitudeIndex", columnList = "latitude")

Java8提供了这样的支持,使用方法如下:

public class Test {

    public static void main(String[] args) {
        // 使用新增的反射方法来获取可重复注解
        Index[] indexs = UseRepeatableAnnotation.class.getAnnotationsByType(Index.class);
        for (Index index : indexs) {
            System.out.println(index);
        }
    }
}

/**
 * 使用可重复注解
 */
@Index("第一个索引")
@Index("第二个索引")
class UseRepeatableAnnotation {
}

/**
 * 定义重复注解的容器
 */
@Retention(RetentionPolicy.RUNTIME)
@interface Indexs {
    Index[] value();
}

/**
 * 要重复使用的注解
 */
@Repeatable(Indexs.class) // 使用容器
@Retention(RetentionPolicy.RUNTIME)
@interface Index {
    String value();
}

运行结果:

@com.cy.lambda.Index(value="第一个索引")
@com.cy.lambda.Index(value="第二个索引")

对泛型进行类型推断

用一行代码形容,就是下面的样子:

Map<String, String> map = new HashMap<>();

后面的泛型可以不写了。

拓宽注解/@Target

@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})

ElementType.TYPE_USEElementType.TYPE_PARAMETER是Java 8新增的两个注解,用于描述注解的使用场景。Java 语言也做了对应的改变,以识别这些新增的注解。

ElementType.TYPE_USE:可以在任何的类型前面使用,尽管是int这样的基础数据类型

ElementType.TYPE_PARAMETER:可以在泛型前面使用的注解

可以获取参数名称

在反射中可以直接获取参数的名称了:

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;

public class Test {

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test.class.getMethod("method", String.class, String.class);
        Parameter[] parameters = method.getParameters();
        for (Parameter parameter : parameters) {
            System.out.println(parameter.getName());
        }
    }

    public static void method(String happy, String newYear) {

    }
}

这是不够的,因为这个特性默认是关闭的,所以需要编译的时候加入-parameters参数

javac -parameters Test.java
java Test

运行结果:

happy
newYear

Optional

Optional 类已经成为 Java 8 类库的一部分,用来解决空指针异常,这部分内容过于简单了,直接看源码上手更快

【参考文章】JAVA8之妙用Optional解决NPE问题

Stream API

新添加的Stream APIjava.util.stream) 把真正的函数式编程风格引入到Java中。

Stream的使用方法很简单,写出来的代码也很简单,但是完成的功能是非常复杂的。

它包含了三种操作:创建操作,中间操作,终止操作

创建操作:可以用两种方式来判断操作是不是创建操作

  • Stream中的静态方法,返回Stream对象
  • 其他对象的某个方法,返回Stream对象

中间操作:Stream对象执行操作后依然返回Stream对象,属于中间操作

终止操作:Stream对象执行操作后,返回其他类型数据,或者void,这个操作就是终止操作

知道了怎么区分这些操作,就可以通过代码试探里面的某些API如何去使用了。

列举常用的一些方法:

forEachcountfilterlimitskipmapsorteddistinctallMatchanyMatchnoneMatchfindFirstfindAnymaxminreduceconcatmapToIntcollect

parallel可以将Stream转换成并行流,它用Fork/Join框架,但是如果不正确的使用,会造成线程不安全的问题,造成的结果就是数据不准确,还有会十分耗费CPU资源,如果不是什么特别重要的操作,不建议使用parallel,因为会占用CPU,影响其他操作的执行。I/O密集型的操作不要使用Fork/Join,比如说下载。如果十分注重顺序,不要使用parallel

通过collect方法结合Collectors工具类可以做到以下操作(不止)

  • 转集合:toListtoSettoCollectiontoArray
  • 聚合计算:maxByminBysummingIntaveragingIntcounting
  • 分组:groupingBy可以做到一到多级分组
  • 分区:partitioningBy可以做到分区,也就是truefalse两种区域
  • 拼接:joining可以做到拼接

【参考文章】Java Stream 详解

Date Time API

旧版的Date Time API设计很差(很多包都有Date),线程不安全(例如:SimpleDateFormat),时区处理麻烦

新版的API位于java.time

LocalDate:包含年月日

LocalTime:包含时分秒毫秒纳秒

LocalDateTime:包含年月日时分秒毫秒

DateTimeFormatter:日期时间格式化

Instant:时间戳,一个特定的时间瞬间

Duration:用于计算两个时间的距离

Period:用于计算两个日期的距离

TemporalAdjuster:时间调整器,例如下个月的第一天,TemporalAdjusters提供了很多调整方式

ZoneDateTime:包含时区

【参考链接】日期、时间 API 概述

引用《阿里巴巴Java开发手册》上的一句话

说明:如果是JDK8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormatter,官方给出的解释:simple beautiful strong immutable thread-safe。

SpringMVCSpringDataJPA也可以使用这些日期时间API,只需要做类型转换就可以了


新的编译工具,如:Nashorn引擎 jjs、 类依赖分析器jdeps

Nashorn, JavaScript 引擎

Java 8提供了一个新的Nashorn Javascript引擎,它允许我们在JVM上运行特定的Javascript应用。

但是JDK11发布后,看到了JEP335,将要废弃Nashorn,因为难以维护。

想到了JavaFX中的WebView就是用来加载网页的,当然就要执行JavaScript,同时可以使用Java执行JS,也可以JS执行Java,才能让Java和WebView中的数据进行交互,这里不具体谈这个WebView。

【参考文章】Java 8 Nashorn 教程

【参考文章】介绍 Nashorn —— Java 8 JavaScript 引擎

Oracle实验室提供了一个全栈虚拟机GraalVM也可以用Java代码来运行JavaScript代码

Base64

Base64.getEncoder()用来获取编码成Base64的对象

Base64.getDecoder()用来获取将Base64解码成明文的对象

JDK9的新特性

模块化

JDK10的新特性

JDK11的新特性

JDK12的新特性

JDK13的新特性