概述
本文主要记录了,Java8之后的每个版本的部分新特性,除了Java8,其他的基本都是简介(部分重要的)。
Java8
1.接口的默认方法
为了解决接口的修改与现有的实现不兼容的问题。Java8的 interface 的方法可以用default
或 static
修饰,这样就可以有方法体,实现类也不必重写此方法。
- default修饰的方法,是普通实例方法,可以用this调用,可以被子类继承、重写。
- static修饰的方法,使用上和一般类静态方法一样。但它不能被子类继承,只能用Interface调用。
在 Java 8 ,接口和抽象类的区别:
- 接口多实现,类单继承
- 接口的方法是
public abstract
修饰,变量是public static final
修饰(不能被修改且必须有初始值)。而抽象类的成员变量默认 default,可在子类中被重新定义,也可被重新赋值。 - 设计层面不同,抽象类作为很多子类的父类,它是一种模板式设计。而接口是一种行为规范,它是一种辐射式设计。抽象类是对一种事物的抽象,即对类抽象,而接口是对行为的抽象。
2.functional interface 函数式接口
函数式接口是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。这样的接口,可以被隐式转换为lambda表达式。
在 java 8 中专门有一个包放函数式接口java.util.function
,该包下的所有接口都有 @FunctionalInterface
注解,提供函数式编程。
在其他包中也有函数式接口,其中一些没有@FunctionalInterface
注解,但是只要符合函数式接口的定义就是函数式接口,与是否有
@FunctionalInterface
注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。`
示例:
@FunctionalInterface
public interface Converter<F, T> {
T convert(F from);
}
// TODO 将数字字符串转换为整数类型
Converter<String, Integer> converter = (from) -> Integer.valueOf(from);
Integer converted = converter.convert("123");
System.out.println(converted.getClass()); //class java.lang.Integer
3.Lambda 表达式
众所周知的 Lambda 表达式。它是推动 Java 8 发布的最重要新特性。是继泛型(Generics)和注解(Annotation)以来最大的变化。
使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程。
Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。
语法格式:
(parameters) -> expression 或
(parameters) ->{
statements;
}
示例1:Runnable 接口
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("The runable now is using!");
}
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();
示例2:Comparator 接口
List<Integer> strings = Arrays.asList(1, 2, 3);
Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;}
});
//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);
另外,Java 8 允许使用::
关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。
4.内置函数式接口(Built-in Functional Interfaces)
JDK 1.8 API包含许多内置函数式接口。 其中一些接口在老版本的 Java 中是比较常见的比如: Comparator
或Runnable
,这些接口都增加了@FunctionalInterface
注解以便能用在 lambda 表达式上。
4.1 Predicate
Predicate 接口是只有一个参数的返回布尔类型值的 断言型 接口。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
4.2 Function
Function 接口接受一个参数并生成结果。默认方法可用于将多个函数链接在一起(compose, andThen)。
4.3 Supplier
Supplier 接口产生给定泛型类型的结果。 与 Function 接口不同,Supplier 接口不接受参数。
4.4 Consumer
Consumer 接口表示要对单个输入参数执行的操作。
4.5 Comparator
Comparator 是老Java中的经典接口, Java 8在此之上添加了多种默认方法。
5.Stream
java 新增了 java.util.stream
包,它和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream
,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何CRUD。
Stream依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。
它的源数据可以是 Collection、Array 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。
Stream流类型:
- stream 串行流
- parallelStream 并行流,可多线程执行
Stream常用方法:
常用方法
/**
* 返回一个串行流
*/
default Stream<E> stream()
/**
* 返回一个并行流
*/
default Stream<E> parallelStream()
/**
* 返回T的流
*/
public static<T> Stream<T> of(T t)
/**
* 返回其元素是指定值的顺序流。
*/
public static<T> Stream<T> of(T... values) {
return Arrays.stream(values);
}
/**
* 过滤,返回由与给定predicate匹配的该流的元素组成的流
*/
Stream<T> filter(Predicate<? super T> predicate);
/**
* 此流的所有元素是否与提供的predicate匹配。
*/
boolean allMatch(Predicate<? super T> predicate)
/**
* 此流任意元素是否有与提供的predicate匹配。
*/
boolean anyMatch(Predicate<? super T> predicate);
/**
* 返回一个 Stream的构建器。
*/
public static<T> Builder<T> builder();
/**
* 使用 Collector对此流的元素进行归纳
*/
<R, A> R collect(Collector<? super T, A, R> collector);
/**
* 返回此流中的元素数。
*/
long count();
/**
* 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
*/
Stream<T> distinct();
/**
* 遍历
*/
void forEach(Consumer<? super T> action);
/**
* 用于获取指定数量的流,截短长度不能超过 maxSize 。
*/
Stream<T> limit(long maxSize);
/**
* 用于映射每个元素到对应的结果
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
* 根据提供的 Comparator进行排序。
*/
Stream<T> sorted(Comparator<? super T> comparator);
/**
* 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
*/
Stream<T> skip(long n);
/**
* 返回一个包含此流的元素的数组。
*/
Object[] toArray();
/**
* 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。
*/
<A> A[] toArray(IntFunction<A[]> generator);
/**
* 合并流
*/
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
小结:
- 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
- 方法参数都是函数式接口类型。
- 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
- Stream 不保存数据,不改变数据源
6.Optional
在阿里巴巴开发手册关于 Optional 的介绍中这样写到:
防止 NPE,是程序员的基本修养,注意 NPE 产生的场景:
- 1) 返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。反例:public int f() { return Integer 对象}, 如果为 null,自动解箱抛 NPE。
- 2) 数据库的查询结果可能为 null。
- 3) 集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。
- 4) 远程调用返回对象时,一律要求进行空指针判断,防止 NPE。
- 5) 对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。
- 6) 级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
- 正例:使用 JDK8 的 Optional 类来防止 NPE 问题。
他建议使用 Optional 解决 NPE(java.lang.NullPointerException
)问题,它就是为 NPE 而生的,其中可以包含空值或非空值。
示例:
假设有一个 Zoo 类,里面有个属性 Dog,需求要获取 Dog 的 age。
class Zoo {
private Dog dog;
}
class Dog {
private int age;
}
传统解决 NPE 的办法如下:
Zoo zoo = getZoo();
if(zoo != null){
Dog dog = zoo.getDog();
if(dog != null){
int age = dog.getAge();
System.out.println(age);
}
}
Optional
解决NPE是这样的实现的:
Optional.ofNullable(zoo).map(o -> o.getDog()).map(d -> d.getAge()).ifPresent(age ->
System.out.println(age)
);
7.Date-Time API
8.Annotations(多重注解)
Java9
1.JShell
JShell 是 Java 9 新增的一个实用工具。为 Java 提供了类似于 Python 的实时命令行交互工具。
在 JShell 中可以直接输入表达式并查看其执行结果。一旦语句输入完成,JShell 立即就能返回执行的结果,而不再需要编辑器、编译器、解释器。
2.模块化系统
模块系统是Jigsaw Project的一部分,把模块化开发实践引入到了 Java 平台中,可以让我们的代码可重用性更好!
简单来说,你可以将一个模块看作是一组唯一命名、可重用的包、资源和模块描述文件(module-info.java)。
任意一个 jar 文件,只要加上一个模块描述文件(module-info.java),就可以升级为一个模块。
在引入了模块系统之后,JDK 被重新组织成 94 个模块。Java 应用可以通过新增的 jlink 工具 (Jlink 是随 Java 9 一起发布的新命令行工具。它允许开发人员为基于模块的 Java 应用程序创建自己的轻量级、定制的 JRE),创建出只包含所依赖的 JDK 模块的自定义运行时镜像。这样可以极大的减少 Java 运行时环境的大小。
我们可以通过 exports 关键词精准控制哪些类可以对外开放使用,哪些类只能内部使用。
module my.module {
//exports 公开指定包的所有公共成员
exports com.my.package.name;
}
module my.module {
//exports…to 限制访问的成员范围
export com.·my.package.name to com.specific.package;
}
3.G1 成为默认垃圾回收器
在 Java 8 的时候,默认垃圾回收器是 Parallel Scavenge(新生代)+Parallel Old(老年代)。到了 Java 9, G1(Garbage-First Garbage Collector) 成为了默认垃圾回收器。
4.快速创建不可变集合
增加了List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合(有点参考 Guava 的味道)。
List.of("Java", "C++");
Set.of("Java", "C++");
Map.of("Java", 1, "C++", 2);
使用 of() 创建的集合为不可变集合,不能进行添加、删除、替换、 排序等操作,不然会报 java.lang.UnsupportedOperationException
异常。
5.String 存储结构优化
Java 8 及之前的版本,String 一直是用 char[] 存储。在 Java 9 之后,String 的实现改用 byte[] 数组存储字符串,节省了空间。
6.接口私有方法
Java 9 允许在接口中使用私有方法。这样的话,接口的使用就更加灵活了,有点像是一个简化版的抽象类。
7.进程 API
Java 9 增加了 java.lang.ProcessHandle 接口来实现对原生进程进行管理,尤其适合于管理长时间运行的进程。
// 获取当前正在运行的 JVM 的进程
ProcessHandle currentProcess = ProcessHandle.current();
// 输出进程的 id
System.out.println(currentProcess.pid());
// 输出进程的信息
System.out.println(currentProcess.info());
Java10
1.局部变量类型推断(var)
Java 10 采用了一个叫做 var 的保留类型来实现局部变量推断。要特别注意的是,为了兼容旧版本,var 不是关键字,而是一个保留类型,也就意味着你仍然可以像这样用 var 为你的变量和函数命名。
int var = 10;
var 并不会改变 Java 是一门静态类型语言的事实,编译器负责推断出类型。
2.G1 并行 Full GC优化
为了最大限度地减少 Full GC 造成的应用停顿的影响,从 Java10 开始,G1 的 FullGC 改为并行算法。同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
3.实验性的基于 Java 的 JIT 编译器
Graal 是一个基于 Java 语言编写的 JIT 编译器,是 JDK 9 中引入的实验性 Ahead-of-Time (AOT) 编译器的基础。
Oracle 的 HotSpot VM 便附带两个用 C++ 实现的 JIT compiler:C1 及 C2。在Java 10 (Linux/x64, macOS/x64) 中,默认情况下HotSpot 仍使用C2,但通过向java 命令添加 -XX:+UnlockExperimentalVMOptions -XX:+UseJVMCICompiler
参数便可将 C2 替换成 Graal。
Java11
1.HTTP Client 标准化
Java 11 对 Java 9 中引入并在 Java 10 中进行了更新的 Http Client API 进行了标准化,在前两个版本中进行孵化的同时,Http Client 几乎被完全重写,并且现在完全支持异步非阻塞。
2.ZGC(可伸缩低延迟垃圾收集器)实验性使用
ZGC 即 Z Garbage Collector,是一个可伸缩的、低延迟的垃圾收集器。
ZGC 主要为了满足如下目标进行设计:
- GC 停顿时间不超过 10ms
- 即能处理几百 MB 的小堆,也能处理几个 TB 的大堆
- 应用吞吐能力不会下降超过 15%(与 G1 回收算法相比)
- 方便在此基础上引入新的 GC 特性和利用 colored 针以及 Load barriers 优化奠定基础
ZGC 目前处在实验阶段,只支持 Linux/x64 平台。在 ZGC 中出现 Stop The World 的情况会更少!
Java12
1.Shenandoah GC实验性使用
Redhat 主导开发的 Pauseless GC 实现,主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等
和 Java11 开源的 ZGC 相比(需要升级到 JDK11 才能使用),Shenandoah GC 有稳定的 JDK8u 版本,在 Java8 占据主要市场份额的今天有更大的可落地性。
2.G1 收集器优化
Java12 为默认的垃圾收集器 G1 带来了两项更新:
- 可中止的混合收集集合
- 及时返回未使用的已分配内存
Java13
1.增强 ZGC(释放未使用内存)
在 Java 11 中是实验性的引入的 ZGC 在实际的使用中存在未能主动将未使用的内存释放给操作系统的问题。
ZGC 堆由一组称为 ZPages 的堆区域组成。在 GC 周期中清空 ZPages 区域时,它们将被释放并返回到页面缓存 ZPageCache 中,此缓存中的 ZPages 按最近最少使用(LRU)的顺序,并按照大小进行组织。
在 Java 13 中,ZGC 将向操作系统返回被标识为长时间未使用的页面,这样它们将可以被其他进程重用。
2.SocketAPI 重构
Java 13 将 Socket API 的底层进行了重写, NioSocketImpl 是对 PlainSocketImpl 的直接替代,它使用 java.util.concurrent
包下的锁而不是同步方法。如果要使用旧实现,请使用 -Djdk.net.usePlainSocketImpl=true
。
public final class NioSocketImpl extends SocketImpl implements PlatformSocketImpl {
}
Java14
1.record 关键字
record
关键字可以简化 数据类(一个 Java 类一旦实例化就不能再修改)的定义方式,使用 record
代替 class 定义的类,只需要声明属性,就可以在获得属性的访问方法,以及 toString()
,hashCode()
, equals()
方法。
类似于使用 class 定义类,同时使用了 lombok 插件,并打上了@Getter,@ToString,@EqualsAndHashCode
注解。
/**
* 这个类具有两个特征
* 1. 所有成员属性都是final
* 2. 全部方法由构造方法,和两个成员属性访问器组成(共三个)
* 那么这种类就很适合使用record来声明
*/
final class Rectangle implements Shape {
final double length;
final double width;
public Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
double length() { return length; }
double width() { return width; }
}
/**
* 1. 使用record声明的类会自动拥有上面类中的三个方法
* 2. 在这基础上还附赠了equals(),hashCode()方法以及toString()方法
* 3. toString方法中包括所有成员属性的字符串表示形式及其名称
*/
record Rectangle(float length, float width) { }
Java15
1.ZGC(转正)
Java11 的时候 ,ZGC 还在试验阶段。
当时,ZGC 的出现让众多 Java 开发者看到了垃圾回收器的另外一种可能,因此备受关注。
经过多个版本的迭代,不断的完善和修复问题,ZGC 在 Java 15 已经可以正式使用了!
不过,默认的垃圾回收器依然是 G1。你可以通过下面的参数启动 ZGC:
$ java -XX:+UseZGC className
2.密封类(Sealed Classes)
Java 15 对 Java 14 中引入的预览新特性进行了增强,主要是引入了一个新的概念 密封类(Sealed Classes)。
密封类可以对继承或者实现它们的类进行限制。
比如抽象类 Person 只允许 Employee 和 Manager 继承。
public abstract sealed class Person
permits Employee, Manager {
//...
}
另外,任何扩展密封类的类本身都必须声明为 sealed、non-sealed 或 final。
public final class Employee extends Person {
}
public non-sealed class Manager extends Person {
}
Java16
1.revord 记录类型(转正)
revord 记录类型正式转正。