从 Java 8 迁移到 Java 17:新功能大汇总

Java 17 vs Java 8

自 2014 年发布 Java 8 以来,Java 已发生了重大发展。2021 年发布的 Java 17 带来了大量新功能和改进,可提高代码的可读性、可维护性和性能。本文将引导您了解 Java 8 和 Java 17 之间引入的关键功能,并举例说明如何迁移代码以利用这些新功能。

Sealed Classes

Java 17 引入了sealed 类,这一强大功能增强了 Java 的面向对象编程模型,并与开关表达式中的模式匹配无缝配合。

新语法:

sealed 关键字声明了一个密封类或接口,permit 子句指定了允许使用的子类:

public sealed interface Shape permits Circle, Rectangle, Triangle { }

子类必须使用三种修饰符之一:finalsealed, or non-sealed.

何时使用

在需要时使用sealed类:

  1. 限制类层次结构的扩展
  2. 定义一组固定的子类
  3. switch表达式中启用穷举模式匹配
  4. 更准确地模拟特定领域的概念

示例

  1. 使用穷举switch塑造层次结构:
    sealed interface Shape permits Circle, Rectangle, Triangle { }
    record Circle(double radius) implements Shape { }
    record Rectangle(double width, double height) implements Shape { }
    record Triangle(double base, double height) implements Shape { }
    
    static double calculateArea(Shape shape) {
        return switch (shape) {
            case Circle c -> Math.PI * c.radius() * c.radius();
            case Rectangle r -> r.width() * r.height();
            case Triangle t -> 0.5 * t.base() * t.height();
        }; // No default needed, switch is exhaustive
    }
    
  2. 通过模式匹配进行支付处理:
    sealed interface Payment permits CreditCard, DebitCard, Cash { }
    record CreditCard(String number, String name) implements Payment { }
    record DebitCard(String number, String bank) implements Payment { }
    record Cash(double amount) implements Payment { }
    
    static void processPayment(Payment payment) {
        switch (payment) {
            case CreditCard cc -> System.out.println("Processing credit card: " + cc.number());
            case DebitCard dc -> System.out.println("Processing debit card from: " + dc.bank());
            case Cash c -> System.out.println("Accepting cash payment of: $" + c.amount());
        }
    }
    
  3. 带有sealed 类的表达式求值器:
    sealed interface Expr permits Literal, Addition, Multiplication { }
    record Literal(int value) implements Expr { }
    record Addition(Expr left, Expr right) implements Expr { }
    record Multiplication(Expr left, Expr right) implements Expr { }
    
    static int evaluate(Expr expr) {
        return switch (expr) {
            case Literal l -> l.value();
            case Addition a -> evaluate(a.left()) + evaluate(a.right());
            case Multiplication m -> evaluate(m.left()) * evaluate(m.right());
        };
    }
    

Java 17 中的sealed 类使开发人员能够创建更健壮、更具表现力的类层次结构。它们在切换模式匹配中的效果尤为出色,可对不同的子类型进行简洁、类型安全和详尽的处理。通过升级到 Java 17 并使用sealed 类,您可以编写更简洁、更易维护的代码,并提高静态分析能力。

Records

Java 17 最终确定了records 功能,该功能在 Java 14 中首次作为预览版推出。records 提供了一种简洁的方法来声明类,这些类是浅层不可变数据的透明持有者。

新语法:

records 声明由名称和组件列表组成:

record Point(int x, int y) { }

这一简明声明自动提供了

  • 每个组件的私有、最终字段
  • 一个公共构造函数
  • 公共访问方法
  • equalshashCode, and toString 方法

何时使用

在需要时使用records :

  1. 创建简单的数据载体类
  2. 表示不可变数据
  3. 减少模板代码
  4. 提高可读性和可维护性

