Modern Java cover image

目录

前言

像 Python、Golang、Rust、Zig、Kotlin 等一些编程语言拥有现代语法(尤其新兴编程语言), 它们去除繁琐和冗长,化繁为简,使用干净简洁直观优雅的语法来表达业务逻辑,灵活自如,拥有很强的表达表现能力。 它们符合直觉,符合人体工学,更加的语义化,朝着偏自然语言的方向演进,对人友好,易于阅读。 它们解决了一些传统语法受人诟病的地方。正因为它们拥有现代化的语法特性而被称为现代化的编程语言。

Java 被一些人认为是老语言(甚至极端的认为是过时了要被淘汰的语言),它的语法冗长繁琐而被人诟病, 让人喜欢不起来,一些人直接看不上,一些人转向了其它语言(如 Spring 之父 Rod Johnson 就对 Kotlin 大加赞赏, 相对于 Java 来说他更喜欢 Kotlin, 参见 Spring 之父:我不是 Java 的”黑粉”,但我也不想再碰它!这门语言拯救了我……), 比如 Golang、Kotlin 等。它的语法常常被用来和 Python 和 Golang 比,认为后者通过简单几句代码就 能表达的东西,而 Java 要写一大堆,非常的冗长和繁琐,不性感。

Java 在大家殷切的期盼下也逐渐加入了一些现代语法特性,旨在提高开发效率、代码可读性和程序性能, 虽然动作很慢,且得等呢。兼容性是一门工业级语言重要的要求, 为了兼容性,Java 不得不背上厚重的历史包袱,做不到像 Python3 那样壮士断腕。这时候,臃肿的 Java 就 混杂着过时传统语法和现代语法,如果没有最佳实践,就容易滥用,体会不到现代 Java,没有获得现代 Java 的精华。 在兼容性这方面,Golang 幸运得多,它工具链中的工具 gofix(go fix ./...)能安全稳定高效的批量更新代码库, 使得大多数的修改都能被快速应用更新,做到了与时俱进、常用常新,不用背上历史包袱, 参见 Google 的 Go:服务于软件工程的语言设计

备注

  • 我使用的是 SDKMan 来管理 JDK 等 SDK 的安装升级卸载和多版本管理。
  • 文中所有示例代码均使用较新版本的 Java 语法编写(如 var 等),不限于当节介绍的版本特性。

正文

Java 自从 Java 8 以来引入了大量现代语言特性,旨在提高开发效率、代码可读性和程序性能。 “现代”是一个相对概念,这里主要指 Java 8 及以后引入的,对编程范式有较大影响的特性。


1. Java 8

1.1 Lambda 表达式

描述: 允许将函数作为方法参数传递,或者将代码视为数据。简化了匿名内部类的写法,尤其在集合操作和事件处理中非常有用。

Demo:

import java.util.Arrays;
import java.util.List;

public class LambdaDemo {
    public static void main(String[] args) {
        var names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // 旧的匿名内部类方式
        // names.sort(new Comparator<String>() {
        //     @Override
        //     public int compare(String a, String b) {
        //         return a.compareTo(b);
        //     }
        // });

        // Lambda 表达式方式
        names.sort((a, b) -> a.compareTo(b));
        // 或者更简洁的方法引用
        // names.sort(String::compareTo);

        names.forEach(name -> System.out.println(name));
        // 或者方法引用
        // names.forEach(System.out::println);
    }
}

1.2 Stream API

描述: 提供了一种声明式、函数式的方式来处理集合数据。支持串行和并行操作,可以进行过滤、映射、归约等复杂操作。

Demo:

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamApiDemo {
    public static void main(String[] args) {
        var numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 筛选出偶数,将其平方,然后收集到一个新的 List 中
        var evenSquares = numbers.stream()     // 创建流
                .filter(n -> n % 2 == 0)       // 筛选偶数
                .map(n -> n * n)               // 计算平方
                .collect(Collectors.toList());  // 收集结果

        System.out.println("Original numbers: " + numbers);
        System.out.println("Squares of even numbers: " + evenSquares); // [4, 16, 36, 64, 100]

        // 计算所有数字的总和
        var sum = numbers.stream()
                .reduce(0, Integer::sum);
        // 或者更简洁
        // var sum = numbers.stream().mapToInt(Integer::intValue).sum();
        System.out.println("Sum of numbers: " + sum); // 55
    }
}

1.3 Optional 类

描述: 一个容器对象,可能包含也可能不包含非 null 值。用于优雅地处理可能为 null 的情况,避免 NullPointerException

Demo:

import java.util.Optional;

public class OptionalDemo {
    public static Optional<String> findUserById(String id) {
        if ("123".equals(id)) {
            return Optional.of("Alice");
        }
        return Optional.empty(); // 或者 Optional.ofNullable(null);
    }

    public static void main(String[] args) {
        Optional<String> userOpt = findUserById("123");

        // 安全地获取值
        var userName = userOpt.orElse("Unknown User");
        System.out.println("User (orElse): " + userName); // Alice

        userOpt.ifPresent(name -> System.out.println("User (ifPresent): " + name)); // User (ifPresent): Alice

        Optional<String> noUserOpt = findUserById("456");
        var anotherUser = noUserOpt.orElseGet(() -> "Default User From Supplier");
        System.out.println("User (orElseGet): " + anotherUser); // Default User From Supplier

        // 可能会抛出异常
        // var mustExistUser = noUserOpt.orElseThrow(() -> new RuntimeException("User not found!"));
        // Java 10+ 也可以直接使用无参方法,默认抛出 NoSuchElementException
        // var mustExistUser = noUserOpt.orElseThrow();
    }
}

1.4 接口的默认方法和静态方法

描述:

  • 默认方法: 允许在接口中提供方法的默认实现,实现该接口的类无需强制实现这些方法,有助于接口的演进。
  • 静态方法: 允许在接口中定义静态方法,这些方法与接口本身相关联,而不是与实现类相关联。

Demo:

interface MyLogger {
    void log(String message); // 抽象方法

    // 默认方法
    default void logInfo(String message) {
        log("INFO: " + message);
    }

    // 静态方法
    static String getLoggerName() {
        return "MyCustomLogger";
    }
}

class ConsoleLogger implements MyLogger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

public class InterfaceMethodsDemo {
    public static void main(String[] args) {
        System.out.println("Logger Name: " + MyLogger.getLoggerName());

        MyLogger console = new ConsoleLogger();
        console.log("An error occurred.");
        console.logInfo("System started."); // 使用默认方法
    }
}

1.5 新的日期时间 API

描述: java.time 包提供了一套全新的、不可变的、线程安全的 API 来处理日期和时间,完全取代了老旧且问题多多的 java.util.Datejava.util.Calendar

Demo:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

public class DateTimeApiDemo {
    public static void main(String[] args) {
        // 创建日期
        var today = LocalDate.now();
        var birthday = LocalDate.of(2025, Month.DECEMBER, 25);
        System.out.println("Today is: " + today);
        System.out.println("Christmas 2025 is on: " + birthday);

        // 日期计算
        var nextWeek = today.plus(1, ChronoUnit.WEEKS);
        System.out.println("Date next week: " + nextWeek);

        // 格式化
        var now = LocalDateTime.now();
        var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        var formattedNow = now.format(formatter);
        System.out.println("Formatted current time: " + formattedNow);
    }
}

