java92017.9.21发布,是一个non-LTS版本,本文将介绍该版本带来的主要新特性。

java9主要新特性一览

  • 模块化系统
  • jshell交互式编程
  • 多版本兼容jar
  • 接口的私有方法
  • 钻石操作符的使用升级
  • try-with-resources语法改进
  • java.lang.String存储结构变更
  • 容器类新增of()方法
  • Stream API新增方法
  • java.util.Optional类新增多个方法
  • 新的HTTP客户端API

模块化系统

在没有模块化系统之前,jdk存在的问题主要有两个方面。一方面是所有Java应用程序的运行时环境都依赖一个叫做rt.jar的文件,每个应用都会加载这个rt.jar文件到内存,即使应用程序只使用到了其中一两个类文件,类加载器在进行类加载时,会从庞大的rt.jar文件中寻找到需要的类。这对虚拟机内存管理来说是较大的一个性能负载;另一方面是代码的封装性,以前我们只能使用访问权限修饰符对类、成员变量、成员方法进行封装,无法做到对包package的封装。

模块化系统的出现,很好的解决了上述两个方面的问题,一方面减少了虚拟机内存消耗;另一方面提供了对整个包封装的能力。

使用方式

前提

安装Java9或其以上版本,使用IDEA新建一个maven项目java9,然后再新建两个子模块项目java9-module1java9-module2

java9-module1模块提供两个包

java9-module1中创建2个包分别为utilentity,然后分别提供一个PrintUtils和一个User类内容分别如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sunchaser.sparrow.javase.java9.util;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class PrintUtils {
private PrintUtils() {}

public static void print(String str) {
System.out.println(str);
}
}
1
2
3
4
5
6
7
8
9
package com.sunchaser.sparrow.javase.java9.entity;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class User {
private String name;
}
创建java9-module1module-info.java文件并导出对外提供的util模块

new-module-info

IDEA中鼠标放至src/main/java目录上点击鼠标右键->new->module-info.java,创建module-info.java文件编写内容如下:

1
2
3
module java9.module1 {
exports com.sunchaser.sparrow.javase.java9.util;
}

模块名java9.module1可自定义。

java9-module2中导入使用java9-module1导出的util模块

首先在java9-module2pom.xml中添加对java9-module1的依赖:

1
2
3
4
5
6
<dependency>
<groupId>com.sunchaser.sparrow</groupId>
<artifactId>java9-module1</artifactId>
<version>1.0-SNAPSHOT</version>
<scope>compile</scope>
</dependency>

同样的方式,我们创建java9-module2module-info.java文件并导入java9-module1的模块,module-info.java文件内容如下:

1
2
3
module java9.module2 {
requires java9.module1;
}

然后编写测试代码,使用一下导出的util包下的PrintUtils类:

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.sunchaser.sparrow.javase.java9;

import com.sunchaser.sparrow.javase.java9.util.PrintUtils;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class Java9ModuleTest {
public static void main(String[] args) {
PrintUtils.print("java9 module");
}
}

然后我们尝试使用一下entity包下的User类,发现无法使用,报错内容如下:

1
Package 'com.sunchaser.sparrow.javase.java9.entity' is declared in module 'java9.module1', which does not export it to module 'java9.module2'

module2-error

Tips:如果运行Java9ModuleTest#main方法报错:

1
2
Error occurred during initialization of boot layer
java.lang.module.ResolutionException: Module lombok does not read a module that exports org.mapstruct.ap.spi

java9-module2pom.xml中添加以下依赖:

1
2
3
4
5
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.3.0.Final</version>
</dependency>

jshell交互式编程

java9之前的版本中,想运行一段java代码,必须先创建.java文件,然后声明类、变量和测试方法,再使用javac将源文件编译成.class字节码文件,最后再使用java运行字节码文件才能执行我们写的代码。整个过程十分累赘和繁琐。