实例

  1. 在switch中用模式匹配来表示一个人::
    record Person(String name, int age) { }
    
    static String classifyPerson(Person person) {
        return switch (person) {
            case Person p when p.age() < 18 -> "Minor";
            case Person p when p.age() >= 18 && p.age() < 65 -> "Adult";
            case Person p when p.age() >= 65 -> "Senior";
            default -> "Unknown";
        };
    }
    
  2. 在collections 中使用 records :
    record TradeOrder(String symbol, int quantity, double price) { }
    
    List<TradeOrder> orders = List.of(
        new TradeOrder("AAPL", 100, 150.0),
        new TradeOrder("GOOGL", 50, 2800.0)
    );
    
    double totalValue = orders.stream()
        .mapToDouble(order -> order.quantity() * order.price())
        .sum();
    
  3. 复杂数据结构的嵌套records :
    record Address(String street, String city, String country) { }
    record Employee(String name, int id, Address address) { }
    
    Employee emp = new Employee("John Doe", 1001, 
        new Address("123 Main St", "New York", "USA"));
    
    System.out.println("Employee city: " + emp.address().city());
    

从 Java 8 升级:

对于从 Java 8 过渡到 Java 17 的开发人员来说,records大大改进了数据携带类的编写方式。在 Java 8 中,您通常会使用一个包含大量字段、 constructors,、getters和重载 Object方法的类。让我们比较一下 Java 8 方法和新的records语法:

Java 8 版本:

public class Customer {
    private final String name;
    private final String email;
    private final int age;

    public Customer(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public String getName() { return name; }
    public String getEmail() { return email; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Customer customer = (Customer) o;
        return age == customer.age &&
               Objects.equals(name, customer.name) &&
               Objects.equals(email, customer.email);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, email, age);
    }

    @Override
    public String toString() {
        return "Customer{" +
               "name='" + name + '\'' +
               ", email='" + email + '\'' +
               ", age=" + age +
               '}';
    }
}

Java 17 Records 版本:

public record Customer(String name, String email, int age) { }

Java 17 中的这一行代码提供了与 Java 8 中整个类相同的功能。record 会自动生成构造函数、访问方法、equals、hashCode 和 toString 方法。模板代码的大幅减少提高了可读性,减少了出错的可能性,使开发人员能够专注于数据模型的重要方面。

Java 17 中的记录提供了一种创建简单、不可变的数据类的方法,只需最少的模板。它们与模式匹配、流和其他现代 Java 功能配合得很好。通过升级到 Java 17 并使用记录,您可以编写更简洁、可读性更强、可维护性更高的代码,尤其是在处理数据传输对象或值类型时。

针对 instanceof 的模式匹配

Java 17 最终确定了 instanceof 的模式匹配功能,该功能简化了基于对象类型及其属性的条件处理。

新语法:

新语法将 instanceof 检查与声明相结合:

if (obj instanceof String s) {
    // Use s directly as a String
}

这取代了传统的:

if (obj instanceof String) {
    String s = (String) obj;
    // Use s
}

何时使用

在需要时使用模式匹配来匹配 instanceof

  • 简化类型检查和转换
  • 提高代码可读性
  • 降低因显式类型转换而产生错误的风险
  • 使多态代码更简洁

示例

  1. 形状面积计算:
    interface Shape { }
    record Circle(double radius) implements Shape { }
    record Rectangle(double width, double height) implements Shape { }
    
    static double calculateArea(Shape shape) {
        if (shape instanceof Circle c) {
            return Math.PI * c.radius() * c.radius();
        } else if (shape instanceof Rectangle r) {
            return r.width() * r.height();
        }
        return 0.0;
    }
    
  2. 增强了异常处理功能::
    try {
        // Some code that might throw exceptions
    } catch (Exception e) {
        String message = switch (e) {
            case IOException io -> "IO error: " + io.getMessage();
            case NumberFormatException nfe -> "Invalid number format: " + nfe.getMessage();
            default -> "Unexpected error: " + e.getMessage();
        };
        System.err.println(message);
    }
    
  3. 处理异构集合:
    List<Object> items = Arrays.asList("Hello", 42, 3.14, "World");
    
    for (Object item : items) {
        if (item instanceof String s) {
            System.out.println("String: " + s.toUpperCase());
        } else if (item instanceof Integer i) {
            System.out.println("Integer: " + (i * 2));
        } else if (item instanceof Double d) {
            System.out.println("Double: " + Math.round(d));
        }
    }
    

从 Java 8 迁移:

