JDK8到JDK17新特性

布鸽不鸽 Lv4

前言

SpringBoot3.0开始强制使用JDK17,想必会迎来一波JDK8到JKD17的更新热潮。本文总结了JDK8-JDK17所有重要的更新内容,以供查阅。

原文地址:https://xuedongyun.cn/post/54344/

带资源的try

JDK7新特性

  • 在try的后面可以增加(),括号中可以声明流对象并初始化
  • try中的代码执行完毕,自动把流对象释放,不用写finally了

说明:

  • 声明的类必须实现AutoCloseableCloseable接口,实现了其中的close方法。Java7几乎把所有的资源类都实现了这些接口。

  • 写到括号中的变量默认是final的,无法更改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
try (
FileInputStream fis = new FileInputStream("d:/1.txt");
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
BufferedReader br = new BufferedReader(isr);

FileOutputStream fos = new FileOutputStream("1.txt");
OutputStreamWriter osw = new OutputStreamWriter(fos, "gbk");
BufferedWriter bw = new BufferedWriter(osw);
) {
String str;
while ((str = br.readLine()) != null) {
bw.write(str);
bw.newLine();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}

JDK9语法有增强

此时可以在外部初始化变量,括号中引用外部名称即可,用分号隔开。

1
2
3
4
5
6
7
InputStreamReader reader = new InputStreamReader(System.in);
OutputStreamWriter writer = new OutputStreamWriter(System.out);
try (reader; writer) {
// 主要流程
} catch (IOException e) {
e.printStackTrace();
}

Lambda表达式

JDK8新特性

在启动线程时,需要传入一个实现java.lang.Runnable接口的对象,来定义线程中的工作。通常,我们使用匿名内部类来实现。

1
2
3
4
5
6
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello from thread");
}
}).start();

但是本质上,我们需要的其实只是一个函数。lambda表达式就是为解决这个问题而出现的。

1
2
3
4
5
6
7
8
Comparator<Integer> com1 = (Integer o1, Integer o2) -> {
return o1.compareTo(o2);
};

// 类型推断:类型可以由编译器推理出来,可以不写
Comparator<Integer> com1 = (o1, o2) -> {
return o1.compareTo(o2);
};

函数式接口

  • 只包含一个抽象方法的接口称为函数式接口(当然可以包含其他非抽象方法)
  • 可以用lambda表达式创建接口的对象
  • 可以在接口上标注@FunctionalInterface注解,用于检查是否满足条件

函数式编程思想:

  • 函数是一等公民,注重获取结果

内置的函数式接口

  • 之前常见的函数式接口

    • java.lang.Runnable,方法:public void run()
    • java.lang.Iterable<T>,方法:public Iterator<T> iterate()
    • java.lang.Comparable<T>,方法:public int compareTo(T t)
    • java.util.Comparator<T>,方法:public int compare(T t1, T t2)
  • 四大核心函数接口

函数式接口称谓抽象方法用途
Consumer<T>消费型接口void accept(T t)对类型为T的对象进行操作
Supplier<T>供给型接口T get()返回类型为T的对象
Function<T, R>函数型接口R apply(T t)对对象进行操作,返回结果
Predicate<T>判断型接口boolean test(T t)判断对象是否满足某条件
  • 消费型接口
函数式接口抽象方法描述
BiConsumer<T,U>void accept(T t, U u)接收两个对象,用于完成功能
DoubleConsumervoid accept(double value)接收一个double值
IntConsumervoid accept(int value)接收一个int值
LongConsumervoid accept(long value)接收一个long值
ObjDoubleConsumer<T>void accept(T t, double value)接收一个对象和一个double值
ObjIntConsumer<T>void accept(T t, int value)接收一个对象和一个int值
ObjLongConsumer<T>void accept(T t, long value)接收一个对象和一个long值
  • 供给型接口
接口名抽象方法描述
BooleanSupplierboolean getAsBoolean()返回一个boolean值
DoubleSupplierdouble getAsDouble()返回一个double值
IntSupplierint getAsInt()返回一个int值
LongSupplierlong getAsLong()返回一个long值
  • 判断型接口
接口名抽象方法描述
BiPredicate<T,U>boolean test(T t, U u)接收两个对象
DoublePredicateboolean test(double value)接收一个double值
IntPredicateboolean test(int value)接收一个int值
LongPredicateboolean test(long value)接收一个long值
  • 函数型接口(看套路总结即可)
