☕Java9新特性 | 字数总计: 3.4k | 阅读时长: 15分钟 | 阅读量: |
java9
于2017.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-module1
和java9-module2
。
java9-module1
模块提供两个包在java9-module1
中创建2
个包分别为util
和entity
,然后分别提供一个PrintUtils
和一个User
类内容分别如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.sunchaser.sparrow.javase.java9.util;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;public class User { private String name; }
创建java9-module1
的module-info.java
文件并导出对外提供的util
模块
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-module2
的pom.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-module2
的module-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;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'
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-module2
的pom.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
代码、定义变量&方法和计算表达式等:
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;public interface MyInterface { String NAME = "interface" ; void abstractMethod () ; default void defaultMethod () { System.out.println("我是Java8接口中的默认方法" ); privateMethod(); } static void staticMethod () { System.out.println("我是Java8接口中的静态方法" ); } 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;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(); MyInterface impl = new MyInterfaceImpl (); impl.defaultMethod(); } }
接口中的私有方法只能被接口中的默认方法调用。
钻石操作符的使用升级 在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.AutoCloseable
或java.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;public class TryWithResources { 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(); } } } private static void writeObjectInJava7 (Object obj, String path) throws IOException { try (FileOutputStream fos = new FileOutputStream (path); ObjectOutputStream ops = new ObjectOutputStream (fos)) { ops.writeObject(obj); } } private static void writeObjectInJava9 (Object obj, String path) throws IOException { FileOutputStream fos = new FileOutputStream (path); ObjectOutputStream ops = new ObjectOutputStream (fos); try (fos; ops) { 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; ... }
类似地,StringBuilder
和StringBuffer
的父类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); }
而java9
在List/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 API
是java8
的主要新特性之一,其中串行流体现了管道模式的思想,而并行流可以充分利用多核CPU
的优势进行并行计算。
在java9
中,Stream
接口新增了4
个方法:
方法名 描述 takeWhile
从流中依次获取满足条件的元素,直到遇到第一个不满足条件的元素就立即停止获取。 dropWhile
从流中依次删除满足条件的元素,直到遇到第一个不满足条件的元素就立即停止删除。 ofNullable
java8
中的Stream
流中的元素不能完全为null
(仅一个元素则不能为null
,多个元素则可以存在null
)。java9
提供的ofNullable()
方法允许我们创建一个单元素流,可以包含一个非空元素,也可以是一个空流。iterate
iterate
重载方法,可以提供一个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;public class StreamAPI { public static void main (String[] args) { List<Integer> list = List.of(1 , 3 , 5 , 7 , 9 ); System.out.println("===================takeWhile===================" ); list.stream().takeWhile(el -> el < 5 ).forEach(System.out::println); System.out.println("===================dropWhile===================" ); 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()); System.out.println("===================iterate===================" ); Stream.iterate(1 , i -> i + 1 ).limit(5 ).forEach(System.out::println); 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
转化为Stream
。value
非空返回仅包含此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;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" ); empty.ifPresentOrElse(System.out::println, () -> System.out.println("emptyAction" )); op1.ifPresentOrElse(System.out::println, () -> System.out.println("emptyAction" )); Optional<Object> or1 = empty.or(() -> op2); Optional<String> or2 = op1.or(() -> op2); System.out.println(or1); System.out.println(or2); op1.stream().forEach(System.out::println); } }
新的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;public class NewHttpClient { public static void main (String[] args) throws Exception { syncHttpGet(); asyncHttpGet(); } 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); } 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); } }