2. Java 9

2.1 接口的私有方法

描述: 允许在接口中定义私有方法。这使得多个默认方法可以共享一段代码,而无需将该共享代码暴露给实现类。

Demo:

interface MyLogger {
    void log(String message); // 抽象方法

    // 默认方法
    default void logInfo(String message) {
        log("INFO: " + wrap(message));
    }

    // 静态方法
    static String getLoggerName() {
        return "MyCustomLogger";
    }

    // 私有方法,仅供接口内的默认方法使用
    private String wrap(String message) {
        return "[ " + message + " ]";
    }
}

class ConsoleLogger implements MyLogger {
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}

class FileLogger implements MyLogger {
    @Override
    public void log(String message) {
        // 假设写入文件
        System.out.println("FILE_LOG: " + message);
    }

    // 可以选择覆盖默认方法
    @Override
    public void logInfo(String message) {
        log("FILE_INFO: " + message);
    }
}

public class InterfaceMethodsDemo {
    public static void main(String[] args) {
        System.out.println("Logger Name: " + MyLogger.getLoggerName());

        MyLogger console = new ConsoleLogger();
        console.log("An error occurred.");
        console.logInfo("System started."); // 使用默认方法

        MyLogger file = new FileLogger();
        file.log("Data saved.");
        file.logInfo("User logged in."); // 使用覆盖后的默认方法
    }
}

2.2 模块系统

描述: 引入了模块化编程的概念 (module-info.java),提供了更强的封装性和更可靠的依赖管理,替代了脆弱的 classpath 机制。

Demo: 这是一个结构性特性,无法在单个文件中演示。以下是文件结构和内容的示例:

order-system/
├── pom.xml                   ( POM 管理依赖和插件)
├── order-api/
   ├── pom.xml
   └── src/main/java/
       ├── com/mycompany/order/api/     ()
       └── module-info.java           (定义模块 com.mycompany.order.api)
├── order-core/
   ├── pom.xml
   └── src/main/java/
       ├── com/mycompany/order/core/  ()
       └── module-info.java           (定义模块 com.mycompany.order.core)
└── order-persistence/
    ├── pom.xml
    └── src/main/java/
        ├── com/mycompany/order/persistence/ ()
        └── module-info.java                 (定义模块 com.mycompany.order.persistence)

order-api/module-info.java:

// 这个模块定义了所有的数据传输对象(DTO)和服务接口
module com.mycompany.order.api {
    // 它只导出 API 包,没有内部实现
    exports com.mycompany.order.api;
}

order-core/module-info.java:

// 这是核心业务逻辑的实现
module com.mycompany.order.core {
    // 需要 API 模块来获取接口和 DTO 定义
    requires com.mycompany.order.api;
    // 可能还需要持久化模块来保存数据
    requires com.mycompany.order.persistence;

    // 它不导出任何包,因为所有实现都是内部的。
    // 或者,如果它提供了一个服务工厂,它可能会导出那个包。
}

order-persistence/module-info.java:

// 负责数据库交互
module com.mycompany.order.persistence {
    // 需要 API 模块,因为持久化层操作的是 API 定义的业务对象
    requires com.mycompany.order.api;
    // 需要 JDBC 模块
    requires java.sql;

    // 导出持久化层的接口,供核心层使用
    exports com.mycompany.order.persistence;
}

JPMS 最佳实践和官方推荐是:每个项目/构建单元创建一个模块。

具体来说,这意味着:

  • 一个 Maven Module (一个 pom.xml 文件) 对应一个 Java 模块 (一个 module-info.java 文件)。
  • 一个 Gradle Module 对应一个 Java 模块。
  • 一个独立的 JAR 包,就应该是一个 Java 模块。

绝对不是为每个 Java 包(Package)创建一个模块。这样做会造成模块数量爆炸,依赖关系变得极其复杂,完全违背了模块化旨在简化依赖、增强封装的初衷。

2.3 集合工厂方法

描述: 提供了如 List.of(), Set.of(), Map.of() 等静态工厂方法,可以方便地创建少量元素的、不可变的集合。

Demo:

import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollectionFactoryDemo {
    public static void main(String[] args) {
        // 创建不可变列表
        var fruits = List.of("Apple", "Banana", "Cherry");
        System.out.println(fruits);

        // 创建不可变集合
        var numbers = Set.of(1, 2, 3);
        System.out.println(numbers);

        // 创建不可变映射
        var scores = Map.of("Alice", 95, "Bob", 88);
        System.out.println(scores);

        try {
            fruits.add("Orange"); // 会抛出 UnsupportedOperationException
        } catch (UnsupportedOperationException e) {
            System.err.println("Cannot modify the list: " + e.getMessage());
        }
    }
}

2.4 JShell:交互式 Java

描述: JShell 是一个交互式的 REPL 工具,允许开发者在不创建完整程序的情况下快速试验 Java 代码片段。这是一个命令行工具,而非语言特性。

Demo:

这是一个 JShell 会话的模拟:

$ jshell                         
|  Welcome to JShell -- Version 24.0.1
|  For an introduction type: /help intro

jshell> String message = "Hello, JShell!"
message ==> "Hello, JShell!"

jshell> System.out.println(message)
Hello, JShell!

jshell> int add(int a, int b) {
   ...>     return a + b;
   ...> }
|  created method add(int,int)

jshell> add(10, 20)
$4 ==> 30

jshell> /exit
|  Goodbye

3. Java 10

3.1 局部变量类型推断 var

描述: 允许在声明局部变量时使用 var 关键字,编译器会根据初始化表达式自动推断变量的类型。使代码更简洁。

Demo:

import java.util.ArrayList;
import java.util.HashMap;

public class VarDemo {
    public static void main(String[] args) {
        var message = "Hello, Java 10!"; // 推断为 String
        var count = 10;                  // 推断为 int
        var list = new ArrayList<String>(); // 推断为 ArrayList<String>
        var map = new HashMap<Integer, String>(); // 推断为 HashMap<Integer, String>

        list.add("Apple");
        list.add("Banana");

        System.out.println(message);
        System.out.println("Count: " + count);
        System.out.println("List: " + list);

        for (var item : list) { // 循环中也可以使用 var
            System.out.println(item.toUpperCase());
        }
        
        // var number; // 错误:var 必须在声明时初始化
        // var n = null; // 错误: 不能用 null 初始化 var
    }
}

4. Java 11

4.1 Lambda 参数中使用 var

描述: 允许在 Lambda 表达式的参数列表中使用 var,主要好处是可以向这些参数添加注解。

Demo:

import java.util.function.BiFunction;
import javax.annotation.Nonnull; // 假设有一个非空注解(此注解来自外部依赖,非 JDK 标准库内置,此处仅为举例说明)

public class LambdaVarDemo {
    public static void main(String[] args) {
        // 使用 var 可以在参数上添加注解
        BiFunction<String, String, String> concat = (@Nonnull var s1, @Nonnull var s2) -> s1 + s2;
        System.out.println(concat.apply("Hello, ", "Java 11!"));
    }
}

4.2 标准化的 HTTP 客户端

描述: java.net.http 包提供了一个现代、流畅、功能丰富的 API 来处理 HTTP 请求,支持 HTTP/1.1, HTTP/2, WebSocket,并提供同步和异步两种模式。