下面是一些从 Java 8 迁移到 Java 17 的示例,尤其侧重于 instanceof 功能的模式匹配:

  1. 处理不同类型的车辆:Java 8:
    public void processVehicle(Vehicle vehicle) {
        if (vehicle instanceof Car) {
            Car car = (Car) vehicle;
            System.out.println("Car with " + car.getDoors() + " doors");
        } else if (vehicle instanceof Motorcycle) {
            Motorcycle motorcycle = (Motorcycle) vehicle;
            System.out.println("Motorcycle with " + motorcycle.getEngineCapacity() + "cc engine");
        } else if (vehicle instanceof Bicycle) {
            Bicycle bicycle = (Bicycle) vehicle;
            System.out.println("Bicycle with " + bicycle.getGears() + " gears");
        }
    }
    

    Java 17:

    public void processVehicle(Vehicle vehicle) {
        if (vehicle instanceof Car car) {
            System.out.println("Car with " + car.getDoors() + " doors");
        } else if (vehicle instanceof Motorcycle motorcycle) {
            System.out.println("Motorcycle with " + motorcycle.getEngineCapacity() + "cc engine");
        } else if (vehicle instanceof Bicycle bicycle) {
            System.out.println("Bicycle with " + bicycle.getGears() + " gears");
        }
    }
    
  2. 处理不同类型的异常:Java 8:
    try {
        // Some code that might throw exceptions
    } catch (Exception e) {
        if (e instanceof IOException) {
            IOException ioException = (IOException) e;
            System.err.println("IO Error: " + ioException.getMessage());
        } else if (e instanceof SQLException) {
            SQLException sqlException = (SQLException) e;
            System.err.println("Database Error: " + sqlException.getSQLState());
        } else if (e instanceof NumberFormatException) {
            NumberFormatException nfe = (NumberFormatException) e;
            System.err.println("Number Format Error: " + nfe.getMessage());
        } else {
            System.err.println("Unexpected Error: " + e.getMessage());
        }
    }
    

    Java 17:

    try {
        // Some code that might throw exceptions
    } catch (Exception e) {
        if (e instanceof IOException io) {
            System.err.println("IO Error: " + io.getMessage());
        } else if (e instanceof SQLException sql) {
            System.err.println("Database Error: " + sql.getSQLState());
        } else if (e instanceof NumberFormatException nfe) {
            System.err.println("Number Format Error: " + nfe.getMessage());
        } else {
            System.err.println("Unexpected Error: " + e.getMessage());
        }
    }
    
  3. 处理形状列表:Java 8:
    public double calculateTotalArea(List<Shape> shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            if (shape instanceof Circle) {
                Circle circle = (Circle) shape;
                totalArea += Math.PI * circle.getRadius() * circle.getRadius();
            } else if (shape instanceof Rectangle) {
                Rectangle rectangle = (Rectangle) shape;
                totalArea += rectangle.getWidth() * rectangle.getHeight();
            } else if (shape instanceof Triangle) {
                Triangle triangle = (Triangle) shape;
                totalArea += 0.5 * triangle.getBase() * triangle.getHeight();
            }
        }
        return totalArea;
    }
    

    Java 17:

    public double calculateTotalArea(List<Shape> shapes) {
        double totalArea = 0;
        for (Shape shape : shapes) {
            if (shape instanceof Circle circle) {
                totalArea += Math.PI * circle.getRadius() * circle.getRadius();
            } else if (shape instanceof Rectangle rectangle) {
                totalArea += rectangle.getWidth() * rectangle.getHeight();
            } else if (shape instanceof Triangle triangle) {
                totalArea += 0.5 * triangle.getBase() * triangle.getHeight();
            }
        }
        return totalArea;
    }
    

这些示例演示了在 Java 17 中,instanceof 的模式匹配如何通过将类型检查和转换合并为一个步骤来简化代码。这使得代码更简洁、更易读,降低了因显式转换而出错的风险,并使代码更易于维护。

Java 17 中针对 instanceof 的模式匹配为处理多态代码提供了一种更优雅的方法。它减少了模板,提高了可读性,并有助于防止与显式铸造相关的错误。通过升级到 Java 17 并使用此功能,您在处理对象的特定类型操作时,可以编写更简洁、更安全、更具表现力的代码。

Switch 表达式

Java 17 最终确定了switch 表达式功能,该功能最初在 Java 12 和 13 中作为预览功能推出,并在 Java 14 中实现了标准化。该功能增强了 switch 语句,使其功能更强大、表现力更强。

新语法:

新语法允许将 switch 用作表达式,并为简洁的大小写标签引入了箭头语法:

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
    default -> throw new IllegalArgumentException("Invalid day: " + day);
};

