likes
comments
collection
share

简单整理一下JDK9-JDK17在开发中可能常用到的一些新功能

作者站长头像
站长
· 阅读数 10

简单梳理一下从 JDK9JDK17 这些版本在 CRUD 中可能常用到的一些新功能;

毕竟现在 Sping6、SpringBoot3 都已经要求是 JDK17

虽然现在用的还是 JDK8... 但是新版本增加了哪些新功能还是要了解一下的

平常都是大致浏览一下看看哪个版本有啥新功能,现在印象中只记得新增了记录类、密封类,还有 var ,但其实还有很多其它有用的功能,比如 JDK14 中的空指针异常,详情更加的清晰明了

如果你用的是 JDK17 ,那么下面列举的新功能都能使用

建议使用 JDK17,因为 JDK17 是一个长期支持的版本

本文只是简单梳理记录并简单演示一下怎么使用

JDK9

1. 模块化

2. 使用 of 创建不可变集合

List<Integer> list = List.of(1, 2, 3, 4, 5);
Map<String, Integer> map = Map.of("key1", 1, "key2", 2, "key3", 3);

3. stream api 新增了4个流操作方法

  1. dropWhile

    从开头开始删除满足条件的元素,直到遇到第一个不满足条件的,然后返回剩余的元素

    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    List<Integer> result = numbers.stream()
                            .dropWhile(n -> n < 5)
                            .collect(Collectors.toList());
    
    System.out.println(result); // 输出:[5, 6, 7, 8, 9, 10]
    
    
  2. takeWhile

    从开头开始获取满足条件的元素,直到遇到第一个不满足条件的,然后返回前面满足条件的元素

    List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    List<Integer> result = numbers.stream()
                            .takeWhile(n -> n < 5)
                            .collect(Collectors.toList());
    
    System.out.println(result); // 输出:[1, 2, 3, 4]
    
  3. ofNullable,可接受 null

    String name = "Hello World";
    Stream<String> stream = Stream.ofNullable(name);
    // 输出:Hello World
    stream.forEach(System.out::println);
    
    name = null;
    stream = Stream.ofNullable(name);
    // 无输出
    stream.forEach(System.out::println);
    
    
  4. iterate

    传入一个种子值和一个生成函数,根据生成函数生成一个无限流。

    文字有点太抽象,直接看代码吧

    public static void main(String[] args) {  
        // 初始值从1开始,每次将前一个数乘以2  
        Stream.iterate(1, n -> n * 2)  
            .limit(10)  
            .forEach(System.out::println);  
          
        // 也可以添加判断条件,当n大于10时停止  
        Stream.iterate(1, n -> n <= 10, n -> n + 1)  
            .forEach(System.out::println);  
          
        // 生成自定义对象  
        Stream.iterate(new Person("John Wick", 30),  
                       person -> new Person(person.getName(), person.getAge() + 1))  
            .limit(10)  
            .forEach(System.out::println);  
          
        // 生成斐波那契数列  
        Stream.iterate(new int[]{0, 1}, arr -> new int[]{arr[1], arr[0] + arr[1]})  
            .mapToInt(arr -> arr[0])  
            .limit(10)  
            .forEach(System.out::println);  
          
        // 生成无限序列的日期  
        Stream.iterate(LocalDate.now(), date -> date.plusDays(1))  
            .limit(10)  
            .forEach(System.out::println);  
    }
    

    自己可以复制代码运行看看......

4. 私有接口方法(Private Interface Methods)

私有方法作用范围限制在类或接口内部,外部类无法访问和重写

有什么用呢?比如可以将一些通用功能封装到私有方法中,每个实现类不用重复的去重写该方法。

下面是一个简单示例

    public interface Calculator {  
        int add(int a, int b);  
        int subtract(int a, int b);  
        int multiply(int a, int b);  
        int divide(int a, int b);  

        /**  
         * 默认方法  
         */  
        default int calculate(int a, int b, String operation) {  
            // 这里用的是switch新语法,不过不是在JDK9  
            return switch (operation) {  
                case "add" -> validateAndCompute(a, b, this::add);  
                case "subtract" -> validateAndCompute(a, b, this::subtract);  
                case "multiply" -> validateAndCompute(a, b, this::multiply);  
                case "divide" -> validateAndCompute(a, b, this::divide);  
                default -> throw new IllegalArgumentException("Invalid operation: " + operation);  
            };  
        }

        /**  
         * 私有方法  
         */  
        private int validateAndCompute(int a, int b, BinaryOperator<Integer> operator) {  
            if (a < 0 || b < 0) {  
                throw new IllegalArgumentException("Numbers must be non-negative.");  
            }  
            return operator.apply(a, b);  
        } 
    }
    
    
    public static void main(String[] args) {  
        CalculatorImpl impl = new CalculatorImpl();  
        int a = -1;  
        int b = 2;  
        int add = impl.calculate(a, b, "add");  
        System.out.println(add);  
    }
   