jshell工具的引入让我们可以在没有创建.java文件和类的情况下直接定义变量、计算表达式和执行语句。即在命令行中直接运行java的代码。

简单使用

找到jdk安装目录,在bin目录下有一个jshell工具,双击即可运行jshell工具。

可以直接运行java代码、定义变量&方法和计算表达式等:

use-jshell

Tips

  • jshell环境下,语句末尾的分号;是可选的。但推荐最好加上提高代码可读性。
  • 可以重新定义相同方法名和参数列表的方法,表示对现有方法的修改。
  • 输入/help可查看帮助及更多高级用法。

接口的私有方法

java8之前,接口中只能定义抽象方法,不能有任何实现,成员变量默认被public static final修饰。

java8,接口中新增了默认方法和静态方法,此时接口就有一点像抽象类了。

而在java9中,接口可以定义private修饰的私有方法了。

代码示例:

接口MyInterface.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.sunchaser.sparrow.javase.java9;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public interface MyInterface {
/**
* 默认被public static final修饰
*/
String NAME = "interface";

/**
* 抽象方法
*/
void abstractMethod();

/**
* Java8的默认方法
*/
default void defaultMethod() {
System.out.println("我是Java8接口中的默认方法");

// since java9
privateMethod();
}

/**
* Java8的静态方法,默认被public修饰
*/
static void staticMethod() {
System.out.println("我是Java8接口中的静态方法");
}

/**
* Java9的私有方法
*/
private void privateMethod() {
System.out.println("我是Java9接口中的私有方法");
}
}

实现类MyInterfaceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.sunchaser.sparrow.javase.java9;

