新冠疫情已经席卷中国三年了。但是,开源社区并没有停止更新的脚步,Spring Boot 于 2022.11.24日 发布了 3.0.0的版本,大致浏览了该版本的新特性 。印象最深的莫过于 需要升级JDK17的版本,不支持JDK17 以下的版本。于是,最终有了这篇博文。在这篇文章中,我们将学会JDK17语言层面的一些特性,并可以将这些新特性运用到项目中。主要包含
这些功能在之前的JDK版本中作为预览版或者最终版发布,总之在JDK17中,开发者可以使用以上所有的特性。现在开始来逐一学习。
模式匹配 instanceof 特性在类型比较后进行类型强转。在下面的代码示例中,再将对象进行判断比较适合,重新强转为新的变量:
package com.andy.spring.boot.docker.jdk17.feature;
import org.junit.jupiter.api.Test;
public class InstanceOfPatternMatching {
@Test
public void instanceOfPatternMatchingTest(){
Object o = "string as an object";
if(o instanceof String str){
System.out.println(str.toUpperCase());
}
if(o instanceof String str && !str.isEmpty()){
System.out.println(str.toUpperCase());
}
Object obj = 123;
if(!(obj instanceof String)){
throw new RuntimeException("Please provide string!");
}
}
}
JDK17 Records 对于不可变的数据载体非常有用,其主要特点如下:
record Footballer(String name, int age, String team) { }
请看以上代码,使用 record 关键字定义构造函授后,编译器将自动定义:
私有、不可变的属性 age、name、team , 效果如下
private final String name;
private final int age;
private final String team;
Footballer(String name, int age, String team) { }
所有字段均有 getters 方法
所有字段均有 hashCode、equals、toString 方法
生成默认的构造方法给字段赋值
package com.andy.spring.boot.docker.jdk17.feature;
import org.junit.jupiter.api.*;
public class Records {
@BeforeEach
void setup(TestInfo testInfo){
System.out.println("setup===================: " + testInfo.getDisplayName());
}
@AfterEach
void tearDown(TestInfo testInfo){
System.out.println("tearDown================: " + testInfo.getDisplayName());
}
record Footballer(String name, int age, String team) { }
//Canonical Constructor
Footballer footballer = new Footballer("Ronaldo", 36, "Manchester United");
@Test
public void recordTest(){
System.out.println("Footballer's name: " + footballer.name);
System.out.println("Footballer's age: " + footballer.age);
record Basketballer(String name, int age) { }
// equals
boolean isFootballer1 = footballer.equals(new Footballer("Ozil", 32, "Fenerbahce")); // false
System.out.println("Is first one footballer? " + isFootballer1);
boolean isFootballer2 = footballer.equals(new Basketballer("Lebron", 36)); // false
System.out.println("Is second one footballer? " + isFootballer2);
boolean isFootballer3 = footballer.equals(new Footballer("Ronaldo", 36, "Manchester United")); // true
System.out.println("Is third one footballer? " + isFootballer3);
//hashcode
int hashCode = footballer.hashCode(); // depends on values of x and y
System.out.println("Hash Code of Record: " + hashCode);
//toString
String toStringOfRecord = footballer.toString();
System.out.println("ToString of Record: " + toStringOfRecord);
}
/**
* 覆盖 record 默认行为,定制构造方法
*/
@Test
public void record2Test() {
record Engineer(String name,int age){
Engineer {
if(age < 1){
throw new IllegalArgumentException("Age less than 1 is not allowed!");
}
//Custom modifications
name = name.toUpperCase();
}
public int age(){
return this.age;
}
}
Engineer engineer1 = new Engineer("Onur", 39);
System.out.println(engineer1);
Assertions.assertEquals("ONUR", engineer1.name);
Exception exception = Assertions.assertThrows(IllegalArgumentException.class, () -> new Engineer("Alex", 0));
Assertions.assertEquals("Age less than 1 is not allowed!", exception.getMessage());
}
}
Sealed Classes 主要针对JAVA的继承特性进行了限定。学过JAVA的同学都知道,当一个类不允许被继承,需要在类声明加上
针对原生的扩展能力。JDK17引入了 Sealed Classes概念,其核心是:通过 sealed关键字来描述某个类为 sealed class。同时使用permits关键字来限定可以继承,或者实现该类的类型有哪些。
需要注意的是:sealed可以修饰的是类(class)或者接口(interface),所以permits关键字的位置应该在extends或者implements之后。
/**
* Sealed Parent Class which only allows Square and Rectangle as its children.
*/
@Getter
public sealed class Shape permits Square, Rectangle{
protected int edge1, edge2;
protected Shape(int edge1, int edge2) {
this.edge1 = edge1;
this.edge2 = edge2;
}
}
定义一个sealed interface,并允许特定的 类进行扩展
package com.andy.spring.boot.docker.jdk17.feature.sealed;
/**
* ShapeService 接口允许被 Square,Square 实现
*/
public sealed interface ShapeService permits Square, Rectangle {
default int getArea(int a, int b) {
return a * b;
}
int getPerimeter();
}
编写扩展类 Rectangle,继承封装类、实现封装接口
package com.andy.spring.boot.docker.jdk17.feature.sealed;
public final class Rectangle extends Shape implements ShapeService {
public Rectangle(int edge1, int edge2) {
super(edge1, edge2);
}
@Override
public int getPerimeter() {
return 2 * (edge1 + edge2);
}
}
编写扩展类 Rectangle,继承封装类、实现封装接口
package com.andy.spring.boot.docker.jdk17.feature.sealed;
public final class Square extends Shape implements ShapeService {
public Square(int edge1, int edge2) {
super(edge1, edge2);
}
@Override
public int getPerimeter() {
return 4 * edge1;
}
}
测试代码
package com.andy.spring.boot.docker.jdk17.feature.sealed;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ShapeTest {
@Test
public void shapeTest() {
/**
* Permitted classes RECTANGLE and SQUARE
*/
//Rectangle Declaration and tests
Rectangle rectangle = new Rectangle(3, 5);
assertEquals(16, rectangle.getPerimeter());
assertEquals(15, rectangle.getArea(3, 5));
//Square Declaration and tests
Square square = new Square(3, 3);
assertEquals(12, square.getPerimeter());
assertEquals(9, square.getArea(3, 3));
}
}
Switch 表达式比之前更加简洁。为了进行对比,我们先使用传统的方式实现一个switch表达式,然后用新特性实现相同的逻辑。
package com.andy.spring.boot.docker.jdk17.feature;
public enum Position {
GOALKEEPER,
DEFENCE,
MIDFIELDER,
STRIKER,
BENCH
}
package com.andy.spring.boot.docker.jdk17.feature;
import org.junit.jupiter.api.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadLocalRandom;
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SwitchExpression {
private Map<Integer, Position> positionMap = new HashMap<>();
private int randomNumber;
private Position randomPosition;
@BeforeEach
public void setup() {
positionMap.put(1, Position.GOALKEEPER);
positionMap.put(2, Position.DEFENCE);
positionMap.put(3, Position.MIDFIELDER);
positionMap.put(4, Position.STRIKER);
randomNumber = ThreadLocalRandom.current().nextInt(1, 6);
randomPosition = Optional.ofNullable(positionMap.get(randomNumber)).orElse(Position.BENCH);
}
@AfterEach
public void tearDown() {
positionMap.clear();
}
@RepeatedTest(5)
@Order(1)
public void oldSwitchExpressionTest() {
switch (randomPosition) {
case GOALKEEPER:
System.out.println("Goal Keeper: Buffon");
break;
case DEFENCE:
System.out.println("Defence: Ramos");
break;
case MIDFIELDER:
System.out.println("Midfielder: Messi");
break;
case STRIKER:
System.out.println("Striker: Zlatan");
break;
default:
System.out.println("Please select a footballer from the BENCH!");
}
}
/**
* 使用switch expression 特性进行方法重写
*/
@RepeatedTest(5)
@Order(2)
public void newSwitchExpressionTest() {
switch (randomPosition) {
case GOALKEEPER -> System.out.println("Goal Keeper: Buffon");
case DEFENCE -> System.out.println("Defence: Ramos");
case MIDFIELDER -> System.out.println("Midfielder: Messi");
case STRIKER -> System.out.println("Striker: Zlatan");
default -> System.out.println("Please select a footballer from the BENCH!");
}
}
@RepeatedTest(5)
@Order(3)
public void newSwitchExpressionWithAssignmentTest() {
String footballer = switch (randomPosition) {
// 新特性支持 多个code 进行比较
case GOALKEEPER, DEFENCE -> {
System.out.println("Defensive Footballer Selection!");
// yield 关键字定义的 对象 返回给 footballer变量,然后输出打印
yield "Defence: Ramos";
}
case MIDFIELDER, STRIKER -> {
System.out.println("Offensive Footballer Selection!");
yield "Midfielder: Messi";
}
default -> "Please select a footballer from the BENCH!";
};
System.out.println(footballer);
}
}
从 Text Blocks 定义开始, Text Blocks 是一个字符串块,使用三个双引号 “””开头,后跟换行符,然后使用三个双引号结束。有了这一特性,开发者可以在文本块中使用换行符和引号,而不必考虑转义换行符,这样,使用JSON、SQL和类似的文本块将更加容易和易读。
package com.andy.spring.boot.docker.jdk17.feature;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
public class TextBlocks {
@AfterEach
void tearDown(TestInfo testInfo){
System.out.println();
}
@Test
public void textBlocksTest() {
String textBlockFootballers = """
{
"code": 200,
"message": "success",
"traceId": "48503586-5c79-4294-be32-5869df8b69be",
"data": {
"communityCloud": 1,
"download": 0,
"bbs": 0,
"edu": 0,
"ask": 0,
"video": 0,
"blink": 0,
"blog": 46,
"live": 0
}
}
""";
System.out.println(textBlockFootballers);
}
@Test
public void textBlocksNoLineBreaksTest() {
String textBlockFootballers = """
Footballers \
with double space indentation \
and "SW TEST ACADEMY TEAM" Rocks! \
""";
System.out.println(textBlockFootballers);
}
@Test
public void textBlocksInsertingVariablesTest() {
//预定义占位符
String textBlockFootballers = """
Footballers
with double space indentation
and "%s" Rocks!
""".formatted("SW TEST ACADEMY TEAM");
System.out.println(textBlockFootballers);
}
}