新冠疫情已经席卷中国三年了。但是,开源社区并没有停止更新的脚步,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 {@Testpublic 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 {@BeforeEachvoid setup(TestInfo testInfo){System.out.println("setup===================: " + testInfo.getDisplayName());}@AfterEachvoid tearDown(TestInfo testInfo){System.out.println("tearDown================: " + testInfo.getDisplayName());}record Footballer(String name, int age, String team) { }//Canonical ConstructorFootballer footballer = new Footballer("Ronaldo", 36, "Manchester United");@Testpublic void recordTest(){System.out.println("Footballer's name: " + footballer.name);System.out.println("Footballer's age: " + footballer.age);record Basketballer(String name, int age) { }// equalsboolean isFootballer1 = footballer.equals(new Footballer("Ozil", 32, "Fenerbahce")); // falseSystem.out.println("Is first one footballer? " + isFootballer1);boolean isFootballer2 = footballer.equals(new Basketballer("Lebron", 36)); // falseSystem.out.println("Is second one footballer? " + isFootballer2);boolean isFootballer3 = footballer.equals(new Footballer("Ronaldo", 36, "Manchester United")); // trueSystem.out.println("Is third one footballer? " + isFootballer3);//hashcodeint hashCode = footballer.hashCode(); // depends on values of x and ySystem.out.println("Hash Code of Record: " + hashCode);//toStringString toStringOfRecord = footballer.toString();System.out.println("ToString of Record: " + toStringOfRecord);}/*** 覆盖 record 默认行为,定制构造方法*/@Testpublic void record2Test() {record Engineer(String name,int age){Engineer {if(age < 1){throw new IllegalArgumentException("Age less than 1 is not allowed!");}//Custom modificationsname = 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);}@Overridepublic 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);}@Overridepublic 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 {@Testpublic void shapeTest() {/*** Permitted classes RECTANGLE and SQUARE*///Rectangle Declaration and testsRectangle rectangle = new Rectangle(3, 5);assertEquals(16, rectangle.getPerimeter());assertEquals(15, rectangle.getArea(3, 5));//Square Declaration and testsSquare 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 positionMap = new HashMap<>();private int randomNumber;private Position randomPosition;@BeforeEachpublic 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);}@AfterEachpublic 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 {@AfterEachvoid tearDown(TestInfo testInfo){System.out.println();}@Testpublic 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);}@Testpublic void textBlocksNoLineBreaksTest() {String textBlockFootballers = """Footballers \with double space indentation \and "SW TEST ACADEMY TEAM" Rocks! \""";System.out.println(textBlockFootballers);}@Testpublic void textBlocksInsertingVariablesTest() {//预定义占位符String textBlockFootballers = """Footballerswith double space indentationand "%s" Rocks!""".formatted("SW TEST ACADEMY TEAM");System.out.println(textBlockFootballers);}
}
上一篇:第十四章《多线程》第8节:线程池