专栏名称: OSC开源社区
OSChina 开源中国 官方微信账号
目录
相关文章推荐
程序员的那些事  ·  热搜第一!DeepSeek百万年薪招AI人才 ... ·  17 小时前  
OSC开源社区  ·  国内AI适配再下一城:天数智芯加入,Deep ... ·  2 天前  
OSC开源社区  ·  DeepSeek R1已在Gitee ... ·  4 天前  
程序员的那些事  ·  奥特曼被逼急:深夜上线 ... ·  5 天前  
程序员小灰  ·  不要脸!DeepSeek被美国海军禁用 ·  1 周前  
51好读  ›  专栏  ›  OSC开源社区

Java 23和IntelliJ IDEA

OSC开源社区  · 公众号  · 程序员  · 2024-11-26 15:44

正文

↓推荐关注↓

Java 23 包含全新和更新的 Java 语言功能、核心 API 以及 JVM,同时适合新的 Java 开发者和高级开发者。从 IntelliJ IDEA 2024.2 开始已支持 Java 23 功能。

跟上 Java 新版本的发布节奏可能很难,这意味着要解决一连串的问题——更改是什么、为什么要更改以及如何使用全新和更新的功能。

在这篇博文中,我将介绍 Java 23 的一些全新和更新功能 – 它们为您解决的痛点、语法和语义,以及 IntelliJ IDEA 如何帮助您使用它们。

我将重点介绍 Java 23 功能,例如在模式匹配中包含基元数据类型、在代码库中导入模块的能力、在文档注释中使用 Markdown 的可能性、隐式声明的类与实例 main 方法,以及灵活的构造函数体。

如果您感兴趣,请访问此链接来查看 Java 23 其他功能的列表。

在深入了解 Java 23 功能的详细信息之前,我们先快速配置 IntelliJ IDEA。


IntelliJ IDEA 配置

IntelliJ IDEA 2024.2 开始已经支持 Java 23。

在您的 Project Settings(项目设置)中,将 SDK 设置为 Java 23。您可以将 IntelliJ IDEA 配置为使用下载版本的 JDK 23,也可以选择从供应商列表中下载而无需退出 IDE。对于语言级别,选择“23(Preview) – Primitive types in patterns, implicitly declared classes, etc.”(23(预览) – 模式中的基元类型,隐式声明的类等),如下面的屏幕截图中所示:

要使用 Markdown 文档注释等正式功能,请将语言级别更改为“23 – Markdown documentation comments”(23 – Markdown 文档注释),如以下设置屏幕截图中所示:

在掌握 IntelliJ IDEA 的配置后,我们来深入学习新功能。


模式、instanceof 和 switch 中

的基元类型(预览功能)

想象一下,您需要编写一个条件构造,它基于 long 变量的值是否匹配几个字面量值或落在某个值范围内来执行代码。

您会怎么做?在此之前,您只能使用 if/else 构造执行此操作。但是,借助 Java 23 中的模式、instanceof 和 switch 中的基元类型(一种预览语言功能),您可以使用更具表达性且易于阅读的 switch 构造来编写此功能,同时在 case 标签中使用 long 值。

将基元类型添加到模式匹配中

意味着什么?

在 Java 23 之前,switch 构造(语句和表达式)仅能处理引用变量和一些基元数据类型,例如 intbyteshort(有约束)。此外,instanceof 运算符不能用于任何基元数据类型。

借助 Java 23,您可以将所有基元数据类型(包括 booleanlongfloatdouble)与 Switch 构造中的模式匹配和 instanceof 运算符一起使用。这适用于在嵌套和顶层上下文中使用。

为什么您应该关注此功能?一项功能的价值取决于它影响的代码库有多大以及使用频率。由于条件语句是编程的基础之一,您可以预期在代码库中大量使用此功能。即使您可能不会编写代码,您也会阅读由其他人编写的代码。

我们通过一个示例来理解此功能。

一个示例(将较长的 if-else 语句

替换为 switch 表达式)

想象一个方法,例如 getHTTPCodeDesc(int),它接受 HTTP 服务器代码作为 int 值,并返回相应的字符串表示,同时将其与某个字面量值或值范围进行比较。

