JDK17新特性
![JDK17新特性](https://codeleader.oss-cn-beijing.aliyuncs.com/site/20240630215331.png)
JDK17新特性
有些特性其实在JDK17之前就出现了。
1.文本块
在Java中,通常需要使用String类型表达HTML,XML,SQL或JSON等格式的字符串,在进行字符串赋值时需要进行转义和连接操作,然后才能编译该代码,这种表达方式难以阅读并且难以维护。
在JDK13中使用"""
作为文本块的开始符和结束符,在其中就可以放置多行的字符串,不需要进行任何转义。因此,文本块将提高Java程序的可读性和可写性。
旧版写法:
public static String getHtmlJDK8() {
return "<html>\n" +
" <body>\n" +
" <p>HelloWorld</p>\n" +
" </body>\n" +
"</html>";
}
新版写法:
public static String getHtmlJDK17() {
return """
<html>
<body>
<p>HelloWorld</p>
</body>
</html>
""";
}
2.增强的空指针异常(NullPointerException)
在 JDK 14 中引入的增强的空指针异常(NullPointerException)消息功能极大地帮助了开发人员调试和解决代码中的 NPE 问题。这一特性也在 JDK17 中得到了保留和完善。
当发生空指针异常时,JVM 会提供详细的信息,指出哪个变量或表达式是 null
,从而帮助开发人员快速定位问题。以下是一些示例,展示了增强的 NPE 消息是如何工作的:
简单的NPE:
try {
//简单的空指针
String str = null;
System.out.println(str.length());
} catch (Exception e) {
e.printStackTrace();
}
在上述代码中,传统的 NPE 消息可能只是简单地告诉你发生了空指针异常,而增强的 NPE 消息会告诉你
str
是null
。
复杂一点的NPE:
try {
//复杂一点的空指针
var arr = List.of(null);
String str = (String) arr.get(0);
System.out.println(str.length());
} catch (Exception e) {
e.printStackTrace();
}
3.Record
**JDK14中预览特性:神说要用record,于是就有了。**实现一个简单的数据载体类,为了避免编写:构造函数,访问器,equals(),hashCode () ,toString ()等,Java 14推出record。
record 是一种全新的类型,它本质上是一个 final 类,同时所有的属性都是 final 修饰,它会自动编译出 public get 、hashcode 、equals、toString、构造器等结构,减少了代码编写量。
具体来说:当你用record
声明一个类时,该类将自动拥有以下功能:
-
获取成员变量的简单方法,比如例题中的 name() 和 partner() 。注意区别于我们平常getter()的写法。
-
一个 equals 方法的实现,执行比较时会比较该类的所有成员属性。
-
重写 hashCode() 方法。
-
一个可以打印该类所有成员属性的 toString() 方法。
-
只有一个构造方法。
此外:
-
还可以在record声明的类中定义静态字段、静态方法、构造器或实例方法。
-
不能在record声明的类中定义实例字段;类不能声明为abstract;不能声明显式的父类等。
传统的写法:
public class Point {
private final int x;
private final int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Point point = (Point) o;
return x == point.x && y == point.y;
}
@Override
public int hashCode() {
return Objects.hash(x, y);
}
@Override
public String toString() {
return "Point{" +
"x=" + x +
", y=" + y +
'}';
}
}
使用record:
public record PointRecord(int x,int y) {
public static void main(String[] args) {
Point point = new Point(2,3);
PointRecord record = new PointRecord(2, 3);
System.out.println(point);
System.out.println(record);
}
}
4.全新的switch表达式
传统switch声明语句的弊端:
-
匹配是自上而下的,如果忘记写break,后面的case语句不论匹配与否都会执行; --->case穿透
-
所有的case语句共用一个块范围,在不同的case语句定义的变量名不能重复;
-
不能在一个case里写多个执行结果一致的条件;
-
整个switch不能作为表达式返回值;
旧版switch写法:
public static int getByJDK8(Week week){
int i=0;
switch (week){
case MONDAY:
i=1;
break;
case TUESDAY:
i=2;
break;
case WEDNESDAY:
i=3;
break;
case THURSDAY:
i=4;
break;
case FRIDAY:
i=5;
break;
case SATURDAY:
i=6;
break;
default:
i=7;
break;
}
return i;
}
public enum Week {
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
SUNDAY
}
JDK13中引入了
yield
语句,用于返回值。这意味着,switch表达式(返回值)应该使用yield,switch语句(不返回值)应该使用break。yield和return的区别在于:return会直接跳出当前循环或者方法,而yield只会跳出当前switch块。
新版写法:
public static int getByJDK17Demo(Week week){
int day=switch (week){
case null:
yield -1;
case MONDAY:
yield 1;
case TUESDAY:
yield 2;
case WEDNESDAY:
yield 3;
case THURSDAY:
yield 4;
case FRIDAY:
yield 5;
case SATURDAY:
yield 6;
default:
yield 7;
};
return day;
}
或者:
public static int getByJDK17(Week week){
int day= switch (week){
case null->-1;
case MONDAY -> 1;
case TUESDAY -> 2;
case WEDNESDAY -> 3;
case THURSDAY -> 4;
case FRIDAY -> 5;
case SATURDAY -> 6;
default -> 7;
};
return day;
}
5.私有接口方法
在 Java 9 中引入的私有接口方法允许接口提供一些共用的私有帮助方法,这些方法不能被接口的实现类访问。私有接口方法有助于减少代码重复,提高代码可读性和维护性。私有接口方法可以是普通方法,也可以是静态方法。
示例1:
public interface MyInterface {
default void defaultMethod1() {
System.out.println("Default Method 1");
privateMethod();
}
default void defaultMethod2() {
System.out.println("Default Method 2");
privateMethod();
}
private void privateMethod() {
System.out.println("Private Method");
}
}
class MyClass implements MyInterface {
public static void main(String[] args) {
MyClass myClass = new MyClass();
myClass.defaultMethod1();
myClass.defaultMethod2();
}
}
示例2:
public interface Calculator {
default int add(int a, int b) {
return a + b;
}
default int subtract(int a, int b) {
return a - b;
}
default int multiply(int a, int b) {
return a * b;
}
default int divide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Division by zero");
}
return a / b;
}
default void printOperations() {
System.out.println("Available operations: add, subtract, multiply, divide");
printSeparator();
}
private void printSeparator() {
System.out.println("------------------------------");
}
}
class CalculatorImpl implements Calculator {
public static void main(String[] args) {
CalculatorImpl calc = new CalculatorImpl();
calc.printOperations();
System.out.println("Add: " + calc.add(10, 5));
System.out.println("Subtract: " + calc.subtract(10, 5));
System.out.println("Multiply: " + calc.multiply(10, 5));
System.out.println("Divide: " + calc.divide(10, 5));
}
}
6.instanceof的模式匹配
这一特性允许你在 instanceof
检查的同时声明一个变量,从而避免冗余的类型转换代码。
旧版写法:
if (obj instanceof String) {
String str = (String) obj;
System.out.println(str.length());
}
新版写法:
if (obj instanceof String str) {
System.out.println(str.length());
}
示例:
public class InstanceofPatternMatching {
public static void main(String[] args) {
Object obj = "Hello, world!";
if (obj instanceof String str) {
System.out.println(str.length()); // 输出: 13
}
}
}
7.局部变量类型推断
局部变量的显示类型声明,常常被认为是不必须的,给一个好听的名字反而可以很清楚的表达出下面应该怎样继续。本新特性允许开发人员省略通常不必要的局部变量类型声明,以增强Java语言的体验性、可读性。
//1.局部变量的实例化
var list = new ArrayList<String>();
var set = new LinkedHashSet<Integer>();
//2.增强for循环中的索引
for (var v : list) {
System.out.println(v);
}
//3.传统for循环中
for (var i = 0; i < 100; i++) {
System.out.println(i);
}
//4. 返回值类型含复杂泛型结构
var iterator = set.iterator();
//Iterator<Map.Entry<Integer, Student>> iterator = set.iterator();
不适用场景:
- 声明一个成员变量
- 声明一个数组变量,并为数组静态初始化(省略new的情况下)
- 方法的返回值类型
- 方法的参数类型
- 没有初始化的方法内的局部变量声明
- 作为catch块中异常类型
- Lambda表达式中函数式接口的类型
- 方法引用中函数式接口的类型
8.String存储结构和API变更
String 再也不用 char[]
来存储啦,改成了 byte[]
加上编码标记,节约了一些空间
(1)isBlank()
:判断字符串是否为空白
//2.isBlank 字符串判空
//如果字符串为空或仅包含空白字符。则返回true,否则返回false
String s="java";
System.out.println(s.isBlank()); //false
System.out.println("".isBlank()); //true
System.out.println(" ".isBlank());//true
(2)repeat
:重复生成字符串
//1.repeat:重复生成字符串
String s="java";
System.out.println(s.repeat(5));//javajavajavajavajava
(3)lines
:从给定多行字符串中提取的行流,并用终止符分隔,行终止符可以是换行符\n 回车符\r 回车符后紧跟换行符\r\n
try {
String str="H\nE\nL\nL\rO";
Stream<String> lines = str.lines();
lines.forEach(System.out::println);
} catch (Exception e) {
e.printStackTrace();
}
(4)transform
:接收一个转换函数,实现字符串的转换
//transform
String str1="java";
//使用transform将字符全部转换为大写
String transform = str1.transform(String::toUpperCase);
System.out.println(transform);//JAVA
9.集合类的工厂方法
旧版写法:
List<String> list=new ArrayList<>();
list.add("石昊");
list.add("萧炎");
list.add("柳神");
list.forEach(System.out::println);
新版写法:
List<String> list1 = List.of("石昊", "萧炎", "柳神");
list1.forEach(System.out::println);
10.Stream API增强
takewhile
:从Stream中依次获取满足条件的元素,直到不满足条件为止
//takewhile:从Stream中依次获取满足条件的元素,直到不满足条件为止
//一旦条件不满足的时候,后面的所有元素都会被忽略
List<Integer> list = Stream.of(2, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.takeWhile(x -> x % 2 == 0)
.toList();
//2,2
list.forEach(System.out::println);
dropwhile
:顺序去掉符合条件的值,直到不满条件时终止判断
//dropwhile:顺序去掉符合条件的值,直到不满条件时终止判断
List<Integer> dropList = Stream.of(2, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.dropWhile(x -> x % 2 == 0)
.toList();
//3,4,5,6,7,8,9,10
dropList.forEach(System.out::println);
ofNullable
:支持传入空流,若没有这个且传入一个空流,会抛出NPE
//ofNullable 支持传入空流,若没有这个且传入一个空流,会抛出NPE
var count = Stream.ofNullable(null).count();
System.out.println(count);//0
11.密封类
在 Java 中如果想让一个类不能被继承和修,这时我们应该使用 final 关键字对类进行修饰。不过这种要么可以继承,要么不能继承的机制不够灵活,有些时候我们可能想让某个类可以被某些类型继承,但是又不能随意继承,是做不到的。Java 15 尝试解决这个问题,引入了 sealed 类,被 sealed 修饰的类可以指定子类。这样这个类就只能被指定的类继承。
JDK15的预览特性:
通过密封的类和接口来限制超类的使用,密封的类和接口限制其它可能继承或实现它们的其它类或接口。
-
使用修饰符
sealed
,可以将一个类声明为密封类。密封的类使用保留关键字permits
列出可以直接扩展(即extends)它的类。 -
sealed
修饰的类的机制具有传递性,它的子类必须使用指定的关键字进行修饰,且只能是final、sealed、non-sealed
三者之一。
public abstract sealed class Shape permits Circle,Rectangle,Square{
}
//final表示Circle不能再被继承了
final class Circle extends Shape { }
sealed class Rectangle extends Shape permits TransparentRectangle,FilledRectangle {}
final class TransparentRectangle extends Rectangle {}
final class FilledRectangle extends Rectangle {}
non-sealed class Square extends Shape {} //non-sealed表示可以允许任何类继承
12.Java的REPL工具: jShell命令
Java 终于拥有了像Python 和 Scala 之类语言的REPL工具(交互式编程环境,read - evaluate - print - loop):jShell
。以交互式的方式对语句和表达式进行求值。即写即得、快速运行。
13.革命性的 ZGC
官方宣称ZGC的垃圾回收停顿时间不超过10ms,能支持高达16TB的堆空间,并且停顿时间不会随着堆的增大而增加。
JDK11:引入革命性的 ZGC
ZGC,这应该是JDK11最为瞩目的特性,没有之一。
ZGC是一个并发、基于region、压缩型的垃圾收集器。
ZGC的设计目标是:支持TB级内存容量,暂停时间低(<10ms),对整个程序吞吐量的影响小于15%。 将来还可以扩展实现机制,以支持不少令人兴奋的功能,例如多层堆(即热对象置于DRAM和冷对象置于NVMe闪存),或压缩堆。
JDK13:ZGC:将未使用的堆内存归还给操作系统
ZGC是Java 11引入的新的垃圾收集器,经过了多个实验阶段,在JDK15中终于成为正式特性。
JDK16:ZGC 并发线程处理
14 更简化的编译运行程序
我们的认知里,要运行一个 Java 源代码必须先编译,再运行。
// 编译
javac JavaStack.java
// 运行
java JavaStack
而在 Java 11 版本中,通过一个 java 命令就直接搞定了,如下所示:
结尾
Java 代码虽然进行了一些类型推断等改进,更易用的集合 API 等,但仍然给开发者留下了过于刻板、形式主义的印象,这是一个长期的改进方向。虽然标榜面向对象编程,却毫不顾忌的加入面向接口编程思想,又扯出匿名对象的概念,每增加一个新的东西,都是对Java根本(面向对象思想)的一次冲击。
- 75128
- 0
-
分享