/**
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class MyInterfaceImpl implements MyInterface {

@Override
public void abstractMethod() {
System.out.println("实现类重写接口中的抽象方法");
}

@Override
public void defaultMethod() {
MyInterface.super.defaultMethod();
System.out.println("实现类重写接口中的默认方法");
}

public static void main(String[] args) {
// 接口中的静态方法只能通过接口进行调用
MyInterface.staticMethod();
// 实现类未继承接口中的静态方法,无法调用
// MyInterfaceImpl.staticMethod();

MyInterface impl = new MyInterfaceImpl();
impl.defaultMethod();

// 接口的私用方法,不能在接口外部被调用
// impl.privateMethod();
}
}

接口中的私有方法只能被接口中的默认方法调用。

钻石操作符的使用升级

java7的新特性中,钻石操作符<>可以进行从左到右的类型推断。

java7之前的写法:

1
List<String> list1 = new ArrayList<String>();

java7的写法:

1
List<String> list2 = new ArrayList<>();

但是当遇到匿名实现类时,钻石操作符就无法进行类型推断了,以下代码在java7&8中都会报错:

1
2
3
4
5
6
Comparator<Integer> comparator = new Comparator<>() {
@Override
public int compare(Integer o1, Integer o2) {
return 0;
}
};

而在java9中,支持了上述语法。

try-with-resources语法改进

jdk7的新特性中,引入了一个叫做try-with-resources的语法糖,任何实现了java.lang.AutoCloseablejava.lang.Closeable接口的对象都可以使用try-with-resource特性来实现对资源的异常处理和关闭,它保证了一个或多个资源在try语句的最后被关闭。

我们以序列化一个对象到文件中的代码为例,看看各版本的语法改进:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package com.sunchaser.sparrow.javase.java9;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;

/**
* try-with-resources
*
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class TryWithResources {

/**
* Java7之前
*/
private static void writeObjectBeforeJava7(Object obj, String path) throws IOException {
FileOutputStream fos = null;
ObjectOutputStream ops = null;
try {
fos = new FileOutputStream(path);
ops = new ObjectOutputStream(fos);
ops.writeObject(obj);
} finally {
try {
if (ops != null) {
ops.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

/**
* Java7&8
*/
private static void writeObjectInJava7(Object obj, String path) throws IOException {
try (FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream ops = new ObjectOutputStream(fos)) {
ops.writeObject(obj);
}
}

/**
* Java9
*/
private static void writeObjectInJava9(Object obj, String path) throws IOException {
FileOutputStream fos = new FileOutputStream(path);
ObjectOutputStream ops = new ObjectOutputStream(fos);
try (fos; ops) {
// fos和ops都被声明为了final,无法再被赋值。
// fos = new FileOutputStream(path);
ops.writeObject(obj);
}
}
}

java.lang.String存储结构变更

java9之前,java.lang.String一直使用char[]存储。在java9中,改成了byte[]加上@Stable注解标记,对于英文字母和数字等单字节字符组成的字符串来说节约了一点空间。

1
2
3
4
5
6
7
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
@Stable
private final byte[] value;

...
}

类似地,StringBuilderStringBuffer的父类AbstractStringBuilder中也将char[]修改为了byte[]

容器类新增of()方法

java9之前,如果不使用第三方类库,想创建一个不可变容器(List/Set/Map),代码写法不够简洁:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static void createImmutableCollectionBeforeJava9() {
// 创建不可变List方式一
List<String> list1 = new ArrayList<>();
list1.add("java");
list1.add("python");
list1.add("go");
list1 = Collections.unmodifiableList(list1);
System.out.println(list1);

// 创建不可变List方式二
List<String> list2 = Arrays.asList("java", "python", "go");
System.out.println(list2);

// 创建不可变Set
Set<String> set = Collections.unmodifiableSet(new HashSet<>(Arrays.asList("java", "python", "go")));
System.out.println(set);

// 创建不可变Map
Map<String, String> map = Collections.unmodifiableMap(new HashMap<String, String>() {
{
put("java", "1");
put("python", "2");
put("go", "3");
}
});
System.out.println(map);
}

java9List/Set/Map容器接口中新增的of静态方法可以很方便地进行创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private static void createImmutableCollectionInJava9() {
// 创建不可变List
List<String> list = List.of("java", "python", "go");
System.out.println(list);
// 创建不可变Set
Set<String> set = Set.of("java", "python", "go");
System.out.println(set);
// 创建不可变Map
Map<String, String> map1 = Map.of(
"java", "1",
"python", "2",
"go", "3"
);
System.out.println(map1);
Map<String, String> map2 = Map.ofEntries(
Map.entry("java", "1"),
Map.entry("python", "2"),
Map.entry("go", "3")
);
System.out.println(map2);
}

Stream API新增方法

Stream APIjava8的主要新特性之一,其中串行流体现了管道模式的思想,而并行流可以充分利用多核CPU的优势进行并行计算。

java9中,Stream接口新增了4个方法:

方法名描述
takeWhile从流中依次获取满足条件的元素,直到遇到第一个不满足条件的元素就立即停止获取。
dropWhile从流中依次删除满足条件的元素,直到遇到第一个不满足条件的元素就立即停止删除。
ofNullablejava8中的Stream流中的元素不能完全为null(仅一个元素则不能为null,多个元素则可以存在null)。
java9提供的ofNullable()方法允许我们创建一个单元素流,可以包含一个非空元素,也可以是一个空流。
iterateiterate重载方法,可以提供一个Predicate断言来指定什么时候结束迭代。

代码使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.sunchaser.sparrow.javase.java9;

import java.util.List;
import java.util.Optional;
import java.util.stream.Stream;

/**
* Stream API
*
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class StreamAPI {
public static void main(String[] args) {
List<Integer> list = List.of(1, 3, 5, 7, 9);

System.out.println("===================takeWhile===================");
// takeWhile:1, 3
list.stream().takeWhile(el -> el < 5).forEach(System.out::println);

System.out.println("===================dropWhile===================");
// dropWhile:5, 7, 9
list.stream().dropWhile(el -> el < 5).forEach(System.out::println);

System.out.println("===================ofNullable===================");
Stream<Object> stream = Stream.ofNullable(null);
System.out.println(stream.count());// 0

System.out.println("===================iterate===================");
// java8的终止方式
Stream.iterate(1, i -> i + 1).limit(5).forEach(System.out::println);
// java9的终止方式
Stream.iterate(1, i -> i <= 5, i -> i + 1).forEach(System.out::println);
}
}

java.util.Optional类新增多个方法

方法名描述
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)value非空则执行action;否则执行emptyAction
or(Supplier<? extends Optional<? extends T>> supplier)value非空返回当前Optional对象;否则返回supplier参数提供的Optional对象。
stream()Optional转化为Streamvalue非空返回仅包含此value的流;否则返回空流。

示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.sunchaser.sparrow.javase.java9;

import java.util.Optional;

/**
* java9中java.util.Optional类新增多个方法
* - ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
* - or(Supplier<? extends Optional<? extends T>> supplier)
* - stream()
*
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/17
*/
public class OptionalMethod {
public static void main(String[] args) {
Optional<Object> empty = Optional.empty();
Optional<String> op1 = Optional.of("java");
Optional<String> op2 = Optional.of("python");

// ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
// value非空则执行action;否则执行emptyAction
empty.ifPresentOrElse(System.out::println, () -> System.out.println("emptyAction"));// emptyAction
op1.ifPresentOrElse(System.out::println, () -> System.out.println("emptyAction"));// java

// or(Supplier<? extends Optional<? extends T>> supplier)
// value非空返回当前Optional对象;否则返回supplier参数提供的Optional对象。
Optional<Object> or1 = empty.or(() -> op2);
Optional<String> or2 = op1.or(() -> op2);
System.out.println(or1);// Optional[python]
System.out.println(or2);// Optional[java]

// stream():将Optional转为Stream
op1.stream().forEach(System.out::println);// java
}
}

新的HTTP客户端API

这个主要是代替原有的HttpURLConnection,提供了新的HttpClient。使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package com.sunchaser.sparrow.javase.java9;

import jdk.incubator.http.HttpClient;
import jdk.incubator.http.HttpRequest;
import jdk.incubator.http.HttpResponse;

import java.net.URI;
import java.util.concurrent.CompletableFuture;

/**
* jdk9全新HttpClient客户端
* 需要在module-info.java中requires引入jdk.incubator.httpclient;模块
*
* @author sunchaser admin@lilu.org.cn
* @since JDK9 2022/2/11
*/
public class NewHttpClient {
public static void main(String[] args) throws Exception {
syncHttpGet();
asyncHttpGet();
}

/**
* 异步HTTP调用,返回CompletableFuture
*/
private static void asyncHttpGet() throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = HttpRequest.newBuilder(URI.create("http://www.baidu.com"))
.header("user-agent", "sunchaser")
.GET()
.build();
HttpResponse.BodyHandler<String> bodyHandler = HttpResponse.BodyHandler.asString();
CompletableFuture<HttpResponse<String>> cf = httpClient.sendAsync(httpRequest, bodyHandler);

cf.thenApply(HttpResponse::body).thenAccept(System.out::println);

HttpResponse<String> httpResponse = cf.get();
String body = httpResponse.body();
System.out.println(body);
}

/**
* 同步HTTP调用
*/
private static void syncHttpGet() throws Exception {
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest httpRequest = HttpRequest.newBuilder(URI.create("http://www.baidu.com"))
.header("user-agent", "sunchaser")
.GET()
.build();
HttpResponse.BodyHandler<String> bodyHandler = HttpResponse.BodyHandler.asString();
HttpResponse<String> httpResponse = httpClient.send(httpRequest, bodyHandler);
int statusCode = httpResponse.statusCode();
String body = httpResponse.body();
System.out.println(statusCode);
System.out.println(body);
}
}