Demo:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.util.concurrent.CompletableFuture;

public class HttpClientDemo {
    public static void main(String[] args) throws Exception {
        var client = HttpClient.newHttpClient();

        // 同步请求
        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://jsonplaceholder.typicode.com/todos/1"))
                .build();
        var response = client.send(request, HttpResponse.BodyHandlers.ofString());
        System.out.println("Sync Response Body: " + response.body());

        // 异步请求
        CompletableFuture<HttpResponse<String>> asyncResponse = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());
        asyncResponse.thenApply(HttpResponse::body)
                     .thenAccept(body -> System.out.println("Async Response Body: " + body))
                     .join(); // 等待异步操作完成
    }
}

4.3 字符串增强方法

描述: 为 String 类添加了一系列实用方法,如 isBlank(), lines(), strip(), repeat() 等。

Demo:

public class StringEnhancementsDemo {
    public static void main(String[] args) {
        var multiline = "JEP 323\nJEP 321\n ";
        multiline.lines().forEach(line -> System.out.println("Line: '" + line + "'"));

        var spaced = "  Hello  ";
        System.out.println("Original: '" + spaced + "'");
        System.out.println("isBlank: " + " \t \n".isBlank()); // true
        System.out.println("strip: '" + spaced.strip() + "'"); // "Hello"
        System.out.println("repeat: " + "Abc".repeat(3)); // "AbcAbcAbc"
    }
}

5. Java 14

5.1 Switch 表达式

描述: 扩展了 switch 语句,使其可以作为表达式返回值。支持 -> 箭头语法,减少了 break 的使用,并能保证穷尽性(配合 default 或覆盖所有情况)。

Demo:

public class SwitchExpressionDemo {
    public enum Day { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY }

    public static String getDayType(Day day) {
        // 旧的 switch 语句
        // String type;
        // switch (day) {
        //     case MONDAY:
        //     case TUESDAY:
        //     case WEDNESDAY:
        //     case THURSDAY:
        //     case FRIDAY:
        //         type = "Weekday";
        //         break;
        //     case SATURDAY:
        //     case SUNDAY:
        //         type = "Weekend";
        //         break;
        //     default:
        //         throw new IllegalArgumentException("Invalid day: " + day);
        // }
        // return type;

        // 新的 switch 表达式
        var type = switch (day) {
            case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
            case SATURDAY, SUNDAY -> "Weekend";
            // default -> throw new IllegalArgumentException("Invalid day: " + day); // 如果Day枚举不完整,需要default
        }; // 注意这里有分号,因为是表达式赋值
        return type;
    }
    
    public static int getValue(String mode) {
        return switch (mode) {
            case "a", "b" -> 1;
            case "c" -> {
                System.out.println("Processing c...");
                yield 2; // yield 用于在多行代码块中返回值
            }
            default -> 0;
        };
    }

    public static void main(String[] args) {
        System.out.println("MONDAY is a " + getDayType(Day.MONDAY));   // Weekday
        System.out.println("SATURDAY is a " + getDayType(Day.SATURDAY)); // Weekend
        System.out.println("Value for 'c': " + getValue("c")); // Processing c... \n Value for 'c': 2
        System.out.println("Value for 'x': " + getValue("x")); // Value for 'x': 0
    }
}

6. Java 15

6.1 文本块

描述: 简化了多行字符串字面量的创建,无需大量的转义字符和拼接操作。

Demo:

public class TextBlockDemo {
    public static void main(String[] args) {
        // 旧的方式
        var htmlOld = "<html>\n" +
                      "    <body>\n" +
                      "        <p>Hello, World</p>\n" +
                      "    </body>\n" +
                      "</html>";
        System.out.println("--- Old HTML ---");
        System.out.println(htmlOld);

        // 使用文本块
        var htmlNew = """
                      <html>
                          <body>
                              <p>Hello, World</p>
                          </body>
                      </html>
                      """; // 内容的缩进相对于结束的三个引号
        System.out.println("--- New HTML (Text Block) ---");
        System.out.println(htmlNew);

        var query = """
                    SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
                    WHERE "CITY" = 'INDIANAPOLIS'
                    ORDER BY "EMP_ID", "LAST_NAME";
                    """;
        System.out.println("--- SQL Query ---");
        System.out.println(query);
    }
}

6.2 String.formatted()

描述: String.format() 的语法糖。

Demo:

public class StringFormattedDemo {
    public static void main(String[] args) { 
        var name = "Alice";
        var age = 30;
        var info = "User: %s, Age: %d".formatted(name, age);
        System.out.println(info);
    }
}

7. Java 16

7.1 Stream.toList()

描述: 为 Stream API 增加了一个便捷的终端操作 Stream.toList() 方法,用于将流中的元素收集到一个不可修改的列表中。比 collect(Collectors.toList()) 更简洁。

Demo:

import java.util.Arrays;
import java.util.List;

public class StreamToListDemo {
    public static void main(String[] args) {
        var numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // 筛选出偶数,将其平方,然后收集到一个新的 List 中
        var evenSquares = numbers.stream()     // 创建流
                .filter(n -> n % 2 == 0)       // 筛选偶数
                .map(n -> n * n)               // 计算平方
                .toList(); // 收集结果(Java 16+)

        System.out.println("Original numbers: " + numbers);
        System.out.println("Squares of even numbers: " + evenSquares); // [4, 16, 36, 64, 100]
    }
}

7.2 Record 类

描述: 一种特殊的类,用于创建不可变的数据载体对象。编译器会自动生成构造函数、equals()hashCode()toString() 以及 getter 方法 (名为组件名,如 x() 而非 getX())。

Demo:

// 定义一个 Record
record Point(int x, int y) {} // 自动生成构造器、getter、equals、hashCode、toString

record User(String username, String email) {
    // 可以添加静态方法
    public static User createGuest() {
        return new User("guest", "guest@example.com");
    }

    // 可以添加实例方法
    public String greeting() {
        return "Hello, " + username;
    }

    // 可以添加紧凑构造函数进行参数校验或规范化
    public User {
        if (username == null || username.isBlank()) {
            throw new IllegalArgumentException("Username cannot be blank");
        }
        // username = username.trim(); // 规范化
    }
}

public class RecordDemo {
    public static void main(String[] args) {
        var p1 = new Point(10, 20);
        var p2 = new Point(10, 20);
        var p3 = new Point(30, 40);

        System.out.println("p1: " + p1); // Point[x=10, y=20]
        System.out.println("p1.x(): " + p1.x()); // 10 (注意 getter 方法名)
        System.out.println("p1.y(): " + p1.y()); // 20

        System.out.println("p1 equals p2: " + p1.equals(p2)); // true
        System.out.println("p1 equals p3: " + p1.equals(p3)); // false
        System.out.println("p1 hashCode: " + p1.hashCode());

        var user = new User("john.doe", "john.doe@example.com");
        System.out.println(user); // User[username=john.doe, email=john.doe@example.com]
        System.out.println(user.greeting());
        System.out.println(User.createGuest());
        
        // try {
        //     var blankUser = new User("", "test@test.com");
        // } catch (IllegalArgumentException e) {
        //     System.err.println(e.getMessage());
        // }
    }
}

