JDK8的新特性
一、JDK8的新特性(2014-2023长支持版本)
1.1 Lambda表达式
1.1.1 什么是lambda表达式
lambda是对基于接口的匿名内部类编写方式的一种优化,使的它可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java语言表达能力得到进一步提升。
1.1.2 基本案例
//原始写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("这是一个线程!");
}
}).start();
//Lambda表达式的写法
new Thread(() -> System.out.println("这是一个线程")).start();
1.1.3 语法格式
(参数1,参数2,...) -> {
方法体;
return 返回值;
}
- 参数部分:与接口中方法的形参数量保持一致
- 方法体部分:方法的具体具体实现
注意
- 参数类型可以省略不写,Lambda可以自动类型推导
- 如果只有一个参数的情况下,小括号可以省略不写
- 如果方法体只有一条语句时,大括号可以省略不写
- 如果方法体中只有一条返回语句时,大括号和return关键字都可以省略不写
- 在普通的匿名内部类中,this代表的是当前匿名内部类的对象, 而Lambda表达式中,this代表的是当前类对象
1.1.4 函数式接口
虽然Lambda表达式可以简化接口的实现,但是并不是所有的接口都可以使用Lambda表达式来简化开发的。Lambda表达式只能实现函数式接口
什么是函数式接口?
简单说,一个接口中有且只有一个抽象方法时,这个接口就是函数式接口
@FunctionalInterface
public interface A {
void method(String a);
}
可以使用注解**@Functionalnterface**来判断一个接口是否为函数式接口
通用的函数式接口
/**
参数 - T
返回值 - void
作用 - 对类型T的参数进行相关操作处理
*/
@FunctionalInterface
public interface Consumer<T> {
/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
}
供给型接口
/**
参数 - 无
返回值 - T
作用 - 返回类型T的数据
*/
@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
*
* @return a result
*/
T get();
}
函数型接口
/**
参数 - T
返回值 - R
作用 - 对参数T进行处理,并返回处理结果R
*/
@FunctionalInterface
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument
* @return the function result
*/
R apply(T t);
}
断言型接口
/**
参数 - T
返回值 - Boolean
作用 - 根据参数T,进行某种逻辑判断,并返回判断的结果
*/
@FunctionalInterface
public interface Predicate<T> {
/**
* Evaluates this predicate on the given argument.
*
* @param t the input argument
* @return {@code true} if the input argument matches the predicate,
* otherwise {@code false}
*/
boolean test(T t);
}
1.1.5 方法引用
方法引用就是通过一种特定的形式,进行方法调用。 本质上是为了进一步简化Lambda表达式的写法而诞生的
public static void main(String[] args) {
//方法引用之前
method(a -> System.out.println(a));
//方法引用之后
method(System.out::println);
}
//同级的静态方法
public static void method(Consumer consumer){
consumer.accept("Hello");
}
方法引用的语法
- (函数式)接口类型 对象变量名 = 对象::被引用的实例方法;
- (函数式)接口类型 对象变量名 = 类名::被引用的静态方法;
- (函数式)接口类型 对象变量名 = 类名::被引用的实例方法;
- (函数式)接口类型 对象变量名 = 类名::new(引用的是类的构造方法);
1.2 interface的新特性
JDK1.8之后,接口(interface)中允许编写 静态方法和默认方法
public interface InterfaceDemo {
/**
* 常亮 - 默认加上 public static final
*/
int a = 10;
/**
* 抽象方法 - 默认加上 public abstract
*/
void method1();
/**
* 静态方法 - 默认加上public
*/
static void method2(){
System.out.println("静态实现...");
}
/**
* 默认方法 - 默认加上 public
*/
default void method3(){
System.out.println("默认实现...");
}
}
- 静态方法和类中的静态方法没有区别,可以通过接口名直接调用
- 静态方法不能被实现类重写
- 默认方法实现类可以选择性重写
- 如果一个类,父类中和接口中出现重名重参的方法,子类又没有重写的情况下,默认调用父类中的方法(类优先原则)
- 如果一个类,实现的多个接口中出现**重名重参的方法,**子类又没有重写的情况下,直接编译时报错
1.3 Stream流
1.3.1 什么是Stream流
Stream流是一组用于数据集(集合/数组)操作的相关API。相对于原来的数据集操作,我们称之为外部迭代,也就是需要显示的写出循环遍历的过程。而Stream流提供了一种内部迭代的处理方式,内部迭代使得代码更加可读和优雅
1.3.2 Stream流的基本案例
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
list.add(5);
//外部迭代的方式
for (Integer i : list) {
System.out.println(i);
}
//通过Stream流内部迭代
list.stream().forEach(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println(integer);
}
});
//Stream流 + Lambda表达式
list.stream().forEach(i -> System.out.println(i));
//Stream流 + Lambda表达式 + 方法引用
list.stream().forEach(System.out::println);
}
- Stream流的操作步骤可以分为:创建流 -> 中间操作 -> 最终操作
1.3.3 创建流的方式
//1、通过函数创建
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);//枚举流
Stream<Integer> iterate = Stream.iterate(0, integer -> integer + 1);//无限流,通常需要配合limit操作使用
Stream<Double> generate = Stream.generate(Math::random);//无限流,通常需要配合limit操作使用
//2、集合转换成stream
//list转Stream
List<Integer> list = new ArrayList<>();
Stream<Integer> listStream = list.stream();
//set转stream
Set<String> set = new HashSet<>();
Stream<String> setStream = set.stream();
//map转stream
Map<Integer, String> map = new HashMap<>();
Stream<Map.Entry<Integer, String>> mapStream = map.entrySet().stream();
//3、数组转化成stream
int[] array = {1,2,3,4,5};
IntStream arrayStream = Arrays.stream(array);
//4、通过文件创建, 文件中每行数据就是一个流数据
try {
Stream<String> fileStream = Files.lines(Paths.get("文件路径"), Charset.defaultCharset());
} catch (IOException e) {
throw new RuntimeException(e);
}
1.3.4 中间操作
所有返回Stream对象的方法,都是中间操作。 Stream流的中间操作都是惰性操作,如果没有执行最终操作,则所有的中间操作都不会执行。
//filter - 过滤元素
Stream.of(1, 2, 3, 4, 5, 6)
//留下所有偶数
.filter(i -> i % 2 == 0)
.forEach(System.out::println);
//map - 转换元素
List<Student> students = new ArrayList<>();
students.add(new Student().setId(1).setName("小明").setAge(19));
students.add(new Student().setId(2).setName("小红").setAge(22));
students.add(new Student().setId(3).setName("小刚").setAge(18));
//将集合转换成Stream流,得到一个存放学生对象的Stream
students.stream()
//转换元素,将Stream流中的学生对象,转换成学生姓名,最终会获得一个存放学生姓名的Stream
.map(stu -> stu.getName())
.forEach(System.out::println);
//----学生类---
@Data
@Accessors(chain = true)
public static class Student {
private Integer id;
private String name;
private Integer age;
}
//distinct - 去重
Stream.of(1,2,1,1,3,2,4,7,6,1).
distinct()
.forEach(System.out::println);
//sorted - 排序
Stream.of(5,18,26,14,17,22,1,3,19,3)
//元素排序,注意排序的元素必须实现java.lang.Comparable接口
.sorted()
.forEach(System.out::println);
//limit - 限制
//创建一个无限流
Stream.generate(Math::random)
//限制输出5个元素
.limit(5)
//将每个随机数乘以10000
.map(d -> d * 10000)
//将5个浮点随机数转换成整数型
.map(Double::intValue)
.forEach(System.out::println);
//skip - 跳过
Stream.of(1,2,3,4,5,6)
//跳过前3个元素
.skip(3)
.forEach(System.out::println);
//peek - 临时查看
Stream.of(1,2,3,4,5)
//限制输出4个元素
.limit(4)
//想查看下限制后的结果,但是又不想终止流操作,可以使用peek来进行
.peek(System.out::println)
//过滤掉所有的奇数
.filter(i -> i % 2 == 0)
.forEach(System.out::println);
//map - 操作
Integer[][] strArrays = {{1,2,3},{4,5,6}};
Arrays.stream(strArrays)
//将元素(一维数组)转换成一个流,并且对一维数组中的每个元素+1
.map(objects -> Stream.of(objects).map(o -> o + 1))
//直接打印的是两个流对象
.forEach(System.out::println);
//flatMap - 扁平化的转换
Integer[][] strArrays = {{1,2,3},{4,5,6}};
Arrays.stream(strArrays)
//将元素(一维数组)转换成一个流,并且对一维数组中的每个元素+1
.flatMap(objects -> Stream.of(objects).map(o -> o + 1))
//直接会打印每个流中的元素
.forEach(System.out::println);
1.3.5 最终操作
除了中间操作的其他方法,都是最终操作。 Stream流中只有调用了最终操作,整个流才会执行。
//forEach - 遍历流
Stream.of(1,2,3,4,5)
.forEach(System.out::println);
//返回第一个元素
Integer i = Stream.of(1, 2, 3, 4, 5)
.findFirst().get();
System.out.println("第一个元素:" + i);
//count - 统计数量
long count = Stream.of(1, 2, 3, 4, 5)
.count();
System.out.println("流数据的数量为:" + count);
//sum求和
Integer sum = Stream.of(1,5,3,8)
//将Stream流 转换为 IntStream
.mapToInt(value -> value)
//求和
.sum();
System.out.println("求和的结果:" + sum);
//最小值
Integer min = Stream.of(1, 2, 3, 4, 5, 6)
.min(Integer::compareTo)
.get();
System.out.println("最小值:" + min);
//最大值
Integer max = Stream.of(1, 2, 3, 4, 5, 6)
.max(Integer::compareTo)
.get();
System.out.println("最大值:" + max);
//anyMatch - 任意元素符合条件
boolean flag = Stream.of(1, 2, 3, 4, 5, 6)
.anyMatch(i -> i % 3 == 0);
System.out.println("是否存在能被3整除的元素?" + flag);
//allMatch - 所有元素符合条件
boolean flag = Stream.of(1, 2, 3, 4, 5, 6)
.allMatch(i -> i % 3 == 0);
System.out.println("是否所有元素都能被3整除?" + flag);
//noneMatch - 所有元素都不符合条件
boolean flag = Stream.of(1, 2, 4, 5)
.noneMatch(i -> i % 3 == 0);
System.out.println("是否没有找到能被3整除的元素?" + flag);
//1个参数的方式
Integer s = Stream.of(1, 2, 3, 4, 5, 6)
//通过reduce进行求和
//参数1 - 中间每个元素的结合过程
.reduce((sum, i) -> sum += i).get();
System.out.println("求和的结果:" + s);
//2个参数的方式
Integer s2 = Stream.of(1, 2, 3, 4, 5, 6)
//通过reduce进行求和
//参数1 - 初始值
//参数2 - 中间每个元素的结合过程
.reduce(0, (sum, i) -> sum += i);
System.out.println("求和的结果:" + s2);
//3个参数的方式
Integer s3 = Stream.of(1,2,3,4,5,6)
.parallel()
//通过reduce进行求和
//参数1 - 初始值
//参数2 - 中间每个元素的结合过程
//参数3 - 并行流的结合过程,需要配合.parallel()方式(将串型流变成并行流)一起使用
.reduce(0, (sum, i) -> sum += i, (t1Val, t2Val) -> t1Val += t2Val);
System.out.println("求和的结果:" + s3);
//准备一个单词数组
String[] words = {"Jerry", "Jack", "Lili", "Tom", "Today", "Jack", "Tom", "Lili", "Tom"};
Map<String, Integer> reduce = Arrays.stream(words)
.map(s -> {
Map<String, Integer> map = new HashMap<>();
map.put(s, 1);
return map;
}).reduce((stringIntegerMap, stringIntegerMap2) -> {
for (Map.Entry<String, Integer> entry : stringIntegerMap2.entrySet()) {
stringIntegerMap.computeIfPresent(entry.getKey(), (s, val) -> val + entry.getValue());
stringIntegerMap.computeIfAbsent(entry.getKey(), s -> 1);
}
return stringIntegerMap;
}).get();
System.out.println("统计的结果:" + reduce);
//将流转换成List集合
List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6)
.collect(Collectors.toList());
//将流转换成Map集合
Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6)
.collect(Collectors.toSet());
//转换成Map集合
Map<String, String> map = Stream.of("key1-value1", "key2-value2", "key3-value3")
.collect(Collectors.toMap(
//参数1 - 根据流数据获得Map的key
s -> s.split("-")[0],
//参数2 - 根据流数据获得Map的Value
s -> s.split("-")[1]
));
String[] words = {"Jerry", "Jack", "Lili", "Tom", "Today", "Jack", "Tom", "Lili", "Tom"};
Map<String, Integer> wordNum = Arrays.stream(words).collect(Collectors.toMap(
//参数一 - 将单词本身作为key
s -> s,
//参数二 - 每个单词默认出现1次,作为value
s -> 1,
//参数三 - 如果遇到key相同时的解决方案,val1和val2分别表示key相同的value值,这里为了统计单词出现的次数,将value进行累加
(val1, val2) -> val1 + val2
));
System.out.println("单词数量的统计:" + wordNum);
1.4 Optional类
Optional类主要是为了解决操作空对象而设计的一种类
1.4.1 创建Optional类
//1、创建一个空的Optional
Optional<Object> empty = Optional.empty();
//2、创建一个非空的Optional,如果传入的对象为空,则会报指针异常
Object obj = null;
Optional<Object> optional = Optional.of(obj);//如果obj为空,则这行会报空指针异常
//3、创建一个可为空的Optional,如果传入的对象为空,则什么也不会发生
Object obj2 = null;
Optional<Object> optional2 = Optional.ofNullable(obj2);
1.4.2 常用方法
Person person = Optional.of(person)
//直接获得Optional中的对象,如果对象为空,则该方法报错
.get();
String name = Optional.of(person)
//将person对象转换成name字符串
.map(Person::getName)
//直接获得Optional中的对象,如果对象为空,则该方法报错
.get();
//看个案例
//Person类中的name也是一个Optional类型
@Data
private static class Person{
private Optional<String> name;
private Integer age;
}
//使用map转换的结果是Optinal<Optional<String>>
Optional<Optional<String>> op1 = Optional.of(person).map(Person::getName);
//使用flatMap转换的结果是Optional<String>,直接消除了内部的Optional对象
Optional<String> op2 = Optional.of(person).flatMap(Person::getName);
Person person = null;
//创建一个允许为null的Optional对象
Optional.ofNullable(person)
//如果对象不为空就执行以下代码,如果为空就不执行,也不会报错
.ifPresent(p -> {
System.out.println("处理了p对象....");
});
Person person = new Person();
Optional.ofNullable(person)
.ifPresentOrElse(person1 -> {
//对象不为空时执行的代码
System.out.println("person对象不为空!");
}, () -> {
//对象为空时执行的代码
System.out.println("person对象为空!");
});
Person person = null;
Person p = Optional.ofNullable(person)
//如果person为空,会执行or方法,提供一个默认的Option<Person>对象
.or(() -> Optional.of(new Person())).get();
Person person = null;
Person p = Optional.ofNullable(person)
//如果person为空,则直接返回默认对象
//和get方法不同的是,person为空,get方法会直接报错
.orElse(new Person());
Person person = null;
Person p = Optional.ofNullable(person)
//如果person为空,则创建一个默认对象再返回
//和orElse的区别在于,orElse无论person是否为空,都会创建默认对象
//而orElseGet只有确定了person为空,才会创建默认对象
.orElseGet(Person::new);
Person person = null;
Person p = Optional.ofNullable(person)
//和get一样,person为空时报错,但是该方法可以自定义抛出的异常
.orElseThrow(NullPointerException::new);
1.5 新的日期和时间类
JDK8提供了一套新的日期时间处理类
1.5.1 新增的日期类
- LocalDate - 表示日期
- LocalTime - 表示时间
- LocalDateTime - 表示日期时间
1.5.2 LocalDateTime的创建
//获得当前时间
LocalDateTime now = LocalDateTime.now();
//指定时间 1999-12-13 18:00:00
LocalDateTime ldate = LocalDateTime.of(1999, 12, 13, 18, 00, 00);
1.5.3 LocalDateTime的相关方法
//获得当前时间
LocalDateTime now = LocalDateTime.now();
//获得年份
int year = now.getYear();
//获得月份
int monthValue = now.getMonthValue();
//获得日期
int dayOfMonth = now.getDayOfMonth();
//获得这一年的第几天
int dayOfYear = now.getDayOfYear();
//获得星期几
int week = now.getDayOfWeek().getValue();
//获得小时
int hour = now.getHour();
//获得分钟
int minute = now.getMinute();
//获得秒
int second = now.getSecond();
//转成LocalDate对象
LocalDate localDate = now.toLocalDate();
//转换成LocalTime对象
LocalTime localTime = now.toLocalTime();
//增加时间 - 注意,以下方法都会返回新的日期对象,而不是修改原有的日期对象
//添加年份
now.plusYears(1);
//添加月份,会自定进位
now.plusMonths(1);
//添加日期,会自动进位
now.plusDays(1);
//添加小时,会自定进位
now.plusHours(1);
//添加分钟,会自定进位
now.plusMinutes(1);
//添加秒,会自定进位
now.plusSeconds(1);
//另外还有
//minusXxxx方法表示减少
//withXxxx方法表示设置
1.5.4 LocalDateTime格式化
//字符串转LocalDateTime对象
String time = "2023-01-05 15:33:26";
LocalDateTime date = LocalDateTime.parse(time, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
System.out.println(date);
//LocalDateTime对象 转 字符串
LocalDateTime now = LocalDateTime.now();
String nowStr = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒SSS毫秒"));
System.out.println(nowStr);
1.5.5 LocalDateTime和Date对象之间的互转
//LocalDateTime转Date
LocalDateTime localDateTime = LocalDateTime.now();
Date date = Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
System.out.println("date:" + date);
//Date转LocalDateTime
Date date2 = new Date();
LocalDateTime localDateTime2 = LocalDateTime.ofInstant(date2.toInstant(), ZoneId.systemDefault());
System.out.println(localDateTime2);
1.6 其他
- JVM使用元空间代替了永久代,元空间使用的是直接内存空间
- ConcurrentHashMap底层由分段锁优化成了CAS + synchronized
- HashMap底层哈希碰撞的解决方案引入了红黑树
- 引入了重复注解@Repeatable
二、JDK9的新特性(2017-9)
2.1 Reactive响应式编程
2.1.1 响应式流的概念
Reactive Stream (响应式流/反应流) 是JDK9引入的一套标准,是一套基于发布/订阅模式的数据处理规范。 它旨在解决处理元素流的问题——如何将元素流从发布者传递到订阅者,而不需要发布者阻塞,或订阅者有无限制的缓冲区或丢弃。 Reactive流目的是“找到最小的一组接口,方法和协议,用来描述必要的操作和实体以实现这样的目标:以非阻塞背压方式实现数据的异步流”。
2.1.2 订阅者和发布者之间的交互

2.1.3 JDK9 中的Reactive Stream规范的实现
Publisher:数据的发布者(生产者) Subscriber:数据订阅者(消费者) Subscription:发布者与订阅者之间的关系纽带,订阅令牌 Processor:数据处理器
Publisher发布者 - 接口
Publisher将数据流发布给注册的Subscriber。 通常使用Executor异步发布数据给订阅者。 Publisher需要确保每个订阅的Subscriber方法严格按顺序调用。 方法:subscribe - 使订阅者订阅发布者 public void subscribe(Subscriber<? super T> subscriber);
Subscriber订阅者 - 接口
Subscriber订阅Publisher的数据流,并接受回调。 如果Subscriber没有发出请求,就不会收到数据。对于给定订阅合同(Subscription),调用Subscriber的方法是严格按顺序的。 方法:onSubscribe - 发布者调用订阅者的这个方法异步传递订阅,这个方法在publisher.subscribe()方法调用后被执行。 public void onSubscribe(Flow.Subscription subscription); 方法:onNext - 发布者调用这个方法传递数据给订阅者。 public void onNext(Object item); 方法:onError - 当Publisher或者Subscriber遇到不可恢复的错误时调用此方法,之后不会再调用其他方法。 public void onError(Throwable throwable); 方法:onComplete - 当数据已经发送完成,并且没有错误导致订阅终止时,调用此方法,之后不在调用其他方法。 public void onComplete();
Subscription订阅合同 - 接口
Subscription用于链接Publisher和SubScriber。 Subscriber只有在请求时才会收到项目,并可以通过Subscription取消订阅。 方法:request - 订阅者调用此方法请求数据 public void request(long n); 方法:cancel - 订阅者调用这个方法来取消订阅,解除订阅者与发布者之间的关系。 public void cancel();
Processor处理器 - 接口
Processor位于Publisher和Subscriber之间,用于做数据转换。 可以有多个Processor同时使用,组成一个处理链,链中最后一个处理器的处理结果发送给Subscriber。 JDK没有提供任何具体的处理器。 处理器同时是订阅者和发布者,同时继承了两者。即作为订阅者接受数据,然后处理,处理后作为发布者再发布出去。 接口的声明: public static interface Processor<T,R> extends Subscriber
, Publisher {}
2.1.4 JDK9中Reactive Stream规范调用流程
Publisher是能够发出元素的发布者,Subscriber是接收元素并做出响应的订阅者。当执行Publisher里的subscribe方法时,发布者会回调订阅者的onSubscribe方法,这个方法中,通常订阅者会借助传入的Subscription向发布者请求n个数据。然后发布者通过不断调用订阅者的onNext方法向订阅者发出最多n个数据。如果数据全部发完,则会调用onComplete告知订阅者流已经发完;如果有错误发生,则通过onError发出错误数据,同样也会终止流。

2.1.5 使用案例
public static void main(String[] args) {
//1、创建生产者Publisher 实现了Publisher接口
SubmissionPublisher<String> publisher = new SubmissionPublisher();
//2、创建订阅者Subscriber,需要自己去实现内容方法
Flow.Subscriber<String> subscriber = new Flow.Subscriber<String>() {
private Flow.Subscription subscription;
@Override
public void onSubscribe(Flow.Subscription subscription) {
this.subscription = subscription;
System.out.println("订阅成功...");
subscription.request(2);
System.out.println("订阅方法请求了一个数据");
}
@Override
public void onNext(String item) {
System.out.println("订阅者接受到数据:" + item);
if (item.equals("5")) throw new NullPointerException("xxxxx");
subscription.request(3);
System.out.println("订阅者继续请求数据");
}
@Override
public void onError(Throwable throwable) {
System.out.println("订阅出现异常:" + throwable.getMessage());
subscription.cancel();
}
@Override
public void onComplete() {
System.out.println("数据已经接收完成!");
}
};
//3、发布者与订阅者建立订阅关系,就是回调订阅者的onSubscribe方法
publisher.subscribe(subscriber);
//4、发布者生成数据
for (int i = 0; i < 10; i++) {
System.out.println("生成数据:" + i);
publisher.submit(i + "");
}
//5、发布完成,关闭发送,此时会回调订阅者的onComplete方法
publisher.close();
//主线程等待
while(true){}
}
publisher.submit(i)方法是阻塞的,内部有个缓冲数组最大容量就是256,只有当订阅者发送 subscription.request(1); 请求后,才会从缓冲数组里拿按照顺序拿出数据传给 onNext方法 供订阅者处理,当subscription.request(1)这个方法被调用后,发布者发现数组里没有满才会再生产数据,这样就防止了生产者一次生成过多的数据把订阅者压垮,从而实现了背压机制
2.1.6 背压机制
如果生产者发出的信息比消费者能够处理消息最大量还要多,消费者可能会被迫一直在抓消息,耗费越来越多的资源,埋下潜在的崩溃风险。为了防止这一点,需要有一种机制使消费者可以通知生产者,降低消息的生成速度。生产者可以采用多种策略来实现这一要求,这种机制称为背压。 Subscription相当于是连接Publisher和Subscriber的“纽带(合同)”。因为当发布者调用subscribe方法注册订阅者时,会通过订阅者的回调方法onSubscribe传入Subscription对象,之后订阅者就可以使用这个Subscription对象的request方法向发布者“要”数据了。背压机制正是基于此来实现的。
2.2 其他
- 私有接口方法,接口中可以使用private修饰方法
- G1成为JVM默认的GC回收器
- 支持http2.0和websocket的API
- 优化了String的存储方式,底层由char数组变成byte数组
- 模块化系统
- 轻量级的JSON API
三、JDK10的新特性(2018-3)
3.1 支持局部变量类型推断
public class VariableDemo {
//全局变量不能用var修饰,会报错
var s = "";
public static void main(String[] args) {
//局部变量可以使用var声明,编译器可以自动类型推导
var str = "";
var i = 10;
var s = new Student();
//使用var时,必须声明时直接赋值,以下写法是错误的
var x;
x = 10;
//var可以用于增强循环中临时变量的类型修饰,以下写法是正确的
int[] array = {};
for (var i1 : array) {
}
}
private static class Student{
}
}
JDK10之后,Java从强类型语句,变成了弱类型语言
3.2 Optional类的调整
使用**orElseThrow()方法来代替get()**方法,但是get()方法仍然可以使用
3.3 其他
- G1底层优化成为并行的full GC
- 修改了增强型for循环的字节码生成
- 新增了集合的部分API
四、JDK11 的新特性(2018-9长支持版本)
4.1 对Stream API进行增强
//jdk8中lambda表达式的写法
o -> System.out.println(o);
//如果要给变量添加注解就需要写出变量的具体类型
(@NotNull String o) -> System.out.println(o);
//jdk11后,可以使用var来代替lambda表达式中的具体类型
(@NotNull var o) -> System.out.println(o);
4.2 对Collection API进行增强
//jdk11后,允许采用这种方法将List集合转换成相应类型的数组
String[] strings = list.toArray(String[]::new);
4.3 其他
- 引入了ZGC垃圾回收器(预览版)
- 支持Unicode 10
- 该版本主要哦是对JDK9和JDK10的完善
五、JDK12的新特性(2019-3)
5.1 switch语法的拓展(预览版)
//jdk12中switch的写法
int i = 10;
switch (i) {
case 1,2,5 -> System.out.println("A");
case 3,4,6 -> System.out.println("B");
case 7,8,9,10 -> {
System.out.println("C");
System.out.println("D");
}
default -> System.out.println("E");
}
//jdk12中,switch可以拥有返回值,但是如果返回结果,就不能进行复杂的业务处理
var s = "A";
//接收返回值
String result = switch (s) {
case "A", "B" -> "优秀";
case "C" -> "及格";
default -> "不及格";
};
5.2 其他
- 新增Shenandoah GC算法
- 优化了G1垃圾回收器
六、JDK13的新特性(2019-9)
6.1 switch语法的拓展
//jdk13对switch再次增强,可以在进行业务处理的同时,通过yield返回结果,类似方法的return
var s = "A";
//接收返回值
String result = switch (s) {
case "A", "B" -> "优秀";
case "C" -> "及格";
default -> {
System.out.println("一些业务处理");
yield "不及格";
}
};
6.2 文本块(预览版)
//jdk13中 对一段文本可以使用文本块的形式表示
String text = """
<html>
<body>
<p>Hello, world</p>
</body>
</html>
""";
6.3 其他
- Socket底层实现优化,引入NIO
- 优化了ZGC回收器
七、JDK14的新特性(2020-3)
7.1 instanceof语法增强(预览版)
//---jdk14之前的写法---
Object o = "Hello";
if (o instanceof String) {
//需要强转
String s = (String) o;
//才能调用字符串中的方法
System.out.println(s.toLowerCase());
}
//---jdk14之后的写法---
Object o = "Hello";
if (o instanceof String s) {
//直接使用字符串中的方法
System.out.println(s.toLowerCase());
} else {
//这里不能使用s
}
7.2 引入Record类(预览版)
/**
* Record类 - 记录类
* 1、默认生成一个全参的构造方法
* 2、给每个属性生成一个get方法,属性名就是方法名
* 3、默认重写了equasl、hashcode、toString方法
* 4、record类是final修饰的,所有属性也是final修饰的,所有没有生成set方法
* 5、record类中,不能在声明普通属性,只能声明静态属性
* 6、record类中,可以声明普通方法
*
* @param name
* @param age
*/
public record Student(String name, int age) {
public static String address;
public void methtod(){
}
}
//使用Record类
public static void main(String[] args) {
Student student = new Student("张三", 18);
System.out.println(student);
}
7.3 其他
- NullPointerException输出优化
八、JDK15的新特性(2020-9)
8.1 其他
- 文本块特性转为正式版
- Record记录类(第二版预览)
- CharSequence类增强(引入了isEmpty方法)
- 引入了隐藏类 hidden class(预览版)
- 引入了密封类 sealed class(预览版)
- ZGC转为正式版
九、JDK16的新特性(2021-3)
9.1 其他
- instanceof语法增强转为正式版
- Record类转为正式版
- 提供向量API(第一次孵化),提供了jdk.incubator.vector来用于矢量计算
- JDK16相当于将JDK14、JDK15的一些特性进行了正式的引入
十、JDK17的新特性(2021-9长支持版本)
10.1 switch匹配模式支持instanceof匹配(预览版)
//jdk17之前的写法
Object o = null;
if (o instanceof String s) {
//对s的处理
} else if (o instanceof Double d) {
//对d的处理
} else if (o instanceof Integer i) {
//对i的处理
}
//jdk17后,Switch可以和instanceof关键字结合使用
Object o = null;
switch (o){
case String s -> s.toLowerCase();
case Double d -> d.byteValue();
case Integer i -> i.longValue();
default -> throw new RuntimeException("类型未找到");
}
9.1 其他
- 密封类 sealed class转为正式版
- 向量API(第二次孵化)
- 为伪随机数生成器提供新的接口类型和实现
- 弃用Applet API
- 强封装JDK的内部API
十一、JDK18的新特性(2022-03)
11.1 其他
- 默认UTF-8字符编码
- 提供一个简单的Web服务器
- JavaDoc支持代码片段
- 优化了反射核心功能
- 向量API(第三次孵化)
- 提供互联网地址解析的SPI
- switch匹配模式(第二次预览)
十二、JDK19的新特性(2022-09)
12.1 虚拟线程(预览版)
12.2 其他
- 向量API(第四次孵化)
- switch匹配模式(第三次预览)
- Record记录模式(预览版)
十三、JDK20的新特性(2023-03)
13.1 其他
- 作用阈值(孵化阶段)
- Record记录模式(第二次预览)
- switch的匹配模式(第四次预览)
- 虚拟线程(第二次预览)
- 向量API(第五次孵化)