JDK17 New Feature

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 {

    @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!");
        }
    }
}

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 {

    @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

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);
        }
        @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 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<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 定义开始, 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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值