一、前言
自从Oracle在2017年9月推出JDK 9之后,JDK版本的更新也定为了每半年更新一次,截至目前,JDK 11即将发布正式版。不过目前大部分的生产环境的版本依旧是8的比较多,这里我们来了解下JDK 9及接下来JDK 10在语言上的新功能,这样以后如果哪天要升级版本了,也好又有个印象。
二、JDK 9
1. 交互式REPL:JShell
REPL,是一种快速运行语句的基于命令行的工具,其他语言基本都有这种交互式的开发环境,比如python等,而在Java的过去版本中,是没有REPL的。如果你想执行一个简单的打印语句,那么我们首先要创建一个有main方法的类,然后通过javac命令进行编译,再然后运行。而JShell的出现则是为了解决这个问题。
这样我们平时可以使用JShell来进行一些简单的测试工作,并且JShell也可以通过tab键进行相应的补全操作。
2. 集合框架的工厂方法:of
在JDK 9之前,如果要创建一个不可变的集合,我们可以通过Collections的unmodifiable开头的方法来进行创建,不过,JDK 9提供了一种更优雅的创建方式:of方法。
在集合框架的基础类型List,Set,Map中都提供了多个重载的of方法,来提供不可变的静态工厂方法,of方法创建的集合要求参数不能为null。
List list = List.of();
Set set = Set.of(1, 2, 3);
Map map = Map.of("green", 1, "red", 2);
不过有关Map,还可以多说一点:
- Map的of方法参数,是键值对依次挨着传递参数的;
- 感兴趣的可以看下实现源码,底层保存数据是通过一个final类型的Object数组,保存的方式是下标
i
保存key,而i+1
保存key对应的value,当然key和value都是不能为null的,底层通过Objects.requireNonNull方法进行非空判断。
static final class MapN extends AbstractImmutableMap {
@Stable
final Object[] table; // pairs of key, value
@Stable
final int size; // number of pairs
MapN(Object... input) {
// 奇偶校验,也就是判断key和value是否一一对应
if ((input.length & 1) != 0) { // implicit nullcheck of input
throw new InternalError("length is odd");
}
size = input.length >> 1;
int len = EXPAND_FACTOR * input.length;
len = (len + 1) & ~1; // ensure table is even length
table = new Object[len];
for (int i = 0; i < input.length; i += 2) {
@SuppressWarnings("unchecked")
K k = Objects.requireNonNull((K)input[i]);
@SuppressWarnings("unchecked")
V v = Objects.requireNonNull((V)input[i+1]);
int idx = probe(k);
if (idx >= 0) {
throw new IllegalArgumentException("duplicate key: " + k);
} else {
int dest = -(idx + 1);
table[dest] = k;
table[dest+1] = v;
}
}
}
具体有关不可变静态工厂方法,可参考API文档说明:https://docs.oracle.com/javase/9/docs/api/java/util/List.html#immutable
3. 接口中的私有方法
JDK 8中接口引入了默认方法和静态方法,在JDK 9中则是引入了私有方法,私有方法是一种有实现的访问修饰符是private的方法,默认方法和静态方法可以共享私有方法,因此在一定程度上这可以避免冗余代码。
public interface MyInterface {
void normalMethod();
static void staticMethod() {
initStatic();
}
default void defaultMethod() {
init();
}
private static void initStatic() {
System.out.println("private static method!");
}
private void init() {
System.out.println("private method");
}
}
不过需要注意的点:
- 私有方法遵循private修饰符属性,无法被覆盖;
- 静态方法无法被覆盖,并且静态方法遵循static属性,无法调用非static方法;
4. Stream API新增方法
在使用JDK 8的过程中,我们已经了解到Stream API所带来的便利,而JDK 9则给Stream接口又新增了4个方法:ofNullable
,iterate
,takeWhile
,dropWhile
,我们来看一下。
4.1 ofNullable方法
public static Stream ofNullable(T t) {
return t == null ? Stream.empty()
: StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}
这个比较简单,将单个元素转换为Stream流,如果是null,则返回一个为空的流。
4.2 iterate方法
public static Stream iterate(T seed, Predicate super T> hasNext, UnaryOperator next)
这是JDK 9添加的iterate的重载方法,原有的iterate方法是一种无限的操作,必须要有limit等类似这样的操作来限制大小。而新添加的这个重载方法多了个条件参数,来决定什么时候结束迭代循环,比如说:
// 比如生成等差数列 0 3 6 9 12 15 18 21 24 27
Stream.iterate(0, n -> n + 3).limit(10).forEach(x -> System.out.print(x + " "));
System.out.println("\n----------------");
Stream.iterate(0, n -> n <= 27, n -> n + 3).forEach(x -> System.out.print(x + " "));
其实,可以说:
Java SE 9's iterate() = Java SE 8's iterate() + Java SE 8's filter()
4.3 takeWhile方法
这个方法用于对Stream流进行一个条件过滤,返回过滤后的流。该方法是一个默认方法。
default Stream takeWhile(Predicate super T> predicate)
先看一个例子:
Stream stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// output: 1 2 3
stream.takeWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));
System.out.println("\n------------------");
stream = Stream.of(1, 4, 2, 5, 3, 6, 7, 8, 9, 10);
// output: 1
stream.takeWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));
需要注意的是过滤的方式是依次对Stream流的元素进行判断,返回第一次判断结果为false的情况下所过滤的流。
4.4 dropWhile方法
dropWhile方法也是对Stream流的一个过滤,不过该方法恰好和takeWhile方法过滤到的流反过来,意思是说删掉满足过滤条件的元素,返回剩下的元素,还是拿上个例子来看:
Stream stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// output: 4 5 6 7 8 9 10
stream.dropWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));
System.out.println("\n------------------");
stream = Stream.of(1, 4, 2, 5, 3, 6, 7, 8, 9, 10);
// output: 4 2 5 3 6 7 8 9 10
stream.dropWhile(x -> x < 4).forEach(i -> System.out.print(i + " "));
Stream API参考自:Java SE 9: Stream API Improvements
5. Optional类新增方法
Optional类是JDK 8提供的一种优雅的处理null的解决方法,同样,在JDK 9中,Optional类中也增加了几个方法,我们来看一下。
5.1 stream方法
public Stream stream() {
if (!isPresent()) {
return Stream.empty();
} else {
return Stream.of(value);
}
}
该方法用于将一个Optional对象转为Stream流,比如说:
Stream stream = Optional.of(1).stream();
Stream> os = ..;
Stream s = os.flatMap(Optional::stream);
5.2 ifPresentOrElse方法
public void ifPresentOrElse(Consumer super T> action, Runnable emptyAction) {
if (value != null) {
action.accept(value);
} else {
emptyAction.run();
}
}
和原有的ifPresent方法很像,不过是多了一个else的判断,也可也算是ifPresent
方法和orElse
方法的组合形式,其实直接看代码就一目了然了。
5.3 or方法
public Optional or(Supplier extends Optional extends T>> supplier) {
Objects.requireNonNull(supplier);
if (isPresent()) {
return this;
} else {
@SuppressWarnings("unchecked")
Optional r = (Optional) supplier.get();
return Objects.requireNonNull(r);
}
}
返回一个Optional对象,如果不为空,返回对象本身,如果为空,返回我们通过Supplier指定的Optional 对象。
6. try-resource改进
我们先看代码,再说改进的内容:
JDK 9之前:
void testARM_Before_Java9() throws IOException {
BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
try (BufferedReader reader2 = reader1) {
System.out.println(reader2.readLine());
}
}
JDK9之后的代码:
void testARM_Java9() throws IOException {
BufferedReader reader1 = new BufferedReader(new FileReader("journaldev.txt"));
try (reader1) {
System.out.println(reader1.readLine());
}
}
在JDK 9之前,try-resource块需要满足以下条件:
- try块中的资源需要实现java.lang.Autocloseable接口;
- 资源的类型需要final或者间接是final类型的;
- 如果资源在Try-With-Resources语句之外声明,我们应该重新引用局部变量(上面的reader1变量);
- 新创建的局部变量在Try-With-Resources语句中是有效的;
JDK 9的话,如果我们已经有一个在Try-With-Resource语句外面声明为final或有效final的资源,那么我们就不需要再声明一个局部变量。我们可以在Try-With-Resource语句中使用以前创建的变量。
7. @Deprecated注解增强
在JDK 8和早期版本中,@Deprecated只是一个没有任何方法的标记接口,用于将某一个类,字段,方法,接口等对象标记为废弃状态。
而在JDK 9中,Oracle对该注解进行了增强,用于提供关于废弃对象的更多信息,并且还提供了扫描jar文件的工具jdeprscan
来分析应用程序种使用废弃API的情况。该注解新增了两个参数:since
,forRemoval
,我们来简单看下:
String since() default "";
boolean forRemoval() default false;
- since,表示该废弃对象废弃的版本;
- forRemoval,表示该废弃元素在以后的版本是否会删除,默认是false;
8. Diamond (钻石) 操作符 (<>)
钻石操作符是JDK 7引入的,目的是为了自动识别泛型<> 尖括号内的类型,以避免代码的冗余,进而提高代码的可读性,举个简单的例子:
Map map = new TreeMap();
JDK 7引入钻石操作符后:
Map map2 = new TreeMap<>();
但是在JDK 9之前,钻石操作符的使用有些限制,就是不能用于匿名内部类上,而JDK 9则解决了这个问题:
List list = new ArrayList<>(){
@Override
public boolean add(String s) {
return super.add(s);
}
};
List list = new ArrayList<>(){};
以上代码在JDK 8是编译不通过的。
9. 响应式流
JDK9中的Flow API对应响应式流规范,响应式流规范是一种事实标准。JEP 266包含了一组最小接口集合,这组接口能捕获核心的异步发布与订阅。希望在未来第三方能够实现这些接口,并且能共享其方式。
java.util.concurrent.Flow包含以下4个接口:
- Flow.Processor(处理器)
- Flow.Publisher(发布者)
- Flow.Subscriber(订阅者)
- Flow.Subscription(订阅管理器)
这些接口都支持响应式流发布-订阅框架。Java 9也提供了实用类SubmissionPublisher。一个发布者产生一个或多个物品,这些物品由一个或多个消费者消耗。并且订阅者由订阅管理器管理。订阅管理器连接发布者和订阅者。
有关响应式流,推荐阅读:
Java 9 - 说说响应式流
https://www.journaldev.com/20723/java-9-reactive-streams
10. CompletableFuture API 增强
CompletableFuture 类我们前面说过,是一个Future的增强类,用于解决Future类所不太好解决的问题,比如说结果的获取。而在JDK 9中,Oracle对该类也进行了增强用于支持一些延时和超时的操作,增加了一些常用方法和更好的子类化的操作方式。
11. Java模块化系统-Jigsaw
模块化是JDK 9中最令人期待也是最重要的功能,模块化和我们原先jar包的形式完全不同,我们都知道原先在JDK 9之前我们都是通过jar包来进行组织的,比如说,我们常用的rt.jar,tools.jar。而引入了模块化系统之后,JDK被重新组织成94个模块,我们常用的代码比如java.util,java.lang等包都被组织到了java.base
模块下。
通常情况下一个模块包含着一个模块描述符文件(module-info.java)用来指定模块的名字、依赖(需要注意的是每个模块必须显式得声明其依赖)、对外可使用的包(其余的包则对外不可见)、模块提供的服务、模块使用的服务以及允许哪些模块可以对其进行反射等配置信息。这里需要注意下:
模块描述符文件通常位于根目录下;
感兴趣的可以看下java.base
模块,几乎包含了我们常用的所有相关包,如果我们要使用其他的模块,在module-info.java进行相应的引入:
module JDK9FirstDemo {
requires java.sql;
}
模块最终都会打包成 jar 包来分发和使用的,而打包则是通过JDK 9中的JLink来进行,那么模块打包的 jar 包和普通的 jar 包有什么区别呢?模块和普通的 jar 包几乎是一样的,只不过比普通的 jar 包在根目录下多了一个模块描述符文件—— module-info.class而已。
这里没有详细介绍模块化有两点原因:
- 一是模块化和我们之前的操作方式有很大区别,虽然JDK 提供了兼容方式,但目前还没有大范围的在生产环境上使用;
- 这里先了解有这么个东西,等哪天模块化普及了,再来仔细学习下。
如果有兴趣的,推荐看下:Java 9 模块化入门,这篇文章大概介绍了模块化的历史及模块化中各个指令的使用。
三、总结
其实这里只列出了JDK 9部分可能常用的语言方法的特性,其实JDK 9还有许多特性:
- 在JDK8中,可以单独使用下划线作为变量,但JDK 9变量不能被命名为_;
- GC的改进;
- Javadoc搜索;
- 进程API的改进;
- 统一的JVM日志处理;
- HTTP 2客户端API;
- HTML 5风格的javadoc文档;
- 多分辨率图像API–JEP 251;
- 废弃Applet API;
如果以后有用到的,再来更新。
本文参考自:Java 9 Features with Examples
如果要查看 JDK 9全部更新,可参考:http://openjdk.java.net/projects/jdk9/
这里把JDK 10的一些更新也发出来,如果有需要,可以参考:https://www.journaldev.com/20395/java-10-features