2021年9月14日Java 17发布。
Java 17是Java 11以来又一个LTS(长期支持)版本,Java 11 和Java 17之间发生了那些变化可以在OpenJDK官网找到JEP(Java增强建议)的完整列表。
在Java17之前的版本里,如果我们定义一个比较长的字符串时基本都是通过如下方式定义的:
public void lowVersion() {
String text = "{\n" +
" \"name\": \"wry\",\n" +
" \"age\": 18,\n" +
" \"address\": \"chongqin\"\n" +
"}";
System.out.println(text);
}
存在问题:
在Java17中通过文本块解决这个问题,三个双引号表示文本块:
private void highVersion() {
String text = """
{
"name": "wry",
"age": 18,
"address": "chongqin"
}
""";
System.out.println(text);
}
Java 17版本中switch表达式将允许switch有返回值,并且可以直接作为结果赋值给一个变量,等等一系列的变化。
在jdk1.8中的switch表达式比较单一,每个case只能有一个值,且每个case需要添加break语句,避免继续往下面的case执行。如:
private static void lowVesion(Integer score) {
switch (score) {
case 1:
System.out.println("很丑!");
break;
case 2:
System.out.println("还行!");
break;
default:
System.out.println("超帅!");
}
}
到Java17,switch表达式有了较大的改变,不仅支持多个条件用一个case,且支持返回值,不需要额外设置break。
private static void withSwitchExpression(Integer score) {
switch (score) {
case 1, 2 -> System.out.println("很丑!");
case 3, 4 -> System.out.println("还行!");
default -> System.out.println("超帅!");
}
}
switch表达式也可以返回一个值,把switch语句当做一个有返回值的函数使用:
private static void withReturnValue(Integer score) {
String text = switch (score) {
case 1, 2 -> "很丑!";
case 3, 4 -> "还行!";
default -> "超帅!";
};
System.out.println(text);
}
如果你想在case里想做不止一件事,又不想返回的时候退出整个函数,比如在返回之前先进行一些计算或者打印操作,可以通过大括号来作为case块,最后的返回值使用关键字yield进行返回。
private static void oldStyleWithYield(Integer score) {
System.out.println(switch (score) {
case 1, 2:
//。。。
yield "很丑!";
case 3, 4:
//。。。
yield "还行!";
default:
//。。。
yield "超帅!";
});
}
record用于创建不可变的数据类。在这之前如果你需要创建一个存放数据的类,通常需要先创建一个Class,然后生成构造方法、getter、setter、hashCode、equals和toString等这些方法,或者使用Lombok来简化这些操作。
比如定义一个Person类:
// 这里使用lombok减少代码
@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
private String address;
}
我们来通过Person类做一些测试,比如创建两个对象,对他们进行比较,打印这些操作。
public static void testPerson() {
Person p1 = new Person("yyy", 18, "chongqing");
Person p2 = new Person("xxx", 28, "hangzhou");
System.out.println(p1);
System.out.println(p2);
System.out.println(p1.equals(p2));
}
假设有一些场景我们只需要对Person的name和age属性进行打印,在有record之后将会变得非常容易。
public static void testPerson() {
Person p1 = new Person("小黑说Java", 18, "北京市西城区");
Person p2 = new Person("小白说Java", 28, "北京市东城区");
// 使用record定义
record PersonRecord(String name,int age){}
PersonRecord p1Record = new PersonRecord(p1.getName(), p1.getAge());
PersonRecord p2Record = new PersonRecord(p2.getName(), p2.getAge());
System.out.println(p1Record);
System.out.println(p2Record);
}
record也可以单独定义作为一个文件定义,但是因为Record的使用非常紧凑,所以可以直接在需要使用的地方直接定义。
package com.wry.test;
public record PersonRecord(String name, int age) {
}
密封类可以让我们更好的控制哪些类可以对我定义的类进行扩展。
在没有sealed关键字之前,一个类要么是可以被extends的,要么是final的,只有这两种选项。
密封类可以控制有哪些类可以对超类进行继承,在Java 17之前如果我们需要控制哪些类可以继承,可以通过改变类的访问级别,比如去掉类的public,访问级别为默认。
比如我们可以定义三个类:
public abstract class Furit {
}
public class Apple extends Furit {
}
public class Pear extends Furit {
}
那么我们可以在另一个包中写如下的代码:
private static void test() {
Apple apple = new Apple();
Pear pear = new Pear();
Fruit fruit = apple;
class Avocado extends Fruit {};
}
在这个包中,既可以定义Apple,Pear,也可以将apple实例赋值给Fruit,并且可以对Fruit进行继承。
如果我们不想让Fruit在本包以外被扩展,那么我们只能修改其访问权限,去掉class的public修饰符。
这样虽然可以控制被被继承,但是也会导致Fruit fruit = apple;也编译失败。在Java 17中通过密封类可以解决这个问题。
//指定Furit只能被Apple和Pear继承
public abstract sealed class Furit permits Apple, Pear {
}
//子类需要指明它是final,non-sealed或sealed的。父类不能控制子类是否可以被继承。
public non-sealed class Apple extends Furit {
}
public final class Pear extends Furit {
}
private static void test() {
Apple apple = new Apple();
Pear pear = new Pear();
// 可以将apple赋值给Fruit
Fruit fruit = apple;
// 只能继承Apple,不能继承Furit
class Avocado extends Apple {};
}
通常我们使用instanceof时,一般发生在需要对一个变量的类型进行判断,如果符合指定的类型,则强制类型转换为一个新变量。
private static void oldStyle(Object o) {
if (o instanceof Furit) {
Furit furit = (GrapeClass) o;
System.out.println("This furit is :" + furit.getName);
}
}
在使用instanceof的模式匹配后,可以将类型转换和变量声明都在if中处理。同时,可以直接在if中使用这个变量
private static void oldStyle(Object o) {
if (o instanceof Furit furit && furit.getColor()==Color.RED) {
System.out.println("This furit is :" + furit.getName);
}
}
因为只有当instanceof的结果为true时,才会定义变量fruit,所以这里可以使用&&,但是改为||就会编译报错。
如果需要将Stream转换成List,需要通过调用collect方法使用Collectors.toList(),代码非常冗长。
在Java 17中将会变得简单,可以直接调用toList()。
private static void streamToList() {
Stream<String> stringStream = Stream.of("a", "b", "c");
List<String> stringList = stringStream.toList();
}