Java9
模块化
模块化有两个目标:
- 对JDK本身进行模块化
- 提供一个应用程序可以使用的模块系统
它们是密切相关的,JDK的模块化可通过使用与应用程序开发人员使用的相同的模块系统来实现.通过引入模块化系统,使得Java真正的获得了对模块的三个核心原则的支持.
它带来的最重要的几个好处是:
- 可靠的配置.模块系统在编译和运行之前会检查给定的模块组合是否满足所有依赖关系,从而导致更少的运行时错误(如因为多个版本的JAR都处于classpath中导致的
NoSuchMethodError
或NoClassDefFoundError
错误). - 强封装型.模块显示的声明了向外部公开的内容,未公开的内容是无法被外部访问的,这防止了来自其他模块的对其内部实现细节的依赖.
- 可扩展性.显式边界能够让开发团队并行工作,同时可创建可维护的代码库.
- 安全性.在JVM的最深层次上执行强封装,从而减少Java运行时的被攻击面,同时无法获得对敏感内部类的反射访问(Java是可以通过反射修改类的private成员的).
JDK把代码划分成了很多个模块,每个模块都清晰的定义了暴露的内容以及依赖的模块.下面是Java9的模块关系图:
#### 定义模块 模块拥有一个名称,并对相关的代码以及可能的其他资源拥进行分组,同时使用一个模块描述符进行描述.模块描述符保存在一个名为`module-info.java`的文件中.下面是`java.prefs`模块的`module-info.java`:1 | module java.prefs { |
模块描述的开头是关键字module
,后面跟着一个模块的名称.由于模块都位于一个全局命名空间中,因此模块名称必须是唯一的.与包名称一样,可以使用反向DNS符号(例如com.mycompany.project.somemodule)等约定来确保模块的唯一性.
模块描述的主体是有三行,其中requires
声明了对另一个模块的依赖.exports
是一个导出声明,告知了别的模块它们可以访问这个包,没有export的包是无法被别的模块访问的.最后一行的意思是声明这个模块是服务java.util.prefs.PreferencesFactory
的消费者.
下面列出了模块描述体中所有支持的语法:
requires module.name
:表示本模块对一个模块的依赖,如果缺少这个模块则会无法编译和运行.requires transitive module.name
:和requires
基本一样,额外说明这个依赖是传递的,依赖本模块的其他模块会自动继承对这一依赖.当只需要其他模块供内部使用时,使用requires就足够了.但另一方面,如果需要在导出类型中使用另一个模块中的类型,那么就要使用requires transitive
.requires static module.name
:表示在编译的时候对这个模块是强依赖,但在运行时这个依赖是可选的.exports pkg.name
:表示导出这个包,依赖这个模块的其他模块可以访问这个包.exports pkg.name to module.name
:表示导出这个包给特定模块.可以使用逗号作为分隔符限定导出给多个模块.opens pkg.name
:表示公开这个包,其他模块可以使用反射访问这个包中的类的私有成员.opens pkg.name to module.name
:表示公开这个包给特定模块.同样可以指定对多个模块公开.uses service.class.name
:它用于声明对一个服务接口的依赖.如果其他模块有通过provides
声明实现这个服务接口,那么这个模块就可以通过java.util.ServiceLoader
消费这些实现(如果实现就在同一个包中,也可以被消费).provides service.class.name with service.class.impl.name
:表示为某个服务接口提供实现类.同样可以提供多个实现类.
上面这些不同的语法提供了不同的可访问性.可见性包括两个方面:编译时可访问性和反射可访问性.下面是反映这些可访问性规则的表:
### JShell JShell是Java9加入的一个交互式的REPL(Read-Eval-Print Loop)工具,它可以对Java的声明,表达式进行求值,而且和一般的Java代码不一样,不需要它们处于一个方法中.而且它还有下面的几个便利的特性:- tab键补全
- 自动补全声明尾部的分号
- 自动import需要的包
- 自动补全定义(为未绑定变量的表达式分配变量名)
JShell的语法如下:
jshell <options> <load files>
具体用法可以使用jshell -h
查看.要退出JShell,可以输入/ex
或/exit
.除此之外,JShell中还可以输入其他一些命令,可以在JShell中输入/?
或/help
来查看.
JShell的主要作用是执行一些简短的实验性质的代码,或者作为Java脚本(一般以.jsh
结尾)的执行器.
Process API
Java9中加入了两个新的进程相关的接口:ProcessHandle
和ProcessHandle.Info
.一个ProcessHandle
标识了一个操作系统进程并提供了管理进程的方法.Info
存储了进程相关的信息.这两个接口的引入解决了下面几个问题.
判断进程是否存活
在之前版本的Java中,只有通过进程的PID去标识一个进程,这种方法的问题是:PID是可以重用的.
对于桌面和服务器操作系统,它们会尽量的不重用PID,但对于一些嵌入式系统,PID是一个16位的数字,所以很容易发生重用.但有了ProcessHandle
,使用它的isAlive()
方法就可以避免这个问题.
获取进程PID
获取进程的PID在实际编程中是很有用的,在之前,Java的Process
类没有任何方法可以获取PID.而现在,可以调用toHandle()
方法把Process
对象转换成ProcessHandle
对象,然后调用pid()
方法就可以得到进程PID.
如果需要得到当前进程的PID,可以调用ProcessHandle.current().pid()
.
获取进程信息
ProcessHandle
对象调用info()
方法可以得到一个ProcessHandle.Info
对象,通过它可以获取下面的信息:
command()
方法返回Optional<String>
,包含了启动进程的命令.arguments()
方法返回Optional<String[]>
,包含启动进程的命令的参数.commandLine()
返回Optional<String>
,包含了启动进程的命令和参数.startInstant()
返回Optional<Instant>
,包含了进程的起始时间.totalCpuDuration()
返回Optional<Duration>
,包含了进程从开始起占用的CPU时间.user()
返回Optional<String>
,包含了进程的所有者的名字.
为什么返回的类型都是Optional
?因为无法保证操作系统或Java的实现可以返回这些信息.但对于大多数操作系统来说,这些信息都是可以获得的.
列出进程
ProcessHandle
提供了方法用于列出进程,包括:
- 列出进程的所有子进程,使用
children()
实例方法. - 列出进程的所有后代进程,使用
descendants()
实例方法. - 列出系统的所有进程,使用
allProcesses()
静态方法.
这些方法返回的都是Stream<ProcessHandle>
.要注意,这些方法只返回在调用的时候存活的进程,但不保证在遍历返回结果的时候这些进程仍然存活.
等待进程结束
之前Java等待进程结束,需要使用使用循环来定期调用isAlive()
方法.ProcessHandle
提供了onExit()
方法来更优雅的完成这一任务.它返回的类型是CompletableFuture<ProcessHandle>
(CompletableFuture
是Java8加入的异步API),你可以通过handle.onExit().join()
来等待进程结束.你可以多次调用onExit()
,每次返回的都是不同的CompletableFuture
对象,它们都和相同的进程关联.
终止进程
ProcessHandle
提供了destroy()
和destroyForcibly()
方法来终止进程.前者是尽量平稳的结束进程,而后者是强行终止进程.但进程的终止依赖很多因素,所以调用这两个方法返回true
的话,不意味着进程已经终止了,只表示操作系统接受了你的终止请求,具体什么时候终止是无法确定的.
便利的集合工厂方法
1 | List<Integer> list = List.of(1, 2, 3); |
输出:
class java.util.ImmutableCollections.ListN
class java.util.ImmutableCollections.MapN
class java.util.ImmutableCollections.SetN
可以看出,这些生成的对象的类型都是不可变集合,所以我们不能对其中的元素进行修改或者排序.
还加入了copyOf
方法用于拷贝集合,它在List
,Set
,Map
都有.
1 | List<String> copy = List.copyOf(strings); |
输出:
[1, 2]
class java.util.ImmutableCollections$List12
可以看出,得到的拷贝也是不可变集合.
Stream API增强
加入了三个新的方法:ofNullable
,takeWhile
,dropWhile
,iterate
.
ofNullable
它是对of
的增强,在of
中传入null
会抛NullPointerException
,这个新方法不会,而只会返回一个空的Stream
.
1 | long count = Stream.ofNullable(null).count(); |
0
takeWhile
它可以传入一个Predicate
,它会获取最长的符合这个Predicate
的头Stream
.
1 | List<Integer> lessThan4 = Stream.of(1, 2, 3, 4, 5).takeWhile(i -> i <= 3).collect(Collectors.toList()); |
[1,2,3]
dropWhile
它可以传入一个Predicate
,它会获取最长的不符合这个Predicate
的尾Stream
.
1 | List<Integer> largerThan3 = Stream.of(1, 2, 3, 4, 5).dropWhile(i -> i < 4).collect(Collectors.toList()); |
[4,5]
iterate
其实之前已经有了存在一个iterate
方法,这次是加入了一个重载方法,可以传入一个Predicate
作为结束条件,一旦生成的元素不符合这个Predicate
,那么这个Stream就会结束.
1 | List<Integer> lessThan100 = Stream.iterate(1, i -> i < 100, i -> i + 1).collect(Collectors.toList()); |
[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, 62, 63, 64, 65, 66, 67, 68, 69, 70,
71, 72, 73, 74, 75, 76, 77, 78, 79, 80,
81, 82, 83, 84, 85, 86, 87, 88, 89, 90,
91, 92, 93, 94, 95, 96, 97, 98, 99]
InputStream增强
为InputStream
加入了一个transferTo
,用于把输入流传递给输出流.
1 | FileInputStream fileInputStream = new FileInputStream("build.gradle"); |
对Project Coin的改进
Project Coin是Java7的时候的引入的,它包括下面几个特性:
swtich
支持String类型- 二进制整形字面量
- 数字字面量支持下划线
catch
可以一次捕获多种异常- 简化泛型类的实例化
try-with-resource
表达式- 改进对不定参数方法的调用
Java9中,对其进行了五个方面的改进:
@SafeVarargs
可以加在private方法上- try-with-resource中不再必须声明新的对象,可以传入已经声明的的
final
或等价于final
的对象 - 不再支持使用下划线
_
作为变量名 - 接口支持有具体实现的私有方法
Variable handlers
随着Java中的并发和并行编程的不断扩大,我们经常会需要对某个类的字段进行原子或有序操作,但是JVM对Java开发者所开放的权限非常有限.例如:如果要原子性地增加某个字段的值,到目前为止我们可以使用下面三种方式:
- 使用
AtomicInteger
来达到这种效果,这种间接管理方式增加了空间开销,还会导致额外的并发问题 - 使用原子性的
FieldUpdaters
,由于利用了反射机制,操作开销也会更大 - 使用
sun.misc.Unsafe
提供的JVM内置函数API,虽然这种方式比较快,但它会损害安全性和可移植性,当然在实际开发中也很少会这么做.
VarHandle
的出现替代了java.util.concurrent.atomic
和sun.misc.Unsafe
的部分操作.并且提供了一系列标准的内存屏障操作,用于更加细粒度的控制内存排序,在安全性,可用性和性能上都要优于现有的API.
这些操作主要是:
- 访问/修改对象成员
- 访问/修改数组元素
去除过期的类或字段的import
声明的编译警告
以前,如果代码中导入了被@Deprecated
标注的类或方法,编译的时候会有警告.现在,编译器在下列的情况下不会产生过时告警:
- 使用
@Deprecated
标注类或方法 - 使用
@SuppressWarnings
标注类或方法 - 产生告警的代码和声明在父类中
- 只用
import
导入过时的类,但没有在其他任何地方使用
JDK9的其他一些改进
Compact strings
紧凑字符串.之前的字符串的底层使用的是char[]
存储.一个char要占16bits,但实际开发过程占,使用最多的是Latin-1
字符,它们只需占用8bits或者说1byte.现在内部使用byte[]
来存储,节约了内存空间.
改进的竞争锁
Java对多个线程共享的数据的管理做的不错,它为每个对象和类分配一个monitor,这些monitor的锁在任何时候只会被一个线程控制.从本质上说,这些锁就是给予了线程这些对象的monitor.如果有其他线程在等待这个锁的队列中,那么就叫竞争锁.
这是为了提高JVM管理竞争锁的整体性能,它的提升主要体现在下面三个操作:
- Faster monitor enter
- Faster monitor exit
- Faster notifications
分段的代码缓存
这个改进使得JVM有更快的更高效的运行时.它的核心是把代码缓存分成三块,每一块存储特定类型的编译好的代码,
代码缓存区是一个固定大小的区域.默认情况下,其中有3MB是固定存放non-methed代码的,剩余的部分平均分配给profiled和non-profiled代码.用户可以通过JVM参数手动调节. > -xx:NonMethodCodeHeapSize > -xx:ProfiledCodeHeapSize > -xx:NonProfiledCodeHeapSize优化字符串拼接
之前,javac
会把"hello,"+"world"
这样的代码在编译的时候转换成调用StringBuiler::append
方法.现在,javac
生成的字节码变成了使用invokedynamic
指令,以后对字符串拼接的优化不再需要改动编译器生成的字节码,因为不再和具体的实现绑定了.
更智能的javac
,第二阶段
sjavac
,一个更智能的javac
,支持增量编译,加快了编译速度,但目前还没有替代javac
.这次在第一阶段的基础上增强了它的稳定性和可移植性.
处理Lint和Doclint的告警
之前的JDK编译的时候会由javac
发出很多lint(字节码和源码检查)和doclint(javadoc检查)告警,这个提案就是为了处理这些告警,至少是核心库的告警.
javac
中使用分层归因
javac
会在编译的时候进行类型推断,之前使用的是一种推断归因工具,这种工具的类型推断结果虽然准确但性能不高,现在使用一种新的叫做分层归因工具替代它,目的是为了加快编译速度.
Annotations pipeline 2.0
在Java8中,引入了三个特性:
- lambda表达式
- 重复注解
- 类型注解
这三个特性影响了Java注解,但javac
没有对原有的对注解的处理做修改,而是加入了一些硬编码对这些新的注解进行处理,但这种方式效率不高而且维护麻烦,所以这个改进是为了去掉这些硬编码,重构了处理注解的代码.
关键区域的预留栈空间
为关键代码区域加上保留的线程栈空间,以此来减少发生StackOverflowError
,的可能性.如果当前执行的方法的栈空间不够了,但这个方法有注解jdk.internal.vm.annotation.ReservedStackAccess
,那么就会分配一段临时的栈空间让方法继续执行.当然,如果需要的额外空间很大,最终还是会抛出StackOverflowError
.
把G1垃圾收集器作为默认的垃圾收集器
之前默认的收集器是Parallel GC,它的优点是高吞吐量.但总体上来说,较少的暂停时间是比高吞吐量更重要的,G1在前者是有着很好的表现的,而且G1经历了8和9已经非常成熟,所以使用它作为默认垃圾收集器.
Java10
类型推断增强
Java10的类型推断功能进行了强化.类型推断指的是编译器根据解析到的存在于代码中的信息,如字面量,操作,方法调用或声明,通过一些规则,推断出变量的类型.
本地变量的类型推断
这次的提升使得局部变量的声明更加简单,不再需要显示的写明类型,而仅仅使用标识符var
,编译器就可以自动推断出它的类型.但这种用法也有一些限制:
- 不能在没有初始化的声明中使用
- 不能在同时声明多个变量的时候使用
- 不能用于数组字面量
- 不能用于元表达式,如lambda和方法引用
- 不能再
catch
中使用
var
可以在for循环中使用:
1 | var numbers = List.of("a", "b", "c"); |
var和原始类型
var
用于原始类型应该是最常见,但其中也有一些使人迷惑的地方.实际上关于原始类型的一些类型推断的规则并没有被修改,如:
- 整数字面量的类型是
int
. - 浮点数字面量的类型是
double
. - 算术运算中,如果其中一个操作数是
char
,byte
,short
,orint
,那么结果至少会提升为int
;如果其中一个操作数是long
,float
或double
,那么结果至少会提升为long
,float
或double
.
所以如果你期待能把var n= 5/2
中的n
推断为float
或double
,那你可能要失望了.
派生类型的类型推断
在之前的Java中,一种常用的模式是把一个具体类型的对象赋值给一个声明为它的基类的变量.但如果你使用var
声明这个变量,那么类型推断的结果为具体的类型.这和你的期望可能不符,需要注意.
1 | class Parent { |
如果有一个既可能返回Parent
类型的对象,也可能返回Child
对象的方法,它的返回值类型会被推断为什么?
1 | class Test{ |
所以推断的结果是Parent
类型.这和以前的推断规则是一样的.
如果一个变量使用var
声明,这个对象的类型是在编译期确定的.如果一个方法的返回值被赋值给一个var
声明的变量,那么编译器就把方法的返回类型推断为这个变量的类型.
接口的类型推断
接口的推断规则和派生类是一样的.把接口实现类型的对象赋值给var
声明的变量,那么类型推断的结果是实现类型.如果有一个返回值的实际类型不确定的方法,把它的返回值赋值给var
声明的变量,类型推断的结果就是这个方法声明的返回类型.
var和数组
使用var
不意味着简单的把变量的类型声明换成var
.数组就是一个例子:
1 | char name[] = {'S','t','r','i','n','g'}; |
你不能使用下面的代码来声明这个数组:
1 | var[] name = {'S', 't', 'r', 'i', 'n', 'g'}; |
正确的做法是:
1 | var name = new char[]{'S','t','r','i','n','g'}; |
泛型的类型推断
带泛型的类型使用var,需要小心.比如下面这样的写法:
1 | var names = new ArrayList<>(); |
这显示出编译器无法确定变量names
的类型参数是什么,所以可以放入任意类型的元素.这显然违背了声明这个变量的初衷.
正确的写法:
1 | var name = new ArrayList<String>(); |
JDK10的其他一些改进
Thread-local handshake
Java10加入的对多线程的改进,使得JVM可以不需要进行全局的暂停就可以停止单个线程,这可以提高偏向锁撤销的速度,降低总体VM延迟,提供更安全的stack trace,减少内存屏障的使用.要启用这个特性,需要JVM参数:
XX:ThreadLocalHandshakes
####s 新的版本格式
新的版本命名格式,原来是使用8u211
这样的格式,现在使用的是形如12.0.1
的格式.
合并JDK代码
Java9由8个不同的代码库组成,如下图:
到了Java10,这几个仓库合成了一个,这样使得开发,维护和更新更简单.支持在其他内存设备上分配堆空间
Java10对非DRAM的内存设备提供了支持,要启用需要JVM参数:
XX:AllocateHeapAt=file-system path
Unicode 8.0.0
支持Unicode8.0.0标准
根证书
在JDK中加入默认的根证书
实验性质的基于Java的JIT编译器
即Graal,它也是GraalVM的默认JIT编译器.它使用JVM的编译器接口(JDK9中加入的).它不是为了和现有的JIT竞争,而是为了探索用于HotSpot的Java-on-Java技术.
JDK11
lambda的参数支持var
语法
var
现在可以在lambda表达式中使用,但要使用的话必须加在所有的形参上:
1 | Comparatorsort((var s1, var s2) -> s1.length() - s2.length()); |
如果lambda只有一个参数,你想使用var
,那你不能省略()
.
1 | var x -> x > 10; // Won't compile |
现在lambda表达式的参数可以加上注解:
1 | var numbers = new ArrayList<String>() { |
Epsilon GC
Epsilon GC是一个no-operation (no-op) GC,它不会做任何垃圾收集工作,它只处理内存分配,当堆内存空间耗尽了,JVM就会停止工作.
为什么要加入这样一个很奇怪的GC?实际上,它是用来预测你的程序的性能的.因为影响Java程序性能的除了GC,还有可能是系统本身的问题或编译器的问题,所以你可以把一个程序用Epsilon GC运行,然后使用另一些GC运行,通过对比,你就可以排除了系统或编译器的因素,只需要从内存占用,吞吐量,延迟这些方面考量,找到最适合这个程序的GC.
开启Epsilon GC的方法是在运行程序的时候加如下的参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
新的HTTP客户端
在Java1.1的时候首次加入了Http客户端,对应的类是HttpURLConnection
,但它存在很多问题,所以开发者们并不喜欢,而是使用其他的第三方的API.
新的HTTP客户端支持HTTP2.0,支持异步非阻塞的请求,并提供了更易于使用的API.实际上从9开始把这个新的客户端加入了孵化器,经过10的一系列修改,在11中最终发布.
例子:
构建request
1 | HttpRequest.Builder requestBuilder = HttpRequest.newBuilder(); |
构建client
1 | HttpClient.Builder clientBuilder = HttpClient.newBuilder(); |
发送同步请求请求
1 | HttpResponse<String> response = client.send(httpRequest, HttpResponse.BodyHandlers.ofString()); |
发送异步请求
1 | CompletableFuture<HttpResponse<String>> responseFuture = client |
可以在这里进一步了解.
字符串增强
String
加入了一些便利的方法:isBlank
,strip
,stripTrailing
,stripLeading
,repeat
,lines
.
isBlank
判断字符串是否为空或全是空格
1 | " ".isBlank(); |
true
strip
这个方法和trim
的作用是一样的,去掉字符串首尾的空格.区别是它们对空格的定义不同:trim
认为任何小于等于U+0020
(这是字符的unicode码)的字符是空格,而strip
认为Character.isWhitespace(int)
返回true
的字符就是空格.
1 | char c = '\u2000'; |
abc
abc
stripTrailing
把字符串尾部的空格去掉
1 | " java ".stripTrailing() |
java
stripLeading
把字符串的头部的空格去掉
1 | " java ".stripLeading() |
java
repeat
重复字符串特定的次数并返回.
1 | "java".repeat(3) |
javajavajava
lines
把字符串以换行符话划分并返回一个Stream<String>
.
1 | "A\nB\nC".lines().count() |
3
Optional增强
从9到11,每个版本都为Optional
类加入了一个或几个新方法,包括:isEmpty
,ifPresentOrElse
,or
,stream
,orElseThrow
.
isEmpty
1 | boolean isEmpty = Optional.of("1").isEmpty(); |
false
ifPresentOrElse
它传入一个Consumer
函数和一个Runnable
函数.当Optional
不为空,那么执行Consumer
函数;如果是空的,那么运行Runnable
函数.这个方法避免了之前那种不得不使用if-else来执行Optional非空和空这两种情况的分支.
1 | Optional.<String>empty().ifPresentOrElse(System.out::println,() -> |
no string
or
它传入一个会返回Optional
的Supplier
.当Optional
不为空,那么直接返回调用这个方法的对象;如果是空的,那么返回Supplier
函数的结果.
1 | Optional<String> or = Optional.<String>empty().or(() -> Optional.of("1")); |
Optional[1]
stream
它可以把Optional
转换成Stream
.
1 | List<Integer> list = Optional.of(1).stream().collect(Collectors.toList()); |
[1]
orElseThrow
如果Optional
不为空,那么直接返回其中的值;如果为空,那么直接抛出异常NoSuchElementException
.
1 | String s = Optional.<String>empty().orElseThrow(); |
Exception in thread “main” java.util.NoSuchElementException: No value present
at java.base/java.util.Optional.orElseThrow(Optional.java:382)
ZGC
ZGC(Z Garbage Collector),一个全新的可扩展,低延迟的GC.它可以处理从KB到TB级别的堆内存.作为一个并发的垃圾收集器,它承诺它造成的系统延迟不会超过10毫秒,即便是对于很大的堆内存.
它最大的一个特点是:它是一个并发的收集器,包括对内存的标记,复制,移动都是并发的.它还有一个并发的引用处理器,用于处理各种类型的引用(soft reference,weak reference,phantom reference).
它和其他的HotSpot虚拟机的GC不同的是,它使用了两种新技术:着色指针和读屏障.具体的实现细节可以看这篇文章.
开启ZGC的方法,在运行的时候加入下面的参数:
-XX:+UnlockExperimentalVMOptions -XX:+UseZGC
它目前还处于实验阶段,还有一些不足之处(如没有分代),需谨慎使用.它目前只支持Linux 64位系统.
JDK11的其他一些改进
新增读取文件内容的方法
1 | String content = Files.readString(Path.of("pom.xml")); |
基于嵌套的访问控制
以前的Java嵌套类对于宿主类的私有字段的访问是通过放宽这个私有字段的访问控制或加入桥接方法来实现的.
例子:
1 | public class Outer { |
这段代码的反编译结果:
1 | // Decompiled class Outer |
这个改进使得内部类不再需要放宽这个私有字段的访问控制或加入桥接方法,就可以访问外部类的私有成员,这个改动直接体现在编译后的字节码中:
1 | public class Outer { |
动态类文件常量
这个改动扩展了类文件格式,加入了CONSTANT_Dynamic
这个新的常量类型.这个常量池用于存储和invokedynamic
指令相关的常量.
针对AArch64处理器改进一些方法的实现
对于AArch64处理器改进现有的string,array相关函数,并新实现java.lang.Math的sin,cos,log方法.
删除Java EE及CORBA模块
将java9标记废弃的Java EE及CORBA模块移除掉,具体如下:
- xml相关的java.xml.ws,java.xml.bind,java.xml.ws,java.xml.ws.annotation,jdk.xml.bind,jdk.xml.ws被移除,只剩下java.xml,java.xml.crypto,jdk.xml.dom这几个模块
- java.corba,java.se.ee,java.activation,java.transaction被移除,但是新增一个java.transaction.xa模块
支持Unicode10
升级现有的API,支持Unicode10.0.0
支持TLS1.3
支持RFC 8446中的TLS 1.3版本
废弃JS引擎Nashorn
废除Nashorn javascript引擎,在后续版本准备移除掉,有需要的可以考虑使用GraalVM