简单梳理一下从 JDK9
到 JDK17
这些版本在 CRUD 中可能常用到的一些新功能;
毕竟现在 Sping6、SpringBoot3
都已经要求是 JDK17
了
虽然现在用的还是 JDK8
... 但是新版本增加了哪些新功能还是要了解一下的
平常都是大致浏览一下看看哪个版本有啥新功能,现在印象中只记得新增了记录类、密封类,还有 var
,但其实还有很多其它有用的功能,比如 JDK14
中的空指针异常,详情更加的清晰明了
如果你用的是 JDK17
,那么下面列举的新功能都能使用
建议使用 JDK17
,因为 JDK17
是一个长期支持的版本
本文只是简单梳理记录并简单演示一下怎么使用
JDK9
1. 模块化
2. 使用 of
创建不可变集合
List list = List.of(1, 2, 3, 4, 5);
Map map = Map.of("key1", 1, "key2", 2, "key3", 3);
3. stream api
新增了4个流操作方法
dropWhile
从开头开始删除满足条件的元素,直到遇到第一个不满足条件的,然后返回剩余的元素
List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List result = numbers.stream()
.dropWhile(n -> n < 5)
.collect(Collectors.toList());
System.out.println(result); // 输出:[5, 6, 7, 8, 9, 10]
takeWhile
从开头开始获取满足条件的元素,直到遇到第一个不满足条件的,然后返回前面满足条件的元素
List numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List result = numbers.stream()
.takeWhile(n -> n < 5)
.collect(Collectors.toList());
System.out.println(result); // 输出:[1, 2, 3, 4]
ofNullable,可接受 null
String name = "Hello World";
Stream stream = Stream.ofNullable(name);
// 输出:Hello World
stream.forEach(System.out::println);
name = null;
stream = Stream.ofNullable(name);
// 无输出
stream.forEach(System.out::println);
iterate
传入一个种子值和一个生成函数,根据生成函数生成一个无限流。
文字有点太抽象,直接看代码吧
public static void main(String[] args) {
// 初始值从1开始,每次将前一个数乘以2
Stream.iterate(1, n -> n * 2)
.limit(10)
.forEach(System.out::println);
// 也可以添加判断条件,当n大于10时停止
Stream.iterate(1, n -> n n + 1)
.forEach(System.out::println);
// 生成自定义对象
Stream.iterate(new Person("John Wick", 30),
person -> new Person(person.getName(), person.getAge() + 1))
.limit(10)
.forEach(System.out::println);
// 生成斐波那契数列
Stream.iterate(new int[]{0, 1}, arr -> new int[]{arr[1], arr[0] + arr[1]})
.mapToInt(arr -> arr[0])
.limit(10)
.forEach(System.out::println);
// 生成无限序列的日期
Stream.iterate(LocalDate.now(), date -> date.plusDays(1))
.limit(10)
.forEach(System.out::println);
}
自己可以复制代码运行看看......
4. 私有接口方法(Private Interface Methods)
私有方法作用范围限制在类或接口内部,外部类无法访问和重写
有什么用呢?比如可以将一些通用功能封装到私有方法中,每个实现类不用重复的去重写该方法。
下面是一个简单示例
public interface Calculator {
int add(int a, int b);
int subtract(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
/**
* 默认方法
*/
default int calculate(int a, int b, String operation) {
// 这里用的是switch新语法,不过不是在JDK9
return switch (operation) {
case "add" -> validateAndCompute(a, b, this::add);
case "subtract" -> validateAndCompute(a, b, this::subtract);
case "multiply" -> validateAndCompute(a, b, this::multiply);
case "divide" -> validateAndCompute(a, b, this::divide);
default -> throw new IllegalArgumentException("Invalid operation: " + operation);
};
}
/**
* 私有方法
*/
private int validateAndCompute(int a, int b, BinaryOperator operator) {
if (a < 0 || b < 0) {
throw new IllegalArgumentException("Numbers must be non-negative.");
}
return operator.apply(a, b);
}
}
public static void main(String[] args) {
CalculatorImpl impl = new CalculatorImpl();
int a = -1;
int b = 2;
int add = impl.calculate(a, b, "add");
System.out.println(add);
}
JDK10
1. 局部变量类型推断(语法糖)
public class VarExample {
public static void main(String[] args) {
// 推断message为String类型
var message = "Hello, world!";
// 推断number为int类型
var number = 10;
System.out.println(message);
System.out.println(number);
var list = new ArrayList();
list.add("Java");
list.add("Python");
list.add("C++");
list.add(1);
for (var item : list) {
System.out.println(item + ",类型:" + item.getClass());
}
// 输出结果
Java,类型:class java.lang.String
Python,类型:class java.lang.String
C++,类型:class java.lang.String
1,类型:class java.lang.Integer
}
}
2. stream
中 collectors
可指定为不可变的集合
// 以前的语法
list.stream().collect(Collectors.toList())
// 新增加的
list.stream().collect(Collectors.toUnmodifiableList());
list.stream().collect(Collectors.toUnmodifiableSet());
list.stream().collect(Collectors.toUnmodifiableMap());
JDK11
1. 命令行运行java
程序
在命令行下,如果要运行一个 java
类,需要先使用 javac
命令编译类文件,再使用 java
命令运行编译后的文件,这个操作在刚学的时候肯定都做过;
现在在 JDK11
中只需要 java
一个命令就可以了。
[root@localhost ~]# java Hello.java
2. 最常用的 string
空判断
String str = "";
boolean blank = str.isBlank();
复制
String str = "Hello World;";
// 重复次数2
String repeat = str.repeat(2);
// 输出 Hello World;Hello World;
System.out.println(repeat);
将字符串拆分成行
String text = "Hello\nWorld";
// 使用 lines() 方法将字符串拆分成行,并返回一个流
Stream lines = text.lines();
// 此时 lineList 中是两个对象:Hello、World
List lineList = lines.collect(Collectors.toList());
去除字符串前后空格
String str = " Hello World ; ";
// 输出 Hello World ;
System.out.println(str.strip());
3. 读写文件 File API
public static void main(String[] args) {
Path filePath = Path.of("C:\Users\Desktop\test.txt");
String content = "Hello World!";
try {
// 写文件 writeString
Files.writeString(filePath, content);
System.out.println("写好了!");
} catch (IOException e) {
e.printStackTrace();
}
try {
// 读文件 readString
String text = Files.readString(filePath);
System.out.println(text);
} catch (IOException e) {
e.printStackTrace();
}
// bytes 写
String content = "Hello, World!";
try {
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
System.out.println("写好了!");
} catch (IOException e) {
e.printStackTrace();
}
// bytes 读
try {
byte[] fileBytes = Files.readAllBytes(filePath);
String fileContent = new String(fileBytes, StandardCharsets.UTF_8);
System.out.println(fileContent);
} catch (IOException e) {
e.printStackTrace();
}
}
4. JDK10
中的 var
局部变量类型推断,在 lambda
表达式中也能用了
BinaryOperator sum = (var a, var b) -> a + b;
int result = sum.apply(10, 20);
// 输出 30
System.out.println(result);
JDK12
1. switch
表达式,预览版本
优化 switch
表达式语法
2. 文件内容对比 Files.mismatch
返回它们之间第一个不匹配的字符的位置,如果两个文件完全相同,该方法将返回 -1
。
String file1 = "file1.txt";
String file2 = "file2.txt";
long mismatch = Files.mismatch(Path.of(file1), Path.of(file2));
System.out.println(mismatch);
JDK13
1. switch
新增 yield
(预览版本)
2. 文本块,预览版本
JDK14
1. 优化空指针异常打印信息
public static void main(String[] args) {
String a = "hello";
String b = null;
int len = a.length() + b.length();
System.out.println(len);
}
现在空指针异常,在异常信息中会直接告诉你是哪个对象导致的,以前还要自己去排查,这个功能还是非常有用的
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "b" is null
2. switch
表达式 ,正式版本(语法糖)
int day = 2;
String dayName = switch (day) {
case 1 -> "星期一";
case 2 -> "星期二";
case 3 -> "星期三";
default -> {
// yield 关键字
yield "无效数字";
}
};
// 输出 星期三
System.out.println(dayName);
int day = 6;
String dayType;
switch (day) {
case 1, 2, 3, 4, 5 -> dayType = "上班";
case 6, 7 -> dayType = "不上班";
default -> dayType = "无效数字";
}
// 输出 不上班
System.out.println(dayType);
JDK15
1. ZGC
没用过应该也都听过了吧,JDK15 中正式发布
java -XX:+UseZGC ApplicationName
2. 文本块,正式版本
终于不用各种转义拼接了
public static void main(String[] args) {
String textBlock = """
Hello,
This is a text block.
It allows you to write
multiple lines of text
without the need for
escape characters or
concatenation operators.
""";
System.out.println(textBlock);
}
JDK16
1. instanceof
,正式版本
Object object = "Hello";
if (object instanceof String s) {
System.out.println(s);
}
类型匹配对了后,直接就转换为指定类型了,省了一步
2. 记录类 record
,正式版本
详细使用可以看我之前写的文章:JDK16新特性中的record类是什么以及如何使用
3. 密封类 sealed
,预览版本
详细使用看下面的 JDK17
JDK17
1. switch
类型匹配,预览版本
Object value = 42;
// 使用 switch 类型匹配进行处理
switch (value) {
case Integer i -> System.out.println("Integer value: " + i);
case String s -> System.out.println("String value: " + s);
case Double d -> System.out.println("Double value: " + d);
case Long l && l > 0 -> System.out.println("Positive Long value: " + l);
default -> System.out.println("Unknown value");
}
2. 密封类(sealed),正式版本
密封类什么意思呢,就是你想让这个类只能指定的类能继承/实现,其它的类不能继承/实现,对比 final
使用起来更加灵活
public sealed interface Expr permits Add, Multiply {
// 使用 permits 指定了只有 Add、Multiply 两个类可以实现该接口
}
public final class Add implements Expr {
}
public final class Multiply implements Expr {
}
public class Test2 {
public static void main(String[] args) {
Expr expr1 = new Add();
Expr expr2 = new Multiply();
process(expr1);
process(expr2);
}
private static void process(Expr expr) {
switch (expr) {
case Add add -> System.out.println("add");
case Multiply multiply -> System.out.println("multiply");
// 这里不需要 default
}
}
}
因为在 Expr
接口类中指定了只有 Add
和 Multiply
这两个类可以实现 Expr
接口,所以在 switch
语句最后那里可以不写 default
下面演示一下 record
、 sealed
和 switch
的类型匹配,三者的配合使用
定义一个接口
public interface Future {
AsyncReturn get();
}
使用sealed
定义一个密封类接口,里面定义 record
类型的实现类
public sealed interface AsyncReturn {
record Success(V result) implements AsyncReturn {
}
record Failure(Throwable cause) implements AsyncReturn {
}
record Timeout() implements AsyncReturn {
}
record Interrupted() implements AsyncReturn {
}
}
使用
public static void main(String[] args) {
// 创建一个线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
Future future = () -> {
// 在这里执行异步操作,并返回相应的AsyncReturn实例
try {
// 模拟异步操作,这里使用线程池提交一个任务
java.util.concurrent.Future asyncResult = executor.submit(() -> {
// 模拟耗时操作
Thread.sleep(2000);
return "Hello World!";
});
// 获取异步操作结果,阻塞等待结果
String result = asyncResult.get();
// 返回异步操作成功的结果
return new AsyncReturn.Success(result);
} catch (Exception e) {
// 返回异步操作失败的原因
return new AsyncReturn.Failure(e);
}
};
// 调用Future的get()方法获取异步操作的结果
AsyncReturn asyncResult = future.get();
// 使用 switch 类型匹配进行处理
switch (asyncResult) {
case AsyncReturn.Success successResult -> {
String result = successResult.result();
System.out.println("异步操作成功: " + result);
}
case AsyncReturn.Failure failureResult -> {
Throwable cause = failureResult.cause();
System.out.println("异步操作失败: " + cause.getMessage());
}
case AsyncReturn.Timeout timeoutResult -> System.out.println("超时");
case AsyncReturn.Interrupted interruptedResult -> System.out.println("中断");
}
// 关闭线程池
executor.shutdown();
}
大致就这些吧。