7.3 instanceof 的模式匹配

描述: 简化了 instanceof 操作和类型转换。如果在 instanceof 判断为 true 后,可以直接在同一个表达式中声明一个绑定变量,该变量是已转换类型,无需显式转换。

Demo:

public class PatternMatchingInstanceofDemo {
    public static void process(Object obj) {
        // 旧的方式
        // if (obj instanceof String) {
        //     String s = (String) obj;
        //     System.out.println("String length: " + s.length());
        // } else if (obj instanceof Integer) {
        //     Integer i = (Integer) obj;
        //     System.out.println("Integer value * 2: " + (i * 2));
        // }

        // 使用模式匹配
        if (obj instanceof String s) { // s 在此作用域内已是 String 类型
            System.out.println("String length: " + s.length());
        } else if (obj instanceof Integer i && i > 10) { // 可以在模式后添加条件
             System.out.println("Integer value > 10 and doubled: " + (i * 2));
        } else if (obj instanceof Double d) {
            System.out.println("Double value: " + d);
        } else {
            System.out.println("Unknown type");
        }
    }

    public static void main(String[] args) {
        process("Hello Java"); // String length: 10
        process(20);           // Integer value > 10 and doubled: 40
        process(5);            // Unknown type (falls through Integer condition)
        process(3.14);         // Double value: 3.14
        process(new Object()); // Unknown type
    }
}

8. Java 17

8.1 密封类和接口

描述: 允许类或接口的作者控制哪些其他类或接口可以扩展或实现它们。提供了更细粒度的继承控制。

Demo:

// 定义一个密封接口 Shape,只允许 Circle 和 Rectangle 实现它
sealed interface Shape permits Circle, Rectangle { // 只允许 Circle 和 Rectangle 直接实现
    double area();
}

// Circle 必须是 final, sealed, 或者 non-sealed
final class Circle implements Shape { // final: 不允许再被继承
    private final double radius;

    public Circle(double radius) { 
        this.radius = radius; 
    }

    @Override 
    public double area() { 
        return Math.PI * radius * radius; 
    }

    public double getRadius() { 
        return radius; 
    }
}

sealed class Rectangle implements Shape permits TransparentRectangle, Square { // sealed: 它的子类也需要声明
    protected final double length, width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override 
    public double area() { 
        return length * width; 
    }

    public double getLength() { 
        return length; 
    }

    public double getWidth() { 
        return width; 
    }
}

non-sealed class Square extends Rectangle { // non-sealed: 允许任何类继承它
    public Square(double side) {
        super(side, side);
    }

    public double getSide() { 
        return length;
    }
}

final class TransparentRectangle extends Rectangle { // final
    private final int transparency;

    public TransparentRectangle(double length, double width, int transparency) {
        super(length, width);
        this.transparency = transparency;
    }

    public int getTransparency() { 
        return transparency; 
    }
}

// 如果 Shape 和其子类在同一个 .java 文件中,可以省略 permits 子句,编译器会自动推断。
// 如果不在同一个文件,则必须使用 permits。

public class SealedClassesDemo {
    public static void describeShape(Shape shape) {
        System.out.print("This shape is a ");
        if (shape instanceof Circle c) {
            System.out.println("Circle with radius " + c.getRadius() + " and area " + c.area());
        } else if (shape instanceof Square s) {
            System.out.println("Square with side " + s.getSide() + " and area " + s.area());
        } else if (shape instanceof Rectangle r) { // Must come after Square due to inheritance
            System.out.println("Rectangle with length " + r.getLength() + ", width " + r.getWidth() + " and area " + r.area());
        } else {
            System.out.println("Unknown shape with area " + shape.area());
        }
        // 在 Java 21 中,这里可以用模式匹配 switch 来完美处理
    }

    public static void main(String[] args) {
        Shape circle = new Circle(5);
        Shape rectangle = new Rectangle(4, 6);
        Shape square = new Square(3);

        describeShape(circle);
        describeShape(rectangle);
        describeShape(square);
    }
}

9. Java 18

9.1 UTF-8 默认字符集

描述: Java 18 将 UTF-8 设置为所有标准 Java API 的默认字符集。这使得在不同操作系统上运行的 Java 应用程序行为更加一致,减少了因字符编码问题导致的错误。

Demo: 这是一个概念性的改变,代码演示其影响。

import java.io.FileReader;
import java.io.FileWriter;
import java.nio.charset.Charset;

public class DefaultCharsetDemo {
    public static void main(String[] args) throws java.io.IOException {
        // 在 Java 18+ 中,这默认为 UTF-8
        System.out.println("Default Charset: " + Charset.defaultCharset());

        // 以下代码在 Java 18 之前行为依赖于操作系统
        // 在 Java 18+ 中,它们将可靠地使用 UTF-8 读写文件(除非被覆盖)
        try (var writer = new FileWriter("test.txt")) {
            writer.write("你好,世界!");
        }

        try (var reader = new FileReader("test.txt")) {
            int c;
            while ((c = reader.read()) != -1) {
                System.out.print((char)c);
            }
        }
    }
}

9.2 简单 Web 服务器

描述: 提供了一个开箱即用的、最小化的静态文件 Web 服务器。可以通过命令行工具 jwebserver 启动,或通过 SimpleFileServer API 以编程方式启动,非常适合用于测试和开发。

Demo:

  • 方法一:命令行 (在终端中运行)
# 在你的项目目录下创建一个名为 index.html 的文件
# 然后运行以下命令,即可在 http://localhost:8000 访问
$ jwebserver
  • 方法二:编程方式
import com.sun.net.httpserver.SimpleFileServer;
import com.sun.net.httpserver.HttpServer;
import java.net.InetSocketAddress;
import java.nio.file.Path;

public class SimpleWebServerDemo {
    public static void main(String[] args) {
        // 创建一个在 8080 端口,服务当前目录文件的服务器
        var server = SimpleFileServer.createFileServer(
                new InetSocketAddress(8080),
                Path.of(".").toAbsolutePath(),
                SimpleFileServer.OutputLevel.VERBOSE
        );
        System.out.println("Starting server on port 8080... Press Ctrl+C to stop.");
        server.start();
    }
}

10. Java 21

10.1 switch 的模式匹配

描述: 将模式匹配扩展到 switch 语句和表达式中,允许根据对象的类型和结构进行分支。

Demo:

public class SwitchPatternMatchingDemo {

    sealed interface Shape permits Circle, Rectangle {}

    final record Circle(double radius) implements Shape {}

    sealed static class Rectangle implements Shape permits TransparentRectangle, Square {
        final double length, width;

        Rectangle(double length, double width) { 
            this.length = length; 
            this.width = width; 
        }
    }

    non-sealed static class Square extends Rectangle {
        Square(double side) { 
            super(side, side); 
        }
    }

    final static class TransparentRectangle extends Rectangle {
        TransparentRectangle(double l, double w) { 
            super(l, w); 
        }
    }


