Java17新特性详解与安装

2023-09-09 15:46:26 浏览数 (1)

前言

Java 17 是 2021 年 9 月 14 日正式发布的,距今也已经快2年多了,是一个长期支持(LTS)版本。Java 17 这个版本非常重要,Spring Framework 6.0Spring Boot 3.0 最低支持都是 Java 17,搞Java开发肯定是离不开Spring这个主流的框架。下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线,可以看得到Java17支持到2029年9月。

增强Switch

之前的 switch 写法如下:

代码语言:javascript复制
String a = "jdk17";
     switch (a){
         case "jdk8":
             System.out.println("我是jdk8");
             break;
         case "jdk17":
             System.out.println("我是jdk17");
             break;
         default:
             System.out.println("未知");
             break;
     }

从jdk12后可以通过 switch 表达式来进行简化。使用箭头“->”,并且不需要每个 casebreak,大大提高了我们的编写效率,如下所示:

代码语言:javascript复制
switch (a) {
            case "jdk8" -> System.out.println("我是jdk8");
            case "jdk17" -> System.out.println("我是jdk17");
            default -> System.out.println("未知");
        }

当我们需要赋值给一个变量的时候,不需要在每个case里头挨个赋值,其实 switch 也可以返回一个值,例如:

代码语言:javascript复制
String a = "jdk17";
        String who = switch (a) {
            case "jdk8" -> "我是jdk8";
            case "jdk17" -> "我是jdk17";
            default -> "未知";
        };
        System.out.println(who);

输出:

代码语言:javascript复制
我是jdk17

如果是匹配多个case 的话用逗号分开,例如:

代码语言:javascript复制
String a = "jdk17";
        String who = switch (a) {
            case "jdk8","jdk17" -> "我是jdk家族";
            case "spring","spring boot" -> "我是spring家族";
            default -> "未知";
        };
 System.out.println(who);

如果你想在case里做不止一件事,比如在返回之前先进行一些计算或者打印操作。可以通过大括号来作为case块,最后的返回值使用关键字 yield 进行返回。

代码语言:javascript复制
String a = "spring";
        String who = switch (a) {
            case "jdk8","jdk17" -> {
                System.out.println(1 1);
                yield "我是jdk家族";
            }
            case "spring","spring boot" -> {
                System.out.println(2 2);
                yield "我是spring家族";
            }
            default -> "未知";
        };
        System.out.println(who);

输出:

代码语言:javascript复制
4
我是spring家族

以及之前的switch只支持 数值和字符串常量的匹配 ,而现在还支持对对象的类型来进行匹配,例如:

代码语言:javascript复制
Object a = 888;
        String who = switch (a) {
            case null -> "is null";
            case Integer i -> String.format("i is %s",i);
            case String s -> String.format("s is string %s",s);
            default -> a.toString();
        };
        System.out.println(who);

输出:

代码语言:javascript复制
i is 888

文本块

在Java17之前的版本里,如果我们需要定义一个字符串,比如一个JSON数据或者一些html等,基本都是采用拼接的方式去定义,大量的加号和转义的双引号非常的恶心且难看,例如:

代码语言:javascript复制
String text = "{n"  
                "  "name": "小黑说Java",n"  
                "  "age": 18,n"  
                "  "address": "北京市西城区"n"  
                "}";