接口名抽象方法描述
UnaryOperator<T>T apply(T t)接收T类型,返回T类型
DoubleFunction<R>R apply(double value)接收double,返回R类型
IntFunction<R>R apply(int value)接收int,返回R类型
LongFunction<R>R apply(long value)接收long,返回类型
ToDoubleFunction<T>double applyAsDouble(T value)接收T类型,返回double
ToIntFunction<T>int applyAsInt(T value)接收T类型,返回int
ToLongFunction<T>long applyAsLong(T value)接收类型T,返回long
DoubleToIntFunctionint applyAsInt(double value)接收double,返回int
DoubleToLongFunctionlong applyAsLong(double value)接收double,返回long
IntToDoubleFunctiondouble applyAsDouble(int value)接收int,返回double
IntToLongFunctionlong applyAsLong(int value)接收int,返回long
LongToDoubleFunctiondouble applyAsDouble(long value)接收long,返回double
LongToIntFunctionint applyAsInt(long value)接收long,返回int
DoubleUnaryOperatordouble applyAsDouble(double operand)接收double,返回double
IntUnaryOperatorint applyAsInt(int operand)接收int,返回int
LongUnaryOperatorlong applyAsLong(long operand)接收long,返回long
BiFunction<T,U,R>R apply(T t, U u)接收T类型和U类型,返回一个R类型
BinaryOperator<T>T apply(T t, T u)接收两个T类型,返回T类型
ToDoubleBiFunction<T,U>double applyAsDouble(T t, U u)接收T类型和U类型,返回double
ToIntBiFunction<T,U>int applyAsInt(T t, U u)接收T类型和U类型,返回int
ToLongBiFunction<T,U>long applyAsLong(T t, U u)接收T类型和U类型,返回long
DoubleBinaryOperatordouble applyAsDouble(double left, double right)接收两个double,返回double
IntBinaryOperatorint applyAsInt(int left, int right)接收两个int,返回int
LongBinaryOperatorlong applyAsLong(long left, long right)接收两个long,返回long

命名套路:

Operator:参数返回值类型一致

Fucntion:参数返回值类型不一致

  • Int-UnaryOperator

  • Int-Function

  • To-Int-Function

  • Double-To-Int-Function

  • Bi-Function

    • To-Int-Bi-Function
  • Binary-Operator

    • Int-Binary-Operator

方法引用与构造器引用

JDK8新特性

本质:使用已有方法赋值lambda表达式,只要类型一致就成立

方法引用

当要传给lambda体的操作,已经有实现的方法时:

  • 对象::实例方法
  • 类::静态方法
  • 类::实例方法
1
Consumer<String> con = System.out::println;

构造器引用

当Lambda表达式是为了创建一个对象,且满足Lambda表达式形参时:

  • 类名::new
1
Supplier<Employee> sup = Employee::new;

数组构造引用

当Lambda表达式是为了创建一个数组对象,且满足Lambda表达式形参时:

  • 数组类型名::new
1
2
Function<Integer,String[]> func = String[]::new;
String[] arr = func.apply(10); // 数组的形参是数组大小

Stream API

JDK8新特性

  • 使用Stream API对集合进行操作,就类似于使用SQL执行数据库查询
  • Stream和Collection的区别
    • Collection是一种静态的内存数据结构,核心是数据
    • Stream是有关计算的,核心是计算
  • Stream操作流程:创建->中间操作->终止操作

创建Stream实例

方法一:通过集合

java8种Collection接口被扩展,提供了两个新的方法

  • default Stream<E> stream(): 返回一个顺序流
  • default Stream<E> parallelStream(): 返回一个并行流
1
Stream<Integer> stream = list.stream();

方法二:通过数组

1
Stream<String> stream = Arrays.stream(arr); 

方法三:通过Stream.of()创建

1
Stream<Integer> stream = Stream.of(1,2,3,4,5);

方法四:创建无限流

可以使用Stream.iterate()Stream.generate(),创建无限流

  • 迭代:public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)
  • 生成:public static<T> Stream<T> generate(Supplier<T> s)
1
2
3
4
5
Stream<Integer> stream = Stream.iterate(0, x -> x + 2);
stream.limit(10).forEach(System.out::println);

Stream<Double> stream1 = Stream.generate(Math::random);
stream1.limit(10).forEach(System.out::println);

中间操作

除非流水线上除法终止操作,否则中间操作不会执行任何处理。所有操作会一次性全部处理

  • 筛选和切片