何时使用

在需要时使用switch 表达式:

  • 将switch 结果直接赋值给变量
  • 从switch 中返回值
  • 简化多情况标签
  • 确保详尽处理所有可能的情况

举例说明

  1. 星期描述:
    String getDayType(DayOfWeek day) {
        return switch (day) {
            case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Weekday";
            case SATURDAY, SUNDAY -> "Weekend";
        };
    }
    
  2. 基于枚举的复杂计算:
    enum Operation { PLUS, MINUS, MULTIPLY, DIVIDE }
    
    int calculate(Operation op, int x, int y) {
        return switch (op) {
            case PLUS -> x + y;
            case MINUS -> x - y;
            case MULTIPLY -> x * y;
            case DIVIDE -> {
                if (y == 0) {
                    throw new ArithmeticException("Division by zero");
                }
                yield x / y;
            }
        };
    }
    
  3. 使用switch 表达式进行模式匹配(Java 17 中的预览功能)::
    static String formatValue(Object obj) {
        return switch (obj) {
            case Integer i -> String.format("int %d", i);
            case Long l -> String.format("long %d", l);
            case Double d -> String.format("double %f", d);
            case String s -> String.format("String %s", s);
            default -> obj.toString();
        };
    }
    

从 Java 8 迁移:

下面是一个将 Java 8 switch 语句迁移到 Java 17 switch 表达式的示例:

Java 8 version:

String getQuarterName(int month) {
    String quarter;
    switch (month) {
        case 1:
        case 2:
        case 3:
            quarter = "Q1";
            break;
        case 4:
        case 5:
        case 6:
            quarter = "Q2";
            break;
        case 7:
        case 8:
        case 9:
            quarter = "Q3";
            break;
        case 10:
        case 11:
        case 12:
            quarter = "Q4";
            break;
        default:
            throw new IllegalArgumentException("Invalid month: " + month);
    }
    return quarter;
}

Java 17 version:

String getQuarterName(int month) {
    return switch (month) {
        case 1, 2, 3 -> "Q1";
        case 4, 5, 6 -> "Q2";
        case 7, 8, 9 -> "Q3";
        case 10, 11, 12 -> "Q4";
        default -> throw new IllegalArgumentException("Invalid month: " + month);
    };
}

Java 17 中的switch 表达式为编写开关语句提供了一种更简洁、更具表现力的方法。它们减少了模板,提高了可读性,并有助于防止与直通行为相关的错误。通过升级到 Java 17 并使用此功能,您可以在处理多向分支逻辑时编写更简洁、更安全、更易维护的代码。

文本块

Java 17 最终确定了文本块功能,该功能在 Java 13 和 14 中作为预览功能推出。文本块为在 Java 代码中表达多行字符串提供了更方便的方法。

新语法:

