• 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!");
            }
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    Records

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

    • 不可变行
    • 对象属性私有且不可更改 - 使用private、final 进行修饰
    • 类里面所有属性通过构造函数赋值
    • 所有属性必须有 getters, equals, hashCode, toString 方法
    record Footballer(String name, int age, String team) { }
    
    • 1

    请看以上代码,使用 record 关键字定义构造函授后,编译器将自动定义:

    • 私有、不可变的属性 age、name、team , 效果如下

      private final String name;
      private final int age;
      private final String team;
      Footballer(String name, int age, String team) { }
      
      • 1
      • 2
      • 3
      • 4
    • 所有字段均有 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());
    
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 定义一个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();
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 编写扩展类 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);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
    • 编写扩展类 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;
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
    • 测试代码

      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));
              
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23
      • 24

    Switch Expressions

    Switch 表达式比之前更加简洁。为了进行对比,我们先使用传统的方式实现一个switch表达式,然后用新特性实现相同的逻辑。

    package com.andy.spring.boot.docker.jdk17.feature;
    
    public enum Position {
        GOALKEEPER,
        DEFENCE,
        MIDFIELDER,
        STRIKER,
        BENCH
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    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);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
  • 相关阅读:
    开源组件搭建完整的Kubernetes-Devops平台方案
    【无标题】
    SciencePub学术刊源 | 10月SCI/SSCI/EI/CNKI刊源表已更新!最快1-2个月录用!
    深度学习-第T5周——运动鞋品牌识别
    IMX6ULL + SPI LCD(驱动IC ILI9341)显示简单的QT界面
    5、K8s控制器- Deployment
    蓝桥杯 vector
    Linux基础命令
    vue-element学习(四)
    工具 | macOS 最简方式安装 adb 工具 | Mac
  • 原文地址:https://blog.csdn.net/u013433591/article/details/128090732