方 法描 述
filter(Predicatep)接收Lambda,从流中排除某些元素
distinct()筛选,通过流所生成元素的hashCode()equals()去除重复元素
limit(long maxSize)截断流,使其元素不超过给定数量
skip(long n)跳过元素,返回一个扔掉了前 n 个元素的流。
若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补
  • 映射
方法描述
map(Function f)函数被应用到每个元素上,将其映射成新的元素
mapToDouble(ToDoubleFunction f)函数被应用到每个元素上,产生新的DoubleStream
mapToInt(ToIntFunction f)函数被应用到每个元素上,产生新的IntStream
mapToLong(ToLongFunction f)函数被应用到每个元素上,产生新的LongStream
flatMap(Function f)将流中的每个值都换成另一个流,然后把所有流连接成一个流
  • 排序
方法描述
sorted()产生一个新流,其中按自然顺序排序
sorted(Comparator com)产生一个新流,其中按比较器顺序排序

终止操作

  • 匹配和查找
方法描述
allMatch(Predicate p)检查是否匹配所有元素
anyMatch(Predicate p)检查是否至少匹配一个元素
noneMatch(Predicate p)检查是否没有匹配所有元素
findFirst()返回第一个元素
findAny()返回当前流中的任意元素
count()返回流中元素总数
max(Comparator c)返回流中最大值
min(Comparator c)返回流中最小值
forEach(Consumer c)内部迭代
(使用Collection接口需要用户去做迭代,称为外部迭代)
  • 归约
方法描述
reduce(T identity, BinaryOperator b)带初始值,可以将流中元素反复结合起来,得到一个值。返回T
reduce(BinaryOperator b)不带初始值,可以将流中元素反复结合起来,得到一个值。返回Optional<T>
  • 收集
方 法描 述
collect(Collector c)将流转换为其他形式。接收参数:Collector接口的实现

Collectors类中帮我们实现了很多静态方法,可以帮助我们很方便的实现收集

方法返回类型作用举例
toListCollector<T, ?, List<T>>收集到List.stream().collect(Collectors.toList());
toSetCollector<T, ?, Set<T>>收集到Set.stream().collect(Collectors.toSet());
toCollectionCollector<T, ?, C>收集到创建的集合.stream().collect(Collectors.toCollection(ArrayList::new));
countingCollector<T, ?, Long>计算元素的个数.stream().collect(Collectors.counting());
summingIntCollector<T, ?, Integer>对Integer属性求和.stream().collect(Collectors.summingInt(Employee::getSalary));
averagingIntCollector<T, ?, Double>对Integer属性平均.stream().collect(Collectors.averagingInt(Employee::getSalary));
summarizingIntCollector<T, ?, IntSummaryStatistics>收集流中Integer属性的统计值。如:平均值.stream().collect(Collectors.summarizingInt(Employee::getSalary));
joiningCollector<CharSequence, ?, String>连接流中每个字符串.stream().map(Employee::getName).collect(Collectors.joining());
maxByCollector<T, ?, Optional<T>>根据比较器选择最大值.stream().collect(Collectors.maxBy(comparingInt(Employee::getSalary)));
minByCollector<T, ?, Optional<T>>根据比较器选择最小值.stream().collect(Collectors.minBy(comparingInt(Employee::getSalary)));
reducingCollector<T, ?, Optional<T>>从一个作为累加器的初始值开始,利用BinaryOperator逐个结合,从而归约成单个值.stream().collect(Collectors.reducing(0, Employee::getSalar, Integer::sum));
collectingAndThenCollector<T,A,R>包裹另一个收集器,对其结果转换函数.stream().collect(Collectors.collectingAndThen(Collectors.toList(), List::size));
groupingByCollector<T, ?, Map<K, List<T>>>根据某属性值对流分组,属性为K,结果为V.stream().collect(Collectors.groupingBy(Employee::getStatus));
partitioningByCollector<T, ?, Map<Boolean, List<T>>>根据true或false进行分区.stream().collect(Collectors.partitioningBy(Employee::getManage));

Optional类

JDK8新特性

Optional<T>java.util.Optional 是一个容器类,它可以保存类型T的值,代表这个值存在。或者仅仅保存null,表示这个值不存在。如果值存在,则isPresent()方法会返回true,调用get()方法会返回该对象。

  • 创建Optional类的方法:
1
2
3
static <T> Optional<T> empty();				// 创建空的Optional实例
static <T> Optional<T> of(T value); // 创建Optional实例,value必须非空
static <T> Optional<T> ofNullable(T value); // 创建Optional实例,value可以为空,也可能为非空
  • 判断Optional容器中是否包含对象:
