Jdk8-19新特性
# JDK8新特性
重要特性:Lambda表达式、函数式接口、方法引用、Stream流式API、采用MetaSpace代替了永久代(PermGen Space)
# 1.Lambda表达式
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力。
# 2.并行(parallel)数组
Java 8增加了大量的新方法来对数组进行并行处理。可以说,最重要的是parallelSort()方法,因为它可以在多核机器上极大提高数组排序的速度。下面的例子展示了新方法(parallelXxx)的使用。
# 3.接口可以添加默认方法和静态方法,也就是定义不需要实现类实现的方法
Java 8用默认方法与静态方法这两个新概念来扩展接口的声明。默认方法与抽象方法不同之处在于抽象方法必须要求实现,但是默认方法则没有这个要求,就是接口可以有实现方法,而且不需要实现类去实现其方法。我们只需在方法名前面加个default关键字即可实现默认方法。为什么要有这个特性?以前当需要修改接口的时候,需要修改全部实现该接口的类。而引进的默认方法的目的是为了解决接口的修改与现有的实现不兼容的问题。
默认方法语法格式如下:
public interface Vehicle {
default void print(){
System.out.println("我是一辆车!");
}
}
2
3
4
5
6
7
8
9
当出现这样的情况,一个类实现了多个接口,且这些接口有相同的默认方法,这种情况的解决方法:
是创建自己的默认方法,来覆盖重写接口的默认方法
可以使用 super 来调用指定接口的默认方法
Java 8 的另一个特性是接口可以声明(并且可以提供实现)静态方法。在JVM中,默认方法的实现是非常高效的,并且通过字节码指令为方法调用提供了支持。默认方法允许继续使用现有的Java接口,而同时能够保障正常的编译过程。尽管默认方法非常强大,但是在使用默认方法时我们需要小心注意一个地方:在声明一个默认方法前,请仔细思考是不是真的有必要使用默认方法,因为默认方法会带给程序歧义,并且在复杂的继承体系中容易产生编译错误。
# 4.方法引用
方法引用提供了非常有用的语法,可以直接引用已有Java类或对象(实例)的方法或构造器。与lambda联合使用,方法引用可以使语言的构造更紧凑简洁,减少冗余代码。
定义了4个方法的Car这个类作为例子,区分Java中支持的4种不同的方法引用。
public static class Car {
public static Car create( final Supplier< Car > supplier ) {
return supplier.get();
}
public static void collide( final Car car ) {
System.out.println( "Collided " + car.toString() );
}
public void follow( final Car another ) {
System.out.println( "Following the " + another.toString() );
}
public void repair() {
System.out.println( "Repaired " + this.toString() );
}
}
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
第一种方法引用是构造器引用,它的语法是Class::new,或者更一般的Class< T >::new。请注意构造器没有参数。
final Car car = Car.create( Car::new );
final List< Car > cars = Arrays.asList( car );
2
3
第二种方法引用是静态方法引用,它的语法是Class::static_method。请注意这个方法接受一个Car类型的参数
cars.forEach( Car::collide );
第三种方法引用是特定类的任意对象的方法引用,它的语法是Class::method。请注意,这个方法没有参数。
cars.forEach( Car::repair );
第四种方法引用是特定对象的方法引用,它的语法是instance::method。请注意,这个方法接受一个Car类型的参数
final Car police = Car.create( Car::new );
cars.forEach( police::follow );
2
3
# 5.重复注解,同一个注解可以使用多次
(1)可以进行重复注解
自从Java 5引入了注解机制,这一特性就变得非常流行并且广为使用。然而,使用注解的一个限制是相同的注解在同一位置只能声明一次,不能声明多次。Java 8打破了这条规则,引入了重复注解机制,这样相同的注解可以在同一地方声明多次。
重复注解机制本身必须用@Repeatable注解。事实上,这并不是语言层面上的改变,更多的是编译器的技巧,底层的原理保持不变。
(2)扩展注解的支持
Java 8扩展了注解的上下文。现在几乎可以为任何东西添加注解:局部变量、泛型类、父类与接口的实现,就连方法的异常也能添加注解。
# 6.引入Optional来避免空指针
Optional类实际上是个容器:它可以保存类型T的值,或者仅仅保存null。Optional 类的引入很好的解决空指针异常。Optional提供很多有用的方法,这样我们就不用显式进行空值检测。尽量避免在程序中直接调用Optional对象的get()和isPresent()方法,避免使用Optional类型声明实体类的属性。
(1)Optional.of(T t) : 创建一个 Optional 实例
(2)Optional.empty() : 创建一个空的 Optional 实例
(3)Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例
(4)isPresent() : 判断是否包含值
(5)orElse(T t) : 如果调用对象包含值,返回该值,否则返回t
(6)orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值
(7)map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回Optional.empty()
(8)flatMap(Function mapper):与 map 类似,要求返回值必须是Optional
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 1.创建optional对象,一般用ofNullable()而不用of():
(1)empty() :用于创建一个没有值的Optional对象:Optional<String> emptyOpt = Optional.empty();
(2)of() :使用一个非空的值创建Optional对象:Optional<String> notNullOpt = Optional.of(str);
(3)ofNullable() :接收一个可以为null的值:Optional<String> nullableOpt = Optional.ofNullable(str);
2
3
4
5
# 2.判断Null
(1)isPresent():如果创建的对象实例为非空值的话,isPresent()返回true,调用get()方法会返回该对象,如果没有值,调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。
# 3.获取对象
(1)get()
# 4.使用map提取对象的值
如果我们要获取User对象中的roleId属性值,常见的方式是先判断是否为null然后直接获取,但使用Optional中提供的map()方法可以以更简单的方式实现
# 5.使用orElse方法设置默认值
Optional类还包含其他方法用于获取值,这些方法分别为:
(1)orElse():如果有值就返回,否则返回一个给定的值作为默认值;
(2)orElseGet():与orElse()方法作用类似,区别在于生成默认值的方式不同。该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;
(3)orElseThrow():与前面介绍的get()方法类似,当值为null时调用这两个方法都会抛出NullPointerException异常,区别在于该方法可以指定抛出的异常类型。
# 6.使用filter()方法过滤
filter()方法可用于判断Optional对象是否满足给定条件,一般用于条件过滤,在代码中,如果filter()方法中的Lambda表达式成立,filter()方法会返回当前Optional对象值,否则,返回一个值为空的Optional对象。:
# 7.引入Streams相关的API
Stream流有一些特性:
Stream流不是一种数据结构,不保存数据,它只是在原数据集上定义了一组操作。
这些操作是惰性的,即每当访问到流中的一个元素,才会在此元素上执行这一系列操作。
Stream不保存数据,故每个Stream流只能使用一次。
所以这边有两个概念:流、管道。元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。这里有2个操作:中间操作、最终操作。
中间操作:返回结果都是Stream,故可以多个中间操作叠加。
终止操作:用于返回我们最终需要的数据,只能有一个终止操作。
使用Stream流,可以清楚地知道我们要对一个数据集做何种操作,可读性强。而且可以很轻松地获取并行化Stream流,不用自己编写多线程代码,可以更加专注于业务逻辑。默认情况下,从有序集合、生成器、迭代器产生的流或者通过调用Stream.sorted产生的流都是有序流,有序流在并行处理时会在处理完成之后恢复原顺序。无限流的存在,侧面说明了流是惰性的,即每当用到一个元素时,才会在这个元素上执行这一系列操作。
# 8.引入新的Date/Time相关的API
Java 8通过发布新的Date-Time API (JSR 310)来进一步加强对日期与时间的处理。在旧版的 Java 中,日期时间 API 存在诸多问题,比如:
非线程安全 − java.util.Date 是非线程安全的,所有的日期类都是可变的,这是Java日期类最大的问题之一。
设计很差 − Java的日期/时间类的定义并不一致,在java.util和java.sql的包中都有日期类,此外用于格式化和解析的类在java.text包中定义。java.util.Date同时包含日期和时间,而java.sql.Date仅包含日期,将其纳入java.sql包并不合理。另外这两个类都有相同的名字,这本身就是一个非常糟糕的设计。
时区处理麻烦 − 日期类并不提供国际化,没有时区支持,因此Java引入了java.util.Calendar和java.util.TimeZone类,但他们同样存在上述所有的问题。
Java 8 在 java.time 包下提供了很多新的 API。以下为两个比较重要的 API:
Local(本地) − 简化了日期时间的处理,没有时区的问题。
Zoned(时区) − 通过制定的时区处理日期时间。
新的java.time包涵盖了所有处理日期,时间,日期/时间,时区,时刻(instants),过程(during)与时钟(clock)的操作。
# 9.并发(Concurrency)
在新增Stream机制与lambda的基础之上,在java.util.concurrent.ConcurrentHashMap
中加入了一些新方法来支持聚集操作。同时也在java.util.concurrent.ForkJoinPool
类中加入了一些新方法来支持共有资源池(common pool)
新增的java.util.concurrent.locks.StampedLock
类提供一直基于容量的锁,这种锁有三个模型来控制读写操作(它被认为是不太有名的java.util.concurrent.locks.ReadWriteLock
类的替代者)。
在java.util.concurrent.atomic包中还增加了下面这些类:
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder
# JDK9新特性
# 1.集合新增方法
jdk9为所有集合(List/Set/Map)都增加了of和copyOf方法,用来创建不可变集合,即一旦创建就无法再执行添加、删除、替换、排序等操作,否则将报java.lang.UnsupportedOperationException异常。一般在特定场景下用还是可以的,不过如果引用了guava库的话推荐还是使用guava把hhhh,例子如下:
List strs = List.of("Hello", "World");
List strsCopy = List. copyOf(strs);
Set strs = Set.of("Hello", "World");
Map maps = Map.of("Hello", 1, "World", 2);
2
3
4
5
6
7
# 2.私有接口方法
jdk8提供了接口的默认方法(default)和静态方法,打破了之前接口只能定义方法而不能存在行为。jdk9则是允许接口定义私有方法,私有方法可以作为通用方法放在默认方法中调用,不过实际中并无多大用处,至少对我来说。
# 3.垃圾收集机制
jdk9把G1作为默认的垃圾收集器实现,替换了jdk7和jdk8的默认垃圾收集器实现:Parallel Scavenge(新生代)+Parallel Old(老年代)。
# 4.I/O流加强
java.io.InputStream 中增加了新的方法来读取和复制 InputStream 中包含的数据:
readAllBytes:读取 InputStream 中的所有剩余字节
readNBytes: 从 InputStream 中读取指定数量的字节到数组中
transferTo:读取 InputStream 中的全部字节并写入到指定的 OutputStream 中
2
3
4
5
# 5.JShell工具
jdk9引入了jshell这个交互性工具,让Java也可以像脚本语言一样来运行,可以从控制台启动 jshell ,在 jshell 中直接输入表达式并查看其执行结果。当需要测试一个方法的运行效果,或是快速的对表达式进行求值时,jshell 都非常实用。
# JDK10新特性
重要特性:通过var关键字实现局部变量类型推断,使Java语言变成弱类型语言、JVM的G1垃圾回收由单线程改成多线程并行处理,降低G1的停顿时间
- 局部变量类型推断,类似JS可以通过var来修饰局部变量,编译之后会推断出值的真实类型
- Full GC,来优化G1的延迟
- 在不执行全局VM安全点的情况下执行线程回调,可以停止单个线程,而不需要停止所有线程或不停止线程
# JDK11新特性
重要特性:对于JDK9和JDK10的完善,主要是对于Stream、集合等API的增强、新增ZGC垃圾收集器
- ZGC,ZGC可以看做是G1之上更细粒度的内存管理策略。由于内存的不断分配回收会产生大量的内存碎片空间,因此需要整理策略防止内存空间碎片化,在整理期间需要将对于内存引用的线程逻辑暂停,这个过程被称为"Stop the world"。只有当整理完成后,线程逻辑才可以继续运行。(并行回收)
- Flight Recorder(飞行记录器),基于OS、JVM和JDK的事件产生的数据收集框架
- 对Stream、Optional、集合API进行增强
# JDK12新特性
重要特性:switch表达式语法扩展、G1收集器优化、新增Shenandoah GC垃圾回收算法
- Shenandoah GC,新增的GC算法
- switch 表达式语法扩展,可以有返回值
- G1收集器的优化,将GC的垃圾分为强制部分和可选部分,强制部分会被回收,可选部分可能不会被回收,提高GC的效率
# JDK13新特性
重要特性:ZGC优化,释放内存还给操作系统、socket底层实现引入NIO
- Socket的底层实现优化,引入了NIO;
- switch表达式增加yield关键字用于返回结果,作用类似于return,如果没有返回结果则使用break;
- ZGC优化,将标记长时间空闲的堆内存空间返还给操作系统,保证堆大小不会小于配置的最小堆内存大小,如果堆最大和最小内存大小设置一样,则不会释放内存还给操作系统;
- 引入了文本块,可以使用"""三个双引号表示文本块,文本块内部就不需要使用换行的转义字符;
# JDK14新特性
instanceof类型匹配语法简化,可以直接给对象赋值,如
if(obj instanceof String str)
,如果obj是字符串类型则直接赋值给了str变量;引入record类,类似于枚举类型,可以向Lombok一样自动生成构造器、equals、getter等方法;
NullPointerException打印优化,打印具体哪个方法抛的空指针异常,避免同一行代码多个函数调用时无法判断具体是哪个函数抛异常的困扰,方便异常排查;
# JDK15新特性
- 隐藏类 hidden class;
- 密封类 sealed class,通过sealed关键字修饰抽象类限定只允许指定的子类才可以实现或继承抽象类,避免抽象类被滥用;
# JDK16新特性
JDK16相当于是将JDK14、JDK15的一些特性进行了正式引入
- ZGC性能优化
- instanceof模式匹配
- record的引入
# JDK17新特性
- 正式引入密封类sealed class,限制抽象类的实现;
- 统一日志异步刷新,先将日志写入缓存,然后再异步刷新; 虽然JDK17也是一个LTS版本,但是并没有像JDK8和JDK11一样引入比较突出的特性,主要是对前几个版本的整合和完善。
# Java 18 新特性
- 默认UTF-8字符编码
- 简单的 Web 服务器
- Javadoc 中支持代码片段
- 使用方法句柄重新实现反射核心功能
- Vector API(三次孵化)
- 互联网地址解析 SPI
# Java19
- 虚拟线程的预览版
- 结构化并发
- 记录模式
官方不同Java版本文档总入口: https://docs.oracle.com/en/java/javase/index.html (opens new window)