    static String processShape(Shape shape) {
        return switch (shape) {
            case null -> "Shape is null"; // 处理 null case
            case Circle c -> String.format("Circle with radius %.2f", c.radius());
            case Square s -> String.format("Square with side %.2f", s.length); // 'length' from Rectangle
            case Rectangle r when r.length == r.width -> String.format("Square-like Rectangle %.2f x %.2f", r.length, r.width); // 带守卫条件
            case Rectangle r -> String.format("Rectangle with dimensions %.2f x %.2f", r.length, r.width); // 也匹配 TransparentRectangle(子类型关系)
            // default -> "Unknown shape"; // 如果 Shape 不是 sealed,或者没有覆盖所有 permits 的情况,则需要 default
            // 对于 sealed interface,如果所有 permits 的类型都被 case 覆盖了,就不需要 default
        };
    }

    public static void main(String[] args) {
        System.out.println(processShape(new Circle(5.0)));
        System.out.println(processShape(new Square(4.0)));
        System.out.println(processShape(new Rectangle(3.0, 7.0)));
        System.out.println(processShape(new Rectangle(6.0, 6.0))); // "Square-like Rectangle"
        System.out.println(processShape(null)); // "Shape is null"
    }
}

10.2 Record 模式

描述: 允许在模式匹配中解构 Record 实例,直接访问其组件。可以与 instanceofswitch 的模式匹配结合使用。

Demo:

public class RecordPatternsDemo {

    record Point(int x, int y) {}
    record ColoredPoint(Point p, String color) {}
    record Pair<T>(T first, T second) {}

    public static void printPointInfo(Object obj) {
        // instanceof 与 Record 模式
        if (obj instanceof Point(int x, int y)) {
            System.out.println("Point coordinates: x = " + x + ", y = " + y);
        } else if (obj instanceof ColoredPoint(Point(var x, var y), var color)) {
            System.out.println("ColoredPoint: x = " + x + ", y = " + y + ", color = " + color);
        } else if (obj instanceof Pair(String s1, String s2)) { // 严格匹配 String 类型的 Pair
            System.out.println("String Pair: (" + s1 + ", " + s2 + ")");
        } else {
            System.out.println("Not a recognized record pattern.");
        }
    }

    public static String describeObjectWithSwitch(Object obj) {
        return switch (obj) {
            case Point(int x, int y) -> String.format("A point at (%d, %d)", x, y);
            // 嵌套 Record 模式
            case ColoredPoint(Point(int x, int y), String color) ->
                String.format("A %s point at (%d, %d)", color, x, y);
            // var 关键字可以在 Record 模式中使用,以捕获整个组件
            case ColoredPoint(var p, var color) -> // p 是 Point, color 是 String
                String.format("A %s point %s", color, p.toString());
            case Pair(Integer i1, Integer i2) -> String.format("Integer Pair: (%d, %d)", i1, i2);
            case null -> "It's null!";
            default -> "Something else: " + obj.toString();
        };
    }

    public static void main(String[] args) {
        var p = new Point(10, 20);
        var cp = new ColoredPoint(p, "RED");
        var sp = new Pair<>("Hello", "World");
        var ip = new Pair<>(100, 200);

        printPointInfo(p);      // Point coordinates: x = 10, y = 20
        printPointInfo(cp);     // ColoredPoint: x = 10, y = 20, color = RED
        printPointInfo(sp);     // String Pair: (Hello, World)
        printPointInfo("Test"); // Not a recognized record pattern.

        System.out.println(describeObjectWithSwitch(p));
        System.out.println(describeObjectWithSwitch(cp));
        System.out.println(describeObjectWithSwitch(ip));
        System.out.println(describeObjectWithSwitch(null));
    }
}

10.3 虚拟线程

描述: 轻量级线程,由 JVM 管理,而不是操作系统。可以创建数百万个虚拟线程而不会耗尽系统资源,极大简化了高并发应用的编写,允许使用传统的阻塞式 I/O 编程模型来获得异步编程的性能优势。

Demo:

import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class VirtualThreadsDemo {
    public static void main(String[] args) throws InterruptedException {
        var startTime = System.currentTimeMillis();

        // 方法1: 直接创建并启动
        // Thread.startVirtualThread(() -> {
        //     System.out.println("Hello from virtual thread 1: " + Thread.currentThread());
        //     try {
        //         Thread.sleep(1000); // 模拟 I/O 阻塞操作
        //     } catch (InterruptedException e) {
        //         e.printStackTrace();
        //     }
        // });

        // 方法2: 使用 Thread.Builder
        // var virtualThread2 = Thread.ofVirtual().name("MyVirtualThread-2").unstarted(() -> {
        //     System.out.println("Hello from virtual thread 2: " + Thread.currentThread());
        //     try {
        //         Thread.sleep(500);
        //     } catch (InterruptedException e) {
        //         e.printStackTrace();
        //     }
        // });
        // virtualThread2.start();

        // 方法3: 使用 ExecutorService (推荐用于管理大量任务)
        // try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
        //     IntStream.range(0, 10_000).forEach(i -> { // 创建 1 万个任务
        //         executor.submit(() -> {
        //             System.out.println("Task " + i + " running on: " + Thread.currentThread());
        //             try {
        //                 Thread.sleep(100); // 模拟耗时操作
        //             } catch (InterruptedException e) {
        //                 // Thread.currentThread().interrupt();
        //             }
        //         });
        //     });
        // } // executor.close() 会等待所有任务完成

        // 简单的批量创建示例
        final var NUM_THREADS = 100_000; // 尝试创建 10 万个虚拟线程
        var threads = new Thread[NUM_THREADS];

        for (var i = 0; i < NUM_THREADS; i++) {
            final var taskId = i;
            threads[i] = Thread.startVirtualThread(() -> {
                // System.out.println("Task " + taskId + " started on " + Thread.currentThread());
                try {
                    Thread.sleep(10); // 模拟 I/O 密集型任务
                } catch (InterruptedException e) {
                    // e.printStackTrace();
                }
                if (taskId % 10000 == 0) { // 每 1 万个打印一次,避免过多输出
                     System.out.println("Task " + taskId + " finished on " + Thread.currentThread());
                }
            });
        }

        // 等待所有虚拟线程完成 (仅为演示,实际中 ExecutorService 更好)
        for (var i = 0; i < NUM_THREADS; i++) {
            threads[i].join();
        }
        
        var endTime = System.currentTimeMillis();
        System.out.println(NUM_THREADS + " virtual threads processed in " + (endTime - startTime) + " ms.");
        // 如果用平台线程,创建 10 万个通常会导致 OutOfMemoryError: unable to create native thread
    }
}

10.4 有序集合

描述: 引入了一系列新的接口 (SequencedCollection, SequencedSet, SequencedMap),为那些元素具有确定出现顺序的集合提供统一的、易于访问的 API,例如获取第一个/最后一个元素,或者反向视图。

Demo:

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SequencedCollection;
import java.util.SequencedSet;
import java.util.LinkedHashMap;
import java.util.SequencedMap;