这段代码似乎没有明显的问题 – 我们都编写过或阅读过类似的代码。不过,处理 if-else 构造的流程可能需要更长的时间,因为它们可能定义不仅限于一个变量的复杂条件。我们简单地假设方法 getHTTPCodeDesc() 的定义如下:

public String getHTTPCodeDesc(int code) {
   if (code == 100) {
       return "Continue";
   } 
   else if (code == 200) {
       return "OK";
   } 
   else if (code == 301) {
       return "Moved permanently";
   } 
   else if (code == 302) {
       return "Found";
   } 
   else if (code == 400) {
       return "Bad request";
   } 
   else if (code == 500) {
       return "Internal server error";
   } 
   else if (code == 502) {
       return "Bad gateway";
   } 
   else  if (code > 100 && code 200) {
       return "Informational";
   } 
   else if (code > 200 && code 300) {
       return "Successful";
   } 
   else if (code > 302 && code 400) {
       return "Redirection";
   } 
   else if (code > 400 && code 500) {
       return "Client error";
   } 
   else if (code > 502 && code 600) {
       return "Server error";
   } 
   else {
       return "Unknown error";
   }
}

在 Java 23 中,可以使用 switch 表达式(使用基元作为模式)替换上述代码,具体如下所示:

public String getHTTPCodeDesc(int code) {
    return switch(code) {
        case 100 -> "Continue";
        case 200 -> "OK";
        case 301 -> "Moved Permanently";
        case 302 -> "Found";
        case 400 -> "Bad Request";
        case 500 -> "Internal Server Error";
        case 502 -> "Bad Gateway";
        case int i when i > 100 && i 200 -> "Informational";
        case int i when i > 200 && i 300 -> "Successful";
        case int i when i > 302 && i 400 -> "Redirection";
        case int i when i > 400 && i 500 -> "Client Error";
        case int i when i > 502 && i 600 -> "Server Error";
        default                            -> "Unknown error";
    };
}

上述代码的第一个明显好处是,相比于使用 if-else 语句的版本,它更易于阅读和理解。您可以一目了然地理解代码逻辑。

另一个不那么明显的好处(但也很重要)是上述代码如何减少您的认知负担。认知负担指的是您在工作记忆中拥有的信息量(工作记忆空间有限)。如果您试图用与您的最终目标无直接关联的指令或信息来使您的工作记忆超载,那么您的工作效率会降低。易于阅读的代码段可以帮助您将注意力集中到代码的其他领域。当我们探讨如何经常从中受益时,这些小的胜利会起到很大作用。

现在,我们来聊聊简单的部分,我是指语法。正如您所注意到的,利用带保护的 (when i > 100 && i < 200) 类型模式 (int i),带保护的 case 标签既可以有常量值(例如 100、200 等),也可以有使用模式匹配指定的值范围。您也可以定义一个 default 子句。

在上述代码中,方法 getHTTPCodeDesc() 使用 switch 表达式返回一个值。无论您传递给方法形参(即 code)的值如何,该方法都必须返回一个值。换句话说,switch 表达式必须为详尽。如果不是,IntelliJ IDEA 可以检测到并提供添加 default 子句的选项,如以下 GIF 中所示:

上述代码在 int 类型的变量上进行切换。类似地,您可以在所有其他基元类型(例如 long、double、float 等)的变量上切换。

您是第一次使用模式匹配还是

首次了解 switch 构造的最近更改?

如果您对模式匹配完全不熟悉,请查看我的博文 Java 17 和 IntelliJ IDEA 中关于基础知识的部分。如果您对如何将模式匹配与 switch 构造配合使用感兴趣,我有关于此主题的另一篇详细博文:Evolution of the Switch Construct in Java—Why Should you Care?。这篇文章介绍了 switch 构造如何使用模式匹配来检查引用值是否符合不同模式,并根据变量的类型及其特性有条件地执行代码。

将模式匹配与布尔值配合使用

我们经常阅读和编写根据 boolean 变量的值是 true 还是 false 来返回值的代码。例如,在以下代码中,方法 calculateDiscount 根据您传递给方法形参 isPremiumMember 的值是 true 还是 false 来计算并返回折扣值:

public class DiscountCalculator {
    private static final int PREMIUM_DISCOUNT_PERCENTAGE = 20;
    private static final int REGULAR_DISCOUNT_PERCENTAGE = 5;

    public int calculateDiscount(boolean isPremiumMember, int totalAmount) {
        int discount;
        if (isPremiumMember) {
            // Calculate discount for premium members
            discount = (totalAmount * PREMIUM_DISCOUNT_PERCENTAGE) / 100;
        } else {
            // Calculate discount for regular members
            discount = (totalAmount * REGULAR_DISCOUNT_PERCENTAGE) / 100;
        }
        return discount;
    }
}

除了 if-else 构造外,您还可以切换布尔方法形参 isPremiumMember 的值,而无需定义局部变量 discount,具体如下所示:

public int calculateDiscount(boolean isPremiumMember, int totalAmount) {
    return switch (isPremiumMember) {
        case true -> (totalAmount * PREMIUM_DISCOUNT_PERCENTAGE) / 100;
        case false -> (totalAmount * REGULAR_DISCOUNT_PERCENTAGE) / 100;
    };
}

由于方法 calculateDiscount() 中的 switch 表达式为详尽,如果您缺少 true 或 false 值中的任何一个,IntelliJ IDEA 都可以检测到并建议您插入默认或缺少的 true/false case,如以下 gif 中所示:


将基元类型与 instanceof 运算符配合使用

在 Java 23 之前,任何基元类型都不能与 instanceof 运算符结合使用。

instanceof 运算符可用于检查变量的类型,并有条件地执行代码。利用 instanceof 的模式匹配,如果待比较变量的类型与类型模式匹配,您还可以声明和初始化模式变量,而无需显式转换。instanceof 变量还可以使用保护模式。

随着将基元类型添加到 instanceof 运算符,您可以定义如下代码:

import static java.io.IO.println;

void main() {
    var weight = 68;
    if (weight instanceof byte byteWeight && byteWeight <= 70) {
        println("Weight less than 70");
    }
}

请注意,Java 23 中的“隐式声明的类与实例 main 方法”功能定义了具有 static 方法的 java.io.IO,允许您导入并使用 println() 将值输出到控制台,而不是使用 System.out.println() 执行此操作。

如果您计划检查更多的类型和条件,可以使用带有保护的 switch 构造,具体如下所示:

var weight = 68;

switch (weight) {
    case byte b when b  println("byte: less than 70");
    case int i  when i  println("int: less than 7000");
    case long l when l >= 7 _000 && l  println("long range: 7_000 - 70_000");
    case double d                               -> println("double");
}

数据类型之间的安全转换 

当您将模式匹配与基元数据类型配合使用时,Java 编译器确保没有信息丢失。在以下示例中,instanceof 运算符可以在检测到安全时在 double 和 byte 数据类型之间转换:

double height = 67;

if (height instanceof byte byteHeight)
    System.out.println(byteHeight);

如果没有 instanceof 运算符,类似的代码将无法执行。以下代码不会编译:

double height = 67;

final byte convertToByte = height;

IntelliJ IDEA 中的稳健数据流分析

IntelliJ IDEA 为 switch 语句中的基元类型提供了有力支持,还集成了复杂的数据流分析,可以帮助开发者避免常见问题。如以下示例中所示,IntelliJ IDEA 中的数据流分析可以确定第二个 case 标签,即 case int _ 和其余的 case 标签不可达(因为如果变量 weight 的值大于 70,代码会退出方法)。IntelliJ IDEA 会就不可到达的代码发出警告并提供适当的建议:


记录和基元数据类型组件

想象一下,您定义了如下记录 Person:

record Person(String name, double weight) {}

在此之前,您可以将其分解为精确的数据类型。不过,借助模式匹配中的基元类型,您可以使用其他兼容的数据类型,例如 int、long 等。示例如下:

Person person = new Person("Java"672);

switch (person) {
    case Person(String name, byte weight) -> println("byte:" + weight);
    case Person(String name, int weight) -> println("int:"  + weight);
    case Person(String name, double weight) -> println("double:" + weight);
    case Person(String name, long weight) -> println("long:" + weight);
    default -> throw new IllegalStateException("Unexpected value: " + person);
}