JDK10

1. 局部变量类型推断(语法糖)

 public class VarExample {
 
     public static void main(String[] args) {
         // 推断message为String类型
         var message = "Hello, world!";
         // 推断number为int类型
         var number = 10;
 
         System.out.println(message);
         System.out.println(number);
 
         var list = new ArrayList<>();
         list.add("Java");
         list.add("Python");
         list.add("C++");
         list.add(1);
 
         for (var item : list) {
             System.out.println(item + ",类型:" + item.getClass());
         }
         // 输出结果
         Java,类型:class java.lang.String
         Python,类型:class java.lang.String
         C++,类型:class java.lang.String
         1,类型:class java.lang.Integer
     }
 }

2. streamcollectors 可指定为不可变的集合

// 以前的语法
list.stream().collect(Collectors.toList())

// 新增加的
list.stream().collect(Collectors.toUnmodifiableList());
list.stream().collect(Collectors.toUnmodifiableSet());
list.stream().collect(Collectors.toUnmodifiableMap());

JDK11

1. 命令行运行java程序

在命令行下,如果要运行一个 java 类,需要先使用 javac 命令编译类文件,再使用 java 命令运行编译后的文件,这个操作在刚学的时候肯定都做过;

现在在 JDK11 中只需要 java 一个命令就可以了。

[root@localhost ~]# java Hello.java

2. 最常用的 string

  1. 空判断

     String str = "";
     boolean blank = str.isBlank();
    
  2. 复制

     String str = "Hello World;";
     // 重复次数2
     String repeat = str.repeat(2);
     // 输出 Hello World;Hello World;
     System.out.println(repeat);
    
  3. 将字符串拆分成行

     String text = "Hello\nWorld";
     // 使用 lines() 方法将字符串拆分成行,并返回一个流
     Stream<String> lines = text.lines();
     // 此时 lineList 中是两个对象:Hello、World
     List<String> lineList = lines.collect(Collectors.toList());
    
  4. 去除字符串前后空格

     String str = "   Hello World   ;  ";
     // 输出 Hello World   ;
     System.out.println(str.strip());
    

3. 读写文件 File API

 public static void main(String[] args) {
 
     Path filePath = Path.of("C:\Users\Desktop\test.txt");
 
     String content = "Hello World!";
     try {
         // 写文件 writeString
         Files.writeString(filePath, content);
         System.out.println("写好了!");
     } catch (IOException e) {
         e.printStackTrace();
     }
 
     try {
         // 读文件 readString
         String text = Files.readString(filePath);
         System.out.println(text);
     } catch (IOException e) {
         e.printStackTrace();
     }
     
     // bytes 写
     String content = "Hello, World!";
     try {
         Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
         System.out.println("写好了!");
     } catch (IOException e) {
         e.printStackTrace();
     }
 
     // bytes 读
     try {
         byte[] fileBytes = Files.readAllBytes(filePath);
         String fileContent = new String(fileBytes, StandardCharsets.UTF_8);
         System.out.println(fileContent);
     } catch (IOException e) {
         e.printStackTrace();
     }
 
 }

4. JDK10 中的 var 局部变量类型推断,在 lambda 表达式中也能用了

BinaryOperator<Integer> sum = (var a, var b) -> a + b;  
int result = sum.apply(10, 20);  
// 输出 30  
System.out.println(result);

JDK12

1. switch 表达式,预览版本

优化 switch 表达式语法

2. 文件内容对比 Files.mismatch

返回它们之间第一个不匹配的字符的位置,如果两个文件完全相同,该方法将返回 -1

String file1 = "file1.txt";
String file2 = "file2.txt";
long mismatch = Files.mismatch(Path.of(file1), Path.of(file2));
System.out.println(mismatch);

JDK13

1. switch新增 yield(预览版本)

2. 文本块,预览版本

JDK14

1. 优化空指针异常打印信息

public static void main(String[] args) {
    String a = "hello";
    String b = null;
    int len = a.length() + b.length();
    System.out.println(len);
}

现在空指针异常,在异常信息中会直接告诉你是哪个对象导致的,以前还要自己去排查,这个功能还是非常有用的

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "b" is null

2. switch表达式 ,正式版本(语法糖)

int day = 2;
String dayName = switch (day) {
    case 1 -> "星期一";
    case 2 -> "星期二";
    case 3 -> "星期三";
    default -> {
        // yield 关键字
        yield "无效数字";
    }
};
// 输出 星期三
System.out.println(dayName);