1
2
boolean isPresent();	// 判断值是否存在
void ifPresent(Consumer<? super T> consumer) //若值存在,就对它进行Consumer指定的操作
  • 获取Optional容器的对象
1
2
3
4
T get()				// 存在则返回值,否则抛异常
T orElse(T other) // 存在则返回值,为空就用指定的默认值
T orElseGet(Supplier<? extends T> other) // 存在则返回值,为空就用Supplier接口提供的值
T orElseThrow(Supplier<? extends X> exceptionSupplier) // 存在则返回值,为空就用抛出你指定的异常类型

JDK9-11新增了特性

新增方法描述新增的版本
boolean isEmpty()判断value是否为空JDK 11
ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)非空执行参数1;
否则空执行参数2
JDK 9
Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)非空返回对应的Optional;
否则返回参数封装的Optional
JDK 9
Stream<T> stream()非空返回仅此value的Stream;
否则返回空Stream
JDK 9
T orElseThrow()非空返回value;
否则抛异常
NoSuchElementException
JDK 10

可以创建空Stream

JDK9新特性

Java8中Stream不能完全为空,否则会报空指针异常。Java9中的ofNullable方法允许创建一个单元素Stream,可以包含空元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 可以
Stream<String> stringStream = Stream.of("AA", "BB", null);
Long num = stringStream.collect(Collectors.counting()); // 3

// 不可以
Stream<String> stringStream = Stream.of(null);

// 可以
Stream<String> stringStream = Stream.ofNullable(null);
Long num = stringStream.collect(Collectors.counting()); // 0

// 可以
Stream<String> stringStream = Stream.ofNullable("AA");
Long num = stringStream.collect(Collectors.counting()); // 1

iterator终止方式

JDK9新特性

定义了重载的新的iterator方法,可以定义终止条件

1
2
3
4
5
// 以前需要靠limit
Stream.iterate(1,i -> i + 1).limit(10).forEach(System.out::println);

// 现在可以定义终止条件
Stream.iterate(1,i -> i < 100,i -> i + 1).forEach(System.out::println);

jshell命令

JDK9新特性

  • jShell命令是Java的REPL工具(交互式编程环境,read-evaluate-print-loop),可以像python那样一行一行执行
  • 命令行输入jshell即可开始使用
1
jshell
  • 不是很常用,略过

String存储结构和API变更

JDK9新特性

存储结构变化

产生背景:

The current implementation of the String class stores characters in a char array, using two bytes (sixteen bits) for each character. Data gathered from many different applications indicates that strings are a major component of heap usage and, moreover, that most String objects contain only Latin-1 characters. Such characters require only one byte of storage, hence half of the space in the internal char arrays of such String objects is going unused.

使用说明:

We propose to change the internal representation of the String class from a UTF-16 char array to a byte array plus an encoding-flag field. The new String class will store characters encoded either as ISO-8859-1/Latin-1 (one byte per character), or as UTF-16 (two bytes per character), based upon the contents of the string. The encoding flag will indicate which encoding is used.

对StringBuilder,StringBuffer等来说也是如此

String-related classes such as AbstractStringBuilder, StringBuilder, and StringBuffer will be updated to use the same representation, as will the HotSpot VM’s intrinsic string operations.

结论:String 不用 char[] 来存储,改成了 byte[] 加上编码标记,节约了一些空间

API变化

新增字符串处理方法

JDK11新特性

描述举例
判断字符串是否为空白.isBlank()
去除首尾空白.strip()
去除尾部空格.stripTrailing()
去除首部空格.stripLeading()
复制字符串.repeat(3)
字符串按行分为流Stream<String> lines = str.lines();

实现了Constable接口

JDK12新特性

1
2
3
4
5
public final class String implements java.io.Serializable, 
Comparable<String>,
CharSequence,
Constable,
ConstantDesc {}
  • Constable接口
1
2
3
4
// Constable定义了方法,用于返回Optianl对象
public interface Constable {
Optional<? extends ConstantDesc> describeConstable();
}
  • JDK12实现接口的,其实就是用了Optional.of
1
2
3
4
@Override
public Optional<String> describeConstable() {
return Optional.of(this);
}
1
Optional<String> optional = str.describeConstable();

transform方法

JDK12新特性

1
2
3
public <R> R transform(Function<? super String, ? extends R> f) {
return f.apply(this);
}

例代码子:

1
2
// 有一点类似流中的map
"foo".transform(input -> input + " bar").transform(String::toUpperCase)

局部变量类型推断

JDK10新特性

