JDK17 New Feature
admin
2024-03-05 13:49:43

JDK17 New Feature

引子

新冠疫情已经席卷中国三年了。但是,开源社区并没有停止更新的脚步,Spring Boot 于 2022.11.24日 发布了 3.0.0的版本,大致浏览了该版本的新特性 。印象最深的莫过于 需要升级JDK17的版本,不支持JDK17 以下的版本。于是,最终有了这篇博文。在这篇文章中,我们将学会JDK17语言层面的一些特性,并可以将这些新特性运用到项目中。主要包含

  • Pattern Matching
  • Records
  • Sealed Classes
  • Switch Expressions
  • Text Blocks

这些功能在之前的JDK版本中作为预览版或者最终版发布,总之在JDK17中,开发者可以使用以上所有的特性。现在开始来逐一学习。

Pattern Matching

模式匹配 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!");}}
}

Records

JDK17 Records 对于不可变的数据载体非常有用,其主要特点如下:

  • 不可变行
  • 对象属性私有且不可更改 - 使用private、final 进行修饰
  • 类里面所有属性通过构造函数赋值
  • 所有属性必须有 getters, equals, hashCode, toString 方法
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

Sealed Classes 主要针对JAVA的继承特性进行了限定。学过JAVA的同学都知道,当一个类不允许被继承,需要在类声明加上

  • 利用final关键字修饰
  • 让类变成 private

针对原生的扩展能力。JDK17引入了 Sealed Classes概念,其核心是:通过 sealed关键字来描述某个类为 sealed class。同时使用permits关键字来限定可以继承,或者实现该类的类型有哪些。

需要注意的是:sealed可以修饰的是类(class)或者接口(interface),所以permits关键字的位置应该在extends或者implements之后。

  • 定义一个sealed class,并允许特定的 类进行扩展
/*** 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 Expressions

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 定义开始, 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);}
}

相关内容

热门资讯

关税风暴过后,第一外贸大省怎么... 最近,在“中国灯饰之都”古镇镇,中山梵尔照明科技有限公司一天之内就往法国发了整整9个货柜。该公司总经...
总经理会议运作、投资管理不规范... 转自:财联社面对每天上千份上市公司公告该看哪些?重大事项公告动辄几十页几百页重点是啥?公告里一堆专业...
广州市民徒步白云山踩中捕兽夹,... 转自:扬子晚报1月18日,有广州市民发帖称,在白云山景区徒步时,在一家餐馆附近的山上不慎踩中捕兽夹。...
受让方成立仅6天!上海国资超1... 每经记者|蔡鼎    每经编辑|陈旭     江化微(SH603078,股价21....
《神断狄仁杰》中的反面人物沙尔... 沙尔汗是《神断狄仁杰》中的反面人物,由傅隽饰演。剧中,他是朝廷内侍省善金局中的四品将作大监,被武则天...