public class SequencedCollectionsDemo {
    public static void main(String[] args) {
        // SequencedCollection (List 是一个例子)
        SequencedCollection<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Cherry");
        System.out.println("Original List: " + list); // [Apple, Banana, Cherry]

        System.out.println("First element: " + list.getFirst()); // Apple
        System.out.println("Last element: " + list.getLast());   // Cherry

        list.addFirst("Orange"); // 在开头添加
        list.addLast("Mango");   // 在末尾添加
        System.out.println("Modified List: " + list); // [Orange, Apple, Banana, Cherry, Mango]

        System.out.println("Removed first: " + list.removeFirst()); // Orange
        System.out.println("Removed last: " + list.removeLast());   // Mango
        System.out.println("List after removals: " + list); // [Apple, Banana, Cherry]

        SequencedCollection<String> reversedList = list.reversed();
        System.out.println("Reversed List View: " + reversedList); // [Cherry, Banana, Apple]
        // reversedList.add("Grape"); // 修改反向视图会影响原始列表
        // System.out.println("Original list after modifying reversed: " + list);

        System.out.println("\n--- SequencedSet ---");
        // SequencedSet (LinkedHashSet 是一个例子)
        SequencedSet<Integer> set = new LinkedHashSet<>();
        set.add(10);
        set.add(20);
        set.add(5);
        System.out.println("Original Set: " + set); // [10, 20, 5] (按插入顺序)

        System.out.println("First element: " + set.getFirst()); // 10
        System.out.println("Last element: " + set.getLast());   // 5
        
        SequencedSet<Integer> reversedSet = set.reversed();
        System.out.println("Reversed Set View: " + reversedSet); // [5, 20, 10]

        System.out.println("\n--- SequencedMap ---");
        // SequencedMap (LinkedHashMap 是一个例子)
        SequencedMap<String, Integer> map = new LinkedHashMap<>();
        map.put("One", 1);
        map.put("Two", 2);
        map.put("Three", 3);
        System.out.println("Original Map: " + map); // {One=1, Two=2, Three=3}

        System.out.println("First entry: " + map.firstEntry()); // One=1
        System.out.println("Last entry: " + map.lastEntry());   // Three=3

        map.putFirst("Zero", 0);
        map.putLast("Four", 4);
        System.out.println("Modified Map: " + map); // {Zero=0, One=1, Two=2, Three=3, Four=4}

        SequencedMap<String, Integer> reversedMap = map.reversed();
        System.out.println("Reversed Map View: " + reversedMap); // {Four=4, Three=3, Two=2, One=1, Zero=0}
    }
}

11. Java 22

11.1 未命名模式和变量

描述: 允许使用下划线 _ 作为未命名模式或变量的占位符。当一个变量或模式组件的值在代码中不需要使用时,这可以提高代码可读性并减少冗余,明确表达开发者”故意不使用”的意图。

Demo:

import java.util.List;

public class UnnamedPatternsDemo {

    record Point(int x, int y) {}
    enum Color { RED, GREEN, BLUE }
    record ColoredPoint(Point p, Color c) {}

    public static void main(String[] args) {
        // 1. 未命名变量:在循环中忽略元素
        List<String> items = List.of("a", "b", "c");
        int count = 0;
        for (String _ : items) { // 元素本身不重要,只关心计数
            count++;
        }
        System.out.println("Counted " + count + " items.");

        // 2. 未命名模式:在 instanceof 中只关心部分组件
        Object obj = new ColoredPoint(new Point(10, 20), Color.RED);

        if (obj instanceof ColoredPoint(Point(int x, _), _)) { // 只关心 x 坐标,忽略 y 坐标和颜色
            System.out.println("Instanceof: Point x-coordinate is " + x);
        }

        // 3. switch 中的未命名模式
        switch (obj) {
            case ColoredPoint(Point(int x, int y), _) -> // 只关心坐标,忽略颜色
                System.out.println("Switch: A point at (" + x + ", " + y + ")");
            case Point(_, int y) -> // 只关心 y 坐标
                System.out.println("Switch: Point y-coordinate is " + y);
            case String _ -> // 匹配任何 String,但其值不使用
                System.out.println("Switch: It's a String, but I don't care about its value.");
            default -> System.out.println("Switch: Other object");
        }

        // 4. try-with-resources 中使用未命名变量
        try (var _ = new AutoCloseableResource("Resource1"); // _ 表示这个资源变量不直接使用
             var resource2 = new AutoCloseableResource("Resource2")) {
            System.out.println("Inside try-with-resources. " + resource2.getName() + " is used.");
            // resource1 的 close() 仍会被调用
        } catch (Exception e) {
            System.err.println("Exception: " + e.getMessage());
        }
        
        // 5. catch 块中忽略异常变量
        try {
            Integer.parseInt("not-a-number");
        } catch (NumberFormatException _) { // 不需要使用异常对象
            System.out.println("Failed to parse number.");
        }

        // 6. Lambda 参数中忽略不需要的参数
        var map = java.util.Map.of("key1", 1, "key2", 2);
        map.forEach((_, value) -> System.out.println("Value: " + value)); // 忽略 key
    }

    static class AutoCloseableResource implements AutoCloseable {
        private String name;

        public AutoCloseableResource(String name) {
            this.name = name;
            System.out.println(name + " opened.");
        }

        public String getName() { return name; }

        @Override
        public void close() throws Exception {
            System.out.println(name + " closed.");
        }
    }
}

12. Java 24

12.1 流收集器

描述:对 Stream API 的一次重大功能增强。它引入了新的中间操作 gather(), 允许开发者使用 Gatherer 来实现自定义的、有状态的流转换。这使得对流元素进行分组、累积计算等高级操作变得更加直接和强大。

Demo:

import java.util.stream.Stream;
import java.util.stream.Gatherers;

public class GatherersDemo {
    public static void main(String[] args) {
        // 使用内置的 Gatherer: windowSliding 对元素进行滑动窗口分组
        var items = Stream.of("A", "B", "C", "D", "E");
        var slidingWindows = items
                .gather(Gatherers.windowSliding(3))
                .toList();

        System.out.println("Sliding windows of size 3: " + slidingWindows);
        // 输出: Sliding windows of size 3: [[A, B, C], [B, C, D], [C, D, E]]
    }
}

13. Java 25

Ref: https://mp.weixin.qq.com/s/wo4V5fC1Pc7i6v6TUD53Qg

13.1 紧凑源文件和实例主方法

描述:在 Java 25 中正式标准化为紧凑源文件和实例主方法(此前预览阶段被称为“隐式声明的类和实例主方法”)。 这彻底改变了 Java 的入门体验。开发者现在可以编写不包含在显式 class 声明中的简单程序, 并且 main 方法不再需要是 public static 修饰的。这使得编写第一个 Java 程序变得前所未有的简单 and 直观。

Demo:

void main() {
    IO.println("Hello from Java 25!");
    greet("World");
}

void greet(String name) {
    IO.println("Greetings, " + name + ".");
}

13.2 作用域值

描述:这是一种在线程内以及线程的子线程之间高效共享不可变数据的全新机制。 它被设计为线程局部变量 (ThreadLocal) 的现代化替代方案,解决了 ThreadLocal 存在的继承性、内存泄漏风险和性能开销等问题。 作用域值在使用上更安全、更易于理解,并且性能更高,尤其是在虚拟线程环境下。

Demo:

// 定义一个作用域值,它将在特定代码块内持有用户信息
private static final ScopedValue<String> LOGGED_IN_USER = ScopedValue.newInstance();

void main() {
    // 在一个 "where" 代码块中,将 LOGGED_IN_USER 的值绑定为 "Alice"
    // 这个绑定仅在此 lambda 表达式的执行作用域内有效
    ScopedValue.where(LOGGED_IN_USER, "Alice")
            .run(() -> processRequest());

    // 在此代码块之外,LOGGED_IN_USER 是未绑定的
    IO.println("Outside the scope, user is present: " + LOGGED_IN_USER.isBound()); // false
}