文本块使用三引号("""")定义,可跨多行:

String textBlock = """
    This is a
    multi-line
    text block.""";

这取代了传统的字符串字面连接:

String oldStyle = "This is a\n" +
                  "multi-line\n" +
                  "string.";

何时使用

在需要时使用文本块:

  • 提高多行字符串的可读性
  • 保留缩进和格式化
  • 避免在 SQL 查询或 JSON 字面中使用转义引号
  • 简化 Java 代码中的 HTML 或 XML 内容

举例说明

 

  1. SQL Query:
    String query = """
        SELECT id, name, email
        FROM users
        WHERE active = true
        ORDER BY name""";
    
  2. JSON:
    String json = """
        {
            "name": "John Doe",
            "age": 30,
            "city": "New York"
        }""";
    
  3. HTML:
    String html = """
        <html>
            <body>
                <h1>Hello, World!</h1>
            </body>
        </html>""";
    

从 Java 8 迁移:

下面是一个使用文本块将多行字符串从 Java 8 迁移到 Java 17 的示例:

Java 8:

String oldHtml = "<!DOCTYPE html>\n" +
                 "<html>\n" +
                 "    <head>\n" +
                 "        <title>Example</title>\n" +
                 "    </head>\n" +
                 "    <body>\n" +
                 "        <h1>Hello, World!</h1>\n" +
                 "    </body>\n" +
                 "</html>";

Java 17:

String newHtml = """
    <!DOCTYPE html>
    <html>
        <head>
            <title>Example</title>
        </head>
        <body>
            <h1>Hello, World!</h1>
        </body>
    </html>""";

附加功能:

  1. 附带空白修剪:
    编译器会自动删除附带的空白,使源代码的缩进与字符串的内容无关。
  2. 尾部空白修剪:
    除非使用反斜线转义,否则会删除每行末尾的空白。
  3. 字符串连接:
    文本块可使用 + 运算符与其他字符串或文本块连接。
  4. 字符串格式化:
    您可以在文本块中使用 String.format() formatted() 方法。

    String name = "Alice";
    String message = """
        Hello, %s!
        Welcome to Java 17.""".formatted(name);
    

Java 17 中的文本块为处理多行字符串提供了一种更具可读性和可维护性的方法。它们减少了对转义字符的需求,保留了格式化,并使涉及长字符串字面量的代码更加简洁。通过升级到 Java 17 并使用文本块,您可以显著提高代码的可读性和可维护性,尤其是在处理 SQL 查询、JSON、HTML 或任何其他多行文本内容时。

局部变量类型推断 (var)

Java 10 引入了使用 var 关键字进行局部变量类型推断的功能,该功能经过进一步改进,现已成为 Java 17 中的一项稳定功能。该功能可通过初始化器推断局部变量的类型,从而使代码更加简洁。

新语法:

您可以使用 var 关键字来代替明确声明类型:

var name = "John Doe";
var age = 30;
var list = new ArrayList<String>();

何时使用

在以下情况下使用局部变量类型推断

  • 类型在右侧显而易见
  • 希望减少代码的冗长度
  • 正在处理复杂的泛型类型
  • 使用匿名类或 lambda 表达式

示例

  1. 基本用法:
    // Java 8
    String name = "John Doe";
    int age = 30;
    List<String> names = new ArrayList<>();
    
    // Java 17
    var name = "John Doe";
    var age = 30;
    var names = new ArrayList<String>();
    
  2. 复杂的通用类型:
    // Java 8
    Map<String, List<String>> map = new HashMap<String, List<String>>();
    
    // Java 17
    var map = new HashMap<String, List<String>>();
    
  3. 在 for-loops 里:
    // Java 8
    for (Map.Entry<String, List<String>> entry : map.entrySet()) {
        // ...
    }
    
    // Java 17
    for (var entry : map.entrySet()) {
        // ...
    }
    
  4. 使用 lambda 表达式:
    // Java 8
    Predicate<String> predicate = s -> s.length() > 5;
    
    // Java 17
    var predicate = (Predicate<String>) s -> s.length() > 5;
    
  5. 与 streams 联用:
    // Java 8
    Stream<String> stream = list.stream().filter(s -> s.startsWith("A"));
    
    // Java 17
    var stream = list.stream().filter(s -> s.startsWith("A"));
    

从 Java 8 迁移:

从 Java 8 迁移到 Java 17 时,您可以在代码库中逐步引入 var。下面是一个如何重构 Java 8 类的示例:

Java 8:

public class UserService {
    public User findUser(String username) {
        DatabaseConnection connection = DatabaseConnection.getInstance();
        PreparedStatement statement = connection.prepareStatement("SELECT * FROM users WHERE username = ?");
        statement.setString(1, username);
        ResultSet resultSet = statement.executeQuery();

        if (resultSet.next()) {
            String name = resultSet.getString("name");
            int age = resultSet.getInt("age");
            return new User(username, name, age);
        }
        return null;
    }
}

Java 17:

public class UserService {
    public User findUser(String username) {
        var connection = DatabaseConnection.getInstance();
        var statement = connection.prepareStatement("SELECT * FROM users WHERE username = ?");
        statement.setString(1, username);
        var resultSet = statement.executeQuery();

        if (resultSet.next()) {
            var name = resultSet.getString("name");
            var age = resultSet.getInt("age");
            return new User(username, name, age);
        }
        return null;
    }
}

Java 17 中的局部变量类型推断提供了一种在不牺牲可读性或类型安全性的前提下编写更简洁代码的方法。在处理复杂的泛型或从上下文中可以明显看出类型时,它对减少模板特别有用。通过升级到 Java 17 并明智地使用 var 关键字,您可以让代码更简洁、更易维护,同时还能充分利用 Java 强大的类型系统。

你也许感兴趣的:

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注