您也可以将它与 instanceof 运算符配合使用,具体如下所示:

 if (person instanceof Person(String name, byte weight)) {
     System.out.println("Instanceof : " + weight);
 }

IntelliJ IDEA 支持此功能的后续计划

对模式匹配中基元数据类型的更多支持正在开发,包括能够在所有基元数据类型上使用后缀运算符 .switch 来开始编写 switch 构造。此外,也在开发的支持是将现有的 if-else 语句转换为使用基元数据类型的 switch 构造 – 这可让您更轻松地采用此新功能。


Markdown 文档注释

(正式功能)

在此之前,Java 文档注释需要使用 HTML 和 JavaDoc 标记进行编写。借助此新功能,即文档注释,您还可以使用 Markdown 编写 JavaDoc 注释。这是 Java 23 中的一项正式功能。

您是否想知道此更改的原因是什么?

原因之一是 HTML 不再是新开发者的流行选择(尽管在 Java 于 20 世纪 90 年代后期推出时,它是一个不错的选择)。手动编写 HTML 并不简单。此外,Markdown 更易于编写、阅读,并且可以轻松转换为 HTML。许多开发者都在使用 Markdown 来记录他们的代码、撰写书籍、文章、博文,以及生成网站页面等。

让我们看看如何在您的源代码文件中使用 Markdown 编写 Javadoc 注释,以及 IntelliJ IDEA 如何提供帮助。

一个示例

Markdown 文档注释以 /// 开头。

使用三个斜杠的选择非常有趣。Oracle 中此功能的负责人 Jonathan Gibbons 分享说,更改 Java 语言中功能的语法并非易事。多行注释以 /* 开头,以 */ 结尾。这样便很难在文档中包括可能包含 */ 的任何代码。因此,Markdown 文档注释以 /// 开头。

此外,也支持编写文档注释的旧方法,即 HTML 和 JavaDoc 标记。Jonathan 还提到,无法将 JavaDoc 标记转换为 Markdown 等效标记。因此,开发者可以使用 Markdown 注释和 JavaDoc 标记的组合,以获得两全其美的结果。

下面是一个使用 Markdown 和 Javadoc 标记记录方法的示例:

///
/// **Getting started and having fun learning Java :)**
///
/// Prints a pattern of the letter 'P' using the specified character.
///
/// @param size the size of the pattern
/// @param charToPrint the character to use for the pattern
///
private void printP(int size, char charToPrint{
    for (int i = 0; i         for (int j = 0; j             if (j == 0 || (i == 0 || i == size / 2) && j 1 || (j == size - 1 && i <= size / 2)) {
                System.out.print(charToPrint + " ");
            } else {
                System.out.print("  ");
            }
        }
        System.out.println();
    }
}

IntelliJ IDEA 可以帮助您在编辑器中切换视图,查看文档注释对任何阅读 Java 文档注释的人如何显示。

在 IntelliJ IDEA 中查看 Java 文档注释

JEP“Markdown 文档注释”的负责人 Jonathan Gibbons 强调,所有开发者都需要检查他们添加到代码库中的 Java 文档注释是否正确。

IntelliJ IDEA 提供了“阅读器模式”,让您能够在源代码中查看 Java 文档注释。您还可以使用 Toggle Rendered View(切换渲染视图)功能在 Java 文档注释代码和查看方式之间切换,如以下 gif 中所示:


在 IntelliJ IDEA 中编写 

Markdown 文档注释

IntelliJ IDEA 可以检测到您正在使用 Markdown 记录方法。当您以 /// 开始并按 Enter 时,它也会在下一行添加 ///,如以下 GIF 中所示:


您是否应该将现有的文档注释

转换为使用 Markdown?

下面的 gif 显示了在 IntelliJ IDEA 中使用 Markdown 编写的 hashCode 方法的文档。利用 Toggle Rendered View(切换渲染视图),您可以轻松在阅读器视图中查看文档,这更容易阅读和理解。

理想情况下,除非您的开发者或 API 用户在使用不提供替代视图的工具(如 IntelliJ IDEA)查看您的(API、库、框架)代码库时遇到重大可读性问题,否则我不鼓励您将现有文档转换为使用 Markdown。


模块导入声明(预览功能)





请到「今天看啥」查看全文