// 模拟一个需要访问用户身份的业务方法
void processRequest() {
    IO.println("Processing request...");
    // 无需通过方法参数传递,直接从作用域值中获取当前用户
    if (LOGGED_IN_USER.isBound()) {
        IO.println("User '" + LOGGED_IN_USER.get() + "' is performing the action.");
    } else {
        IO.println("No user is logged in.");
    }
}

13.3 新的 IO 类

描述:在 java.lang 包中新增了 IO 类,提供更简单的控制台 I/O 操作。

Demo

void main() {
    var name = IO.readln("名字: ");
    IO.print("欢迎:");
    IO.println(name);
}

13.4 模块导入声明

描述:引入了 import module M; 语法。允许在普通的 .java 源码文件中直接导入整个模块(如 java.base)所导出的所有包, 而不需要逐个导入具体的包或类。这极大简化了引入标准库或大型库的样板代码。

Demo

// 之前需要分别导入
// import java.util.Map;
// import java.util.function.Function;
// import java.util.stream.Collectors;
// import java.io.File;

// 现在 (通过 import module)
import module java.base; // 导入了 java.base 模块下所有 public 的 API

public class ModuleImportDemo {
    public static void main(String[] args) {
        // 直接使用 Map, Function, Collectors, File 等,无需额外 import
        Map<String, String> map = Map.of("key", "value");
        System.out.println(map);
    }
}

13.5 灵活的构造函数体

描述:在此特性之前,构造函数中的 this(…) 或 super(…) 调用必须是第一条语句。 这使得在调用父类或重载构造函数之前无法执行任何逻辑(如参数验证)。此 JEP 放宽了这一限制, 允许在满足特定条件下(例如,不访问实例字段),在 this() 或 super() 调用之前执行一些前置代码。 这是一个重要的语言级改进,解决了 Java 构造函数的一个长期限制。

Demo

class Person {
    String name;

    public Person(String name) {
        IO.println("Person constructor called.");
        this.name = name;
    }
}

class Employee extends Person {
    int employeeId;

    public Employee(String name, int id) {
        // 可以在 super() 调用前执行逻辑
        if (name == null || name.isBlank()) {
            throw new IllegalArgumentException("Employee name cannot be null or blank.");
        }
        // 甚至可以调用静态方法来准备参数
        var processedName = name.trim().toUpperCase();

        super(processedName); // 不再强制为第一行
        
        IO.println("Employee constructor called.");
        this.employeeId = id;
    }
}

public class FlexibleConstructorDemo {
    void main() {
        Employee emp = new Employee("  John Doe  ", 123);
        IO.println("Created Employee: " + emp.name + " with ID: " + emp.employeeId);
        
        try {
            new Employee("", 456);
        } catch (IllegalArgumentException e) {
            System.err.println("Caught expected exception: " + e.getMessage());
        }
    }
}

13.6 密钥派生函数 API

描述:引入了一个新的、标准化的 API (javax.crypto.KDF),用于密钥派生函数。 KDF 用于从一个共享的秘密(如密码)安全地派生出一个或多个加密密钥。 这个 API 为常见的算法(如 HKDF, PBKDF2)提供了统一、易用且安全的实现,避免了开发者自己实现复杂且易出错的加密逻辑。

Demo

import javax.crypto.KDF;
import javax.crypto.SecretKey;
import javax.crypto.spec.HKDFParameterSpec;

public class KdfApiDemo {
    void main() throws Exception {
        // 1. 原始密钥材料
        var masterSecret = "my-very-secret-master-key".getBytes();
        var salt = "some-random-salt".getBytes();
        var info = "aes-128-gcm-key".getBytes();
        
        // 2. 获取 KDF 实例
        KDF kdf = KDF.getInstance("HKDF-SHA256");
        
        // 3. 使用 Builder 构建参数
        HKDFParameterSpec spec = HKDFParameterSpec.ofExtract()
            .addIKM(masterSecret)
            .addSalt(salt)
            .thenExpand(info, 16);
        
        // 4. 派生密钥
        SecretKey derivedKey = kdf.deriveKey("AES", spec);
        
        // 5. 验证结果
        IO.println("Derived Key Algorithm: " + derivedKey.getAlgorithm());
        IO.println("Derived Key Length (bits): " + derivedKey.getEncoded().length * 8);
        IO.println("Derived Key (hex): " + toHexString(derivedKey.getEncoded()));
    }
    