而从jdk15后这个写法才得以改善,只需要在前后加上 """ 。

代码语言:javascript复制
String text = """
                {
                  "name": "小黑说Java",
                  "age": 18,
                  "address": "北京市西城区"
                }
                """;

显然,更方便,更简洁了。若是在文本块内加入空格或者换行之类的,请参考以下:

代码语言:javascript复制
s:表示空格
:表示不换行
s:表示添加一个空格且不换行

instanceof增强

通常我们使用instanceof时,一般发生在需要对一个变量的类型进行判断,如果符合指定的类型,则强制类型转换为一个新变量。

之前的写法:

代码语言:javascript复制
List<?> rows = selectMenuList(menu, getUserId()).getRows();
List<SysMenu> menus =  new ArrayList<>();
rows.forEach(r->{
   if (r instanceof SysMenu)
       menus.add((SysMenu)r);
});

jdk17后的写法:

代码语言:javascript复制
List<?> rows = selectMenuList(menu, getUserId()).getRows();
List<SysMenu> menus =  new ArrayList<>();
rows.forEach(r->{
   if (r instanceof SysMenu sysMenu)
       menus.add(sysMenu);
});

Record关键字

record用于创建不可变的数据类。在这之前如果你需要创建一个存放数据的类,通常需要先创建一个Class,然后生成构造方法、getter、setter、hashCode、equals和toString等这些方法,或者使用Lombok来简化这些操作。而record就基本相当于实体类 Lombok的效果,但是 record 是不支持继承的,适合数据量少且不复杂的情况下使用。

创建一个实体类 Pon:

代码语言:javascript复制
@Data
@AllArgsConstructor
public class Pon {
    private String name;
    private String nickName;
    private Integer addrNum;
}

然后进行一些简单的测试

代码语言:javascript复制
public class JunitTest {
    @Test
    void test() {

        Pon pon = new Pon("张三", "小三", 65220);
        System.out.println(pon.getName());
        System.out.println(pon.getNickName());
        System.out.println(pon.getAddrNum());
    }
}

输出:

代码语言:javascript复制
张三
小三
65220

以下是使用record代替前面的Pon类,record的属性不需要去定义,直接通过参数的形式设置。

代码语言:javascript复制
public record RecordPon(String name, String nikName, Integer addrNum) {
  
}

接下来对RecordPon进行一些简单的测试

代码语言:javascript复制
public class JunitTest {
    @Test
    void test() {
        RecordPon recordPon = new RecordPon("张三", "小三", 65220);
        System.out.println(recordPon.name());
        System.out.println(recordPon.nikName());
        System.out.println(recordPon.addrNum());
        RecordPon recordPon2 = new RecordPon("李四", "小四", 65220);
        System.out.println(recordPon2.name());
        System.out.println(recordPon2.nikName());
        System.out.println(recordPon2.addrNum());
    }
}

输出:

代码语言:javascript复制
张三
小三
65220
李四
小四
65220

当然了,record 同样也有构造方法,可以在构造方法中对数据进行一些操作,例如:

代码语言:javascript复制
public record RecordPon(String name, String nikName, Integer addrNum) {
    public RecordPon {
        if (Objects.equals(name, ""))
            System.err.println( "name is not null");
    }
}

总结:Lombokrecord 是不同的工具,服务于不同的目的。此外,Lombok 更加灵活,它可以用于 record 受限的场景。

Helpful NullPointerExceptions

Java8 我们如果遇到NPE,通常只会输出将显示NullPointerException发生的行号,但不知道哪个方法调用时产生的null,必须通过调试的方式找到。对于复杂度高的代码来讲就非常耗时,而从Helpful NullPointerExceptions可以在我们遇到NPE时节省一些时间,会准确显示发生NPE的精确位置,比如以下代码会发生一个NPE:

代码语言:javascript复制
public class JunitTest {
    @Test
    void test() {
        String str = null;
        int length = str.length();
    }
}

输出:

密封类 sealed class

密封类是一种特殊的类,它用来表示受限的类继承结构,即一个类只能有有限的几种子类,而不能有任何其他类型的子类。这样可以让我们更好的控制哪些类可以对我定义的类进行扩展,而在这之前一个类要么是可以被extends的,要么是final的任何人都不能继承的,只有这两种选项,没有什么灵活性。首先创建Animal、DogCat

代码语言:javascript复制
public abstract class Animal {
     public void put(){
        System.out.println("我是动物");
    }
}
代码语言:javascript复制
public class Dog extends Animal {

}
代码语言:javascript复制
public class Cat extends Animal {

}

在这里这个Animal 是可以被任何一个类继承的,例如:

代码语言:javascript复制
public class JunitTest {
    @Test
    void test() {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Animal animal = cat;
        Animal animal2 = dog;
        class china extends Animal{}
        class usa extends Dog{}
    }
}

除了DogCat类能继承之外,这里创建的china类也能继承Animal类,usa类也继承Dog类。ok这本来没什么问题,就是这个关系有点不正常,国家怎么能继承于动物呢?

Jdk17中通过密封类可以解决这个问题,主要就这几个关键字:

  • final:不允许被继承
  • sealed:密封类(需要指定哪个类可以扩展它)
  • non-sealed:可以被任何类继承
  • permits :指定哪个类可以继承于我

现在我们将上面的Animal 类改成密封类,例如:

代码语言:javascript复制
public sealed class Animal permits Dog{
    public void put(){
        System.out.println("我是动物");
    }
}

此处的Animal是密封类,指定了只有Dog类可以继承它

代码语言:javascript复制
public sealed class Dog extends Animal permits DogSon{
}

而此时Dog类也是一个密封类,它也指定了只有DogSon这个类可以继承它。这里做个小测试,创建一个BigDog类并继承Dog

直接告诉你 密封层次结构中不允许使用"BigDog" ,需要给BigDog指定这个permits ,其他亦是如此,除非你定义成non-sealed

备注????:密封类子类必须是 final类、sealed类或non-sealed类,并且 父类子类 必须在同一个包下

开始安装

下载安装可以参考我之前的文章:《Jdk17安装 环境配置详细教程【Windows》

总结

以上主要介绍了Jdk17的一些主流的新特性,更多具体特性请参考官方文档: https://www.oracle.com/java/technologies/javase/17-relnote-issues.html#NewFeature

0 人点赞