int day = 6;
String dayType;
switch (day) {
    case 1, 2, 3, 4, 5 -> dayType = "上班";
    case 6, 7 -> dayType = "不上班";
    default -> dayType = "无效数字";
}
// 输出 不上班
System.out.println(dayType);

JDK15

1. ZGC

没用过应该也都听过了吧,JDK15 中正式发布

java -XX:+UseZGC ApplicationName

2. 文本块,正式版本

终于不用各种转义拼接了

public static void main(String[] args) {
    String textBlock = """
            Hello,
            This is a text block.
            It allows you to write
            multiple lines of text
            without the need for
            escape characters or
            concatenation operators.
            """;

    System.out.println(textBlock);
}

JDK16

1. instanceof,正式版本

Object object = "Hello";
if (object instanceof String s) {
    System.out.println(s);
}

类型匹配对了后,直接就转换为指定类型了,省了一步

2. 记录类 record ,正式版本

3. 密封类 sealed,预览版本

详细使用看下面的 JDK17

JDK17

1. switch 类型匹配,预览版本

Object value = 42;
// 使用 switch 类型匹配进行处理
switch (value) {
    case Integer i -> System.out.println("Integer value: " + i);
    case String s -> System.out.println("String value: " + s);
    case Double d -> System.out.println("Double value: " + d);
    case Long l && l > 0 -> System.out.println("Positive Long value: " + l);
    default -> System.out.println("Unknown value");
}

2. 密封类(sealed),正式版本

密封类什么意思呢,就是你想让这个类只能指定的类能继承/实现,其它的类不能继承/实现,对比 final 使用起来更加灵活

public sealed interface Expr permits Add, Multiply {  
    // 使用 permits 指定了只有 Add、Multiply 两个类可以实现该接口
}

public final class Add implements Expr {  
  
}

public final class Multiply implements Expr {  
  
}
public class Test2 {  
  
    public static void main(String[] args) {  
      
        Expr expr1 = new Add();  
        Expr expr2 = new Multiply();  
      
        process(expr1);  
        process(expr2);  
      
    }  
      
    private static void process(Expr expr) {  
        switch (expr) {  
            case Add add -> System.out.println("add");  
            case Multiply multiply -> System.out.println("multiply");
            // 这里不需要 default
        }  
    }  
  
}

因为在 Expr 接口类中指定了只有 AddMultiply 这两个类可以实现 Expr接口,所以在 switch 语句最后那里可以不写 default

下面演示一下 recordsealedswitch 的类型匹配,三者的配合使用

  1. 定义一个接口

    public interface Future<V> {
        AsyncReturn<V> get();
    }
    
  2. 使用sealed定义一个密封类接口,里面定义 record 类型的实现类

    public sealed interface AsyncReturn<V> {
    
        record Success<V>(V result) implements AsyncReturn<V> {
        }
    
        record Failure<V>(Throwable cause) implements AsyncReturn<V> {
        }
    
        record Timeout<V>() implements AsyncReturn<V> {
        }
    
        record Interrupted<V>() implements AsyncReturn<V> {
        }
    
    }
    
  3. 使用

    public static void main(String[] args) {
        // 创建一个线程池
        ExecutorService executor = Executors.newSingleThreadExecutor();
        Future<String> future = () -> {
            // 在这里执行异步操作,并返回相应的AsyncReturn实例
            try {
                // 模拟异步操作,这里使用线程池提交一个任务
                java.util.concurrent.Future<String> asyncResult = executor.submit(() -> {
                    // 模拟耗时操作
                    Thread.sleep(2000);
                    return "Hello World!";
                });
                // 获取异步操作结果,阻塞等待结果
                String result = asyncResult.get();
                // 返回异步操作成功的结果
                return new AsyncReturn.Success<>(result);
            } catch (Exception e) {
                // 返回异步操作失败的原因
                return new AsyncReturn.Failure<>(e);
            }
        };
    
        // 调用Future的get()方法获取异步操作的结果
        AsyncReturn<String> asyncResult = future.get();
    
        // 使用 switch 类型匹配进行处理
        switch (asyncResult) {
            case AsyncReturn.Success<String> successResult -> {
                String result = successResult.result();
                System.out.println("异步操作成功: " + result);
            }
            case AsyncReturn.Failure<String> failureResult -> {
                Throwable cause = failureResult.cause();
                System.out.println("异步操作失败: " + cause.getMessage());
            }
            case AsyncReturn.Timeout<String> timeoutResult -> System.out.println("超时");
            case AsyncReturn.Interrupted<String> interruptedResult -> System.out.println("中断");
        }
    
        // 关闭线程池
        executor.shutdown();
    
    }
    

大致就这些吧。