    private static String toHexString(byte[] bytes) {
        var sb = new StringBuilder();
        for (var b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}

14. Java 26

14.1 HTTP/3 支持

描述: 为标准的 java.net.http.HttpClient API 增加了对 HTTP/3 协议的原生支持。 HTTP/3 基于 QUIC(UDP)协议,相较于基于 TCP 的 HTTP/1.1 和 HTTP/2,具有更低的连接建立延迟和更好的弱网表现。 使用方式非常简单,只需在构建 HttpClient 或 HttpRequest 时指定 HttpClient.Version.HTTP_3 即可。 如果服务器不支持 HTTP/3,客户端会自动优雅降级到 HTTP/2 或 HTTP/1.1。

Demo:

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

public class Http3Demo {
    public static void main(String[] args) throws Exception {
        // 创建一个偏好 HTTP/3 的客户端
        var client = HttpClient.newBuilder()
                .version(HttpClient.Version.HTTP_3) // 优先使用 HTTP/3
                .build();

        var request = HttpRequest.newBuilder()
                .uri(URI.create("https://openjdk.org/"))
                .GET()
                .build();

        // 发送请求,如果服务器不支持 HTTP/3,会自动降级到 HTTP/2 或 HTTP/1.1
        var response = client.send(request, HttpResponse.BodyHandlers.ofString());

        IO.println("Status Code: " + response.statusCode());
        IO.println("HTTP Version: " + response.version()); // 查看实际使用的协议版本
        IO.println("Body (first 200 chars): " + response.body().substring(0, Math.min(200, response.body().length())));

        // 也可以在单个请求级别指定 HTTP/3
        var http3Request = HttpRequest.newBuilder()
                .uri(URI.create("https://www.google.com/"))
                .version(HttpClient.Version.HTTP_3) // 仅此请求偏好 HTTP/3
                .GET()
                .build();

        var defaultClient = HttpClient.newHttpClient(); // 默认客户端(HTTP/2)
        var response2 = defaultClient.send(http3Request, HttpResponse.BodyHandlers.ofString());
        IO.println("\nGoogle Status Code: " + response2.statusCode());
        IO.println("Google HTTP Version: " + response2.version());
    }
}

这里是自己平时的一些胡思乱想、瞎琢磨和对一些其它语言的学习了解而逐渐形成的一些自己的心得感悟, 一些自己觉得的最佳实践(姑妄言之),主要是对其它一些现代编程语言的借鉴和类似语法模仿(尤其是 Golang 和 Kotlin), 以形成 干净、简洁、简单、直观、快速、现代 的编程风格:

我的最佳实践(瞎建议)

  • 模块系统 JPMS 主要适用于基础库和框架开发,或者使用 jlink 打包自定义运行时镜像。普通的企业级业务应用开发(如 Spring Boot)由于模块化兼容性及配置复杂度,依然建议使用传统的 Classpath。

  • 尽量使用局部变量,慎重使用全局变量。

  • 尽可能使用局部变量类型推断 var。(参考 Golang、Kotlin 等现代编程语言的默认做法)

  • 各种 DTO、VO 等用于数据传输和展现的 Java Bean 应尽可能使用 record。但注意,JPA/Hibernate 的 Entity(实体类)由于需要可变性(用于脏检查)以及无参构造函数,不能使用 record

  • 尽量使用不可变数据结构,尽量创建不可变类。

  • 代码主要是用来给人读的。在写上层的业务代码时,尽可能的使命名和现实世界的事物一致,贴合领域。尽可能的使语句表达贴合自然语言。

  • 尽量使用数据替换而不是修改。(参考 Reactjs 的状态管理)

  • 使用虚拟线程。

  • 使用谷歌开源的代码格式化工具 google-java-format 来格式化代码,这个工具是对 gofmt 的借鉴。(受 Golang 工具链中的 gofmt 影响。)

  • Java 的检查异常争议不断,很多语言,尤其是新诞生的现代编程语言都不添加检查异常了,应该借鉴 C# 等面向对象语言, 只使用非检查异常 RuntimeException,不使用检查异常。(参考 C#)

  • 面向对象中的继承机制也广受争议,有很复杂麻烦的继承链问题,复杂度高,心智负担重, 过度使用继承可能导致”类爆炸”和高耦合,应该优先使用组合。(借鉴 Golang)

  • 类负责封装,接口负责多态。(借鉴 Golang)

  • 架构是对问题做正交分解,分解成尽可能小的独立的块,然后任意两块就能相互组合形成功能,这样就能形成乘法效应, 用最少的代码实现最多的功能,同时又是灵活的。

  • 先设计类,如果有需要再从各类中挑选方法放到接口里,即自底向上的自然发展,自然长起来,而不是自顶向下的顶层设计,业务是按需长起来的,而不是事先冒着 极高的心智负担去猜测去做复杂的过度的设计。涌现式设计。(受 Golang 编程哲学、Rob Pike、许式伟影响)

  • 实现一个功能前,先去网上搜搜,看一下业界的最佳实践。使用最佳实践,使用业界(全球)主流的、流行的产品、工具、方法。(受陈皓(左耳朵耗子)影响)

  • 写代码前,先用自然语言或伪代码写一遍,再正式写代码。

  • 开发项目写代码,借鉴建筑行业盖楼的方法,先用钢筋和水泥搭一个框架,再往这个框架里续砖。 搭的框架的方法里先抛出异常(如 UnsupportedOperationException),写功能代码填充框架时再删掉异常, 以便运行测试时知道哪里还没做,避免遗忘、遗漏。

  • 在写代码前,在规划设计时,写列一个要实现的功能列表清单,每一项是要实现的一个功能一个方法,并且按重要性、核心度 从上到下排列,以便写代码时抓住重点,先写先验证核心部分。清单上下分成两大部分,用横线从中分隔,前部分是 必须要实现的基础核心功能,后部分是不是那么重要、紧迫、非核心、可选的功能。实现一个功能就在前面打一个✅, 相当于是 task list。

  • TDD(测试驱动开发)。搭好项目结构化框架后,先在测试类里写好功能和非功能的测试代码,再跑到正式类 里实现业务逻辑,然后再单元测试这个方法,弄完后再以同样的方式写其它的方法,循环往复。并不是一开始就 先写好全部测试方法代码再去写业务代码。

  • 需求、开发时,搞清楚 X-Y 问题。(陈皓观点)

  • 除非重构(重构是因之前没设计好,再重新设计一下,或者是由于技术进步,把原有的功能以更好的方式重新实现一遍), 否则不要改代码,加功能通过加模块来实现。

  • 控制 + 逻辑 开发模式,控制负责框架流程,逻辑是功能实现,是细小的方法,通过函数式接口以参数的方式传入流程控制的方法中。

  • 多查一下国际命名,如数据库里面的时间字段国际上多用 createdAt、updatedAt、deletedAt/removedAt, 或者人员字段 createdBy、updatedBy…… 。最好先用 AI 查一下。

  • 当你讨厌 Java 语法,喜欢 C# 语法风格时,不妨看看 Kotlin。当你准备学写 C 时,不妨看看 Golang(干净、简单简洁优雅、直观、强大)。 当你准备学写 C++ 时,不妨看看 Rust(极致性能、极致控制、极致安全、极度复杂)。当你准备学写 JavaScript 时,不妨看看 TypeScript。 当你准备学写 CSS 时,不妨看看 SCSS。 (当然,TypeScript 是 JavaScript 的超集(加了类型),SCSS 是 CSS 的超集(加了几个工具方法))

架构师许式伟观点理念

  • 工程是一门有关于如何”把事做成”的学问。工程可以和所有学科交叉(工程+X)。
  • 模块:将复杂问题拆解成独立子问题。
  • 设计不是指”产品包装”,而是一门有关于”决策”的学问(要做什么事,以及把事做成什么样子)。
  • 初级产品经理和初级架构师都喜欢做加法。但是顶级产品经理做的是减法。而顶级架构师做的是乘法。
  • 产品设计:少做加法,做减法。
  • 少就是指数级的多。
  • 卖点越多的产品其实就是没有卖点。
  • 架构的开闭原则对产品同样适用(产品要满足的需求可以是无限的,但产品功能必须是封闭(有边界)的)。
  • 只要图灵完备(包、变/常量、条件/循环、方法),理论上做什么都可以。
  • 架构从手法上就是模块拆解。连接与组合。实体 (Entity) 的边界问题。
  • 把整个网站需求分解为 A、B、C、D、E 等完全正交无耦合、甚至和你的网站原始需求不直接挂钩的通用模块, 用极短的桥接代码1把这些通用模块组装起来完成页面1,桥接代码2完成页面2,这才是做乘法。
    • 而判断乘法做的好不好的标准非常简单:桥接代码的代码量越少,乘法的威力 越大,你的架构设计的能力也就越强。
  • 模块切分,通常和需求并不形成对应关系。如果对应,往往反而说明模块划分和团队分工是有问题的。
  • 需求分析要做什么?需求未来可能的发展方向预判(防止过度设计)。洞察需求的内在逻辑关联(模块切分的基础)。
  • 心性:韧性:是否能够长期坚持为一个目标去努力。要性:对成功的渴望。空杯:倾听、迭代认知的能力。
  • 很多人推崇设计模式。但是在我眼里,设计模式是没有任何价值,如果不能够明白软件架构的本质的话。
  • 软件架构就是准确把控需求的基础上对系统的解剖。 准确把控需求,不只是要准确理解当前的需求,也要准确理解需求的变化,预见什么会发生,而什么不会发生。
  • 架构只需要一个原则:开闭原则。软件实体(类,函数,模块)可以扩展,不可以修改;即扩展是开放的,修改是封闭的;面对需求,对程序的改动是通过增加代码实现的,而不是更改现有代码;
  • 架构的关键不是设计框架,而是需求的正交分解。
    • 大需求(应用程序)被切分为小需求(模块),小需求继续分解(类/组件/函数)。
  • 模块的使用界面体现了模块的需求。

Java foot image

推荐阅读