本特性允许开发人员省略通常不必要的类型声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 局部变量
var list = new ArrayList<String>();

// 增强for循环
for (var v : list) {
System.out.println(v);
}

// 普通for循环
for (var i = 0; i < 100; i++) {
System.out.println(i);
}

// 返回值类型含复杂泛型结构
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
var iterator = set.iterator();

注意:

  • var不是关键词,而是一个类型名
  • var不会改变Java是一门静态类型语言的事实。编译器负责推断出结果,将结果写入字节码文件。

文本块

JDK13新特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 以前
"line1\nline2\nline3\n"

// 现在
"""
line1
line2
line3
"""

// 如果最后不需要换行
"""
line1
line2
line3"""

JDK14预览版增加了两个新的语法,JDK15转正

  • \:取消换行
  • \s:表示一个空格
1
2
3
4
5
6
String sql2 = """
SELECT id,NAME,email \
FROM customers\s\
WHERE id > 4 \
ORDER BY email DESC
""";

switch表达式

JDK12预览版特性,JDK14转正

  • 使用case L ->
  • 省略了break语句
  • 可以将多个case合并在一起
1
2
3
4
5
6
7
Fruit fruit = Fruit.GRAPE;
switch(fruit){
case PEAR -> System.out.println(4);
case APPLE,MANGO,GRAPE -> System.out.println(5);
case ORANGE,PAPAYA -> System.out.println(6);
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
  • 更进一步,还可以处理返回值
1
2
3
4
5
6
7
Fruit fruit = Fruit.GRAPE;
int numberOfLetters = switch(fruit){
case PEAR -> 4;
case APPLE,MANGO,GRAPE -> 5;
case ORANGE,PAPAYA -> 6;
default -> throw new IllegalStateException("No Such Fruit:" + fruit);
};
  • JDK13中引入了yield语句。switch语句应使用yield,而不是return。return会直接跳出当前方法,而yield只会跳出当前switch块。
1
2
3
4
5
6
7
8
String x = "3";
int i = switch (x) {
case "1" -> 1;
case "2" -> 2;
default -> {
yield 3;
}
};

JDK17预览特性:switch的模式匹配

1
2
3
4
5
6
7
8
// 自动完成模式匹配,大大简化代码
return switch (o) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> o.toString();
};

instanceof的模式匹配

JDK14预览版特性,JDK16转正

实现简洁的类型安全代码。使用该方法,可以减少Java中显示强制转换的数量。

  • 旧写法
1
2
3
4
if (obj instanceof String) {
String str = (String) obj; //需要强转
str.contains(..);
}
  • 现在的写法
1
2
3
if (obj instanceof String str) {
str.contains(..);
}

Record

JDK14预览版推出,JDK16转正

record 声明一个类时,该类将自动拥有以下功能:

  • 获取成员变量的简单方法,比如例题中的 name() 和 partner() 。注意区别于我们平常getter()的写法。
  • 一个 equals 方法的实现,执行比较时会比较该类的所有成员属性。
  • 重写 hashCode() 方法。
  • 一个可以打印该类所有成员属性的 toString() 方法。
  • 包含所有参数的构造方法。

此外:

  • 还可以定义静态字段、静态方法、构造器或实例方法。

  • 不能定义实例字段;类不能声明为abstract;不能声明显式的父类等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//不可以将record定义的类声明为abstract
//不可以给record定义的类声明显式的父类

public record Person(String name,Person partner) {

//还可以声明静态的属性、静态的方法、构造器、实例方法

public static String nation;

public static String showNation(){
return nation;
}

public Person(String name){
this(name,null);
}

public String getNameInUpperCase(){
return name.toUpperCase();
}

// 不可以声明非静态的属性
// private int id;
}

其它结构变化

下划线使用的限制

JDK9新特性

现在不允许使用”_”作为变量名

更简化的编译运行程序

JDK11新特性

以前

1
2
3
4
5
// 编译
javac JavaStack.java

// 运行
java JavaStack

现在

1
java JavaStack.java

GC方面新特性

  • 此部分待更新

参考文档

https://zhuanlan.zhihu.com/p/458509231

https://blog.csdn.net/best_luxi/article/details/122543074/

https://www.bilibili.com/video/BV1PY411e7J6

  • 标题: JDK8到JDK17新特性
  • 作者: 布鸽不鸽
  • 创建于 : 2023-04-22 22:18:51
  • 更新于 : 2023-06-09 15:05:50
  • 链接: https://xuedongyun.cn//post/54344/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论