当前位置: 首页 > article >正文

Java22-匿名变量/模式(Unnamed Variables Patterns)

序言

明确赋值

每个通过语句声明的局部变量每个空白final字段在访问其值时都必须具有明确赋值

对其值的访问包括变量的简单名称(或者,对于字段,由限定的字段的简单名称)出现在表达式中的任何位置,除了作为简单赋值运算符,this的左边操作数 "=".

如果右侧操作数的类型与变量的类型不兼容,则会发生编译时错误。

否则,在运行时,将按以下三种方式之一评估表达式。

  • 对左侧表达式进行求值(例如O.f,则会获取O的值),如果意外终止,则表达式也终止
  • 对右侧操作数求值,如果意外终止,则表达式也终止
  • 对左侧变量进行评估,不符合则终止
  • 将右侧操作数的值赋予左侧变量

概括匿名变量/模式

使用未命名变量和未命名模式增强 Java 编程语言,当需要声明,但从未使用变量声明或嵌套模式时可以使用它们,两者都用下划线字符 表示_

目的

  • 捕获开发人员的意图,开发者可以明确表示某个绑定(如变量)或 Lambda 表达式的参数不会被使用,通过工具或规则强制执行这一约定。这么做可以让程序更加清晰,并减少可能出现的错误。这样,开发者和工具都能更好地理解哪些参数是故意未使用的,避免误认为是代码缺陷。

  • 增强代码的可维护性,通过识别那些必须声明但未使用的变量(例如 catch 语句中的变量),减少不必要的代码。

  • 允许在 switch 语句的 case 标签中出现多个模式,只要这些模式没有声明任何模式变量。

  • 改善record模式的可读性,通过省略不必要的嵌套类型模式,使代码更加简洁明了。

非目的 

  • 允许未命名的字段或方法参数并不是目标。

  • 例如,在明确赋值分析中,改变局部变量的语义并不是目标 。

动机 

开发人员有时会声明他们不打算使用的变量,无论是出于代码风格还是因为语言在某些上下文中需要变量声明。不使用该变量的意图在编写代码时是已知的,但如果没有明确捕获,那么以后的维护者可能会意外使用该变量,从而违反意图。如果能够使意外使用此类变量成为不可能,那么代码将更具信息性、更易读,并且更不容易出错。

匿名变量(Unused variables)

声明一个未使用过的变量的需求在副作用比结果更重要的代码中尤其常见。

例如,此代码计算total循环的副作用,而不使用循环变量 order

static int count(Iterable<Order> orders) {
    int total = 0;
    for (Order order : orders)    // order is unused
        total++;
    return total;
}

鉴于没有使用order,order声明的突出是不幸的。声明可以缩短为var  order,但无法避免为这个变量命名。名称本身可以缩写为,例如o,但这种语法技巧并不能传达变量永远不会被使用的意图。此外,静态分析工具通常会警告或抛出异常,告知开发人员有未使用的变量,即使开发人员打算不使用,也可能无法消除警告。

对于另一个例子,表达式的副作用比其结果更重要,以下代码将数据出列,但只需要每三个元素中的两个:

Queue<Integer> q = ... // x1, y1, z1, x2, y2, z2 ..
while (q.size() >= 3) {
   int x = q.remove();
   int y = q.remove();
   int z = q.remove();            // z is unused
    ... new Point(x, y) ...
}

第三次调用remove()需要的副作用 — 使元素出队 — 无论其结果是否分配给变量 ,因此z可以省略的声明。

但是,出于可维护性考虑,此代码的作者可能希望通过声明变量来一致地表示的结果remove()。他们目前有两个选择,但都不太令人满意:

  • 不声明变量z,这会导致不对称,并可能导致关于忽略返回值的静态分析警告。
  • 声明一个未使用的变量,且可能收到关于未使用变量的静态分析警告。

未使用的变量经常出现在另外两个关注副作用的语句中: 

  •  try-with-resources语句总是用于其副作用,即自动关闭资源。在某些情况下,资源表示try块的代码执行的上下文;代码不直接使用上下文,因此资源变量的名称无关紧要。例如,假设ScopeContext资源是可自动关闭的,则以下代码将获取并自动释放上下文:
try (var acquiredContext = ScopedContext.acquire()) {
    ... acquiredContext not used ...
}

 acquiredContext这个名字只是一个随便起的名字,所以最好去掉它。

  • 异常是最终的副作用,处理异常通常会产生一个未使用的变量。例如,大多数开发人员都编写了这种形式的catch块,其中异常参数ex未使用:
String s = ...;
try {
    int i = Integer.parseInt(s);
    ... i ...
} catch (NumberFormatException ex) {
    System.out.println("Bad number: " + s);
}

 即使没有副作用的代码有时也必须声明未使用的变量。例如:

...stream.collect(Collectors.toMap(String::toUpperCase,v -> "NODATA"));

此代码生成一个映射,将每个键映射到相同的占位符值。由于未使用lambda参数v,因此其名称无关紧要。 

所有这些情况,变量都是未使用的,其变量名是无意义的,如果我们可以简单地声明没有名称的变量,那就更好了。这将使维护人员不用区理解无意义的名称,并避免静态分析工具对未使用变量的警告。

可以合理地声明没有名称的变量是那些在方法外部不可见的变量:

  • 局部变量
  • 异常参数
  • lambda参数

如上所示。这些类型的变量可以重命名或匿名,而不会受到外部影响(也可以理解为不会对外部产生影响)。相比之下,字段(即使它们是私有的)在方法之间传递对象的状态,而匿名名的状态既没有帮助也不可维护,所以字段不适合被声明为匿名变量。

匿名模式变量(Unused pattern variables)

局部变量也可以通过类型模式声明——这样的局部变量被称为模式变量——因此类型模式也可以声明未使用的变量。考虑以下代码,它在切换密封类Ball实例的switch语句的case标签中使用类型模式:

sealed abstract class Ball permits RedBall, BlueBall, GreenBall { }
final  class RedBall   extends Ball { }
final  class BlueBall  extends Ball { }
final  class GreenBall extends Ball { }

Ball ball = ...
switch (ball) {
    case RedBall   red   -> process(ball);
    case BlueBall  blue  -> process(ball);
    case GreenBall green -> stopProcessing();
}

switch的case使用类型模式检查Ball的类型,但模式变量red、blue和green不在case子句的右侧使用。如果我们能省略这些变量名,这段代码会更清晰。

现在假设我们定义了一个record类Box,它可以容纳任何类型的Ball,但也可能容纳null值:

record Box<T extends Ball>(T content) { }

Box<? extends Ball> box = ...
switch (box) {
    case Box(RedBall   red)     -> processBox(box);
    case Box(BlueBall  blue)    -> processBox(box);
    case Box(GreenBall green)   -> stopProcessing();
    case Box(var       itsNull) -> pickAnotherBox();
}

嵌套类型模式仍然声明未使用的模式变量。由于此switch比前一个更复杂,因此省略嵌套类型模式中未使用的变量的名称将进一步提高可读性。 

 

匿名嵌套模式(Unused nested patterns)

我们可以在record类中嵌套record类,从而导致数据结构的形状与其中的数据项同样重要。例如:

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

... new ColoredPoint(new Point(3,4), Color.GREEN) ...

if (r instanceof ColoredPoint(Point p, Color c)) {
    ... p.x() ... p.y() ...
}

在这段代码中,创建一个ColoredPoint实例,而另一部分使用模式instanceof来测试一个变量是否为 ColoredPoint,如果是,则提取它的两个组成值。

ColoredPoint(Point p, Color c)具有非常好的描述性,但程序通常只使用部分组件值进行进一步处理。

例如,上面的代码仅p在 if块中使用,而不是c。每次在进行模式匹配时都需要为record类的所有组件编写类型模式,显得有些繁琐。此外,如果某个组件(如 Color)完全没有用到,这样的条件判断也不够直观,这可能会让代码的可读性变差,尤其是在记录模式嵌套的情况下。当记录模式嵌套以提取组件内的数据时,这一点尤其明显,如下所示:

if (r instanceof ColoredPoint(Point(int x, int y), Color c)) {
    ... x ... y ...
}

我们可以使用匿名的模式变量来降低视觉成本。

在 Java 的模式匹配中,如果我们只需要 ColoredPoint(Point(int x, int y), Color _) 中的 Point,可以用 _ 来忽略 Color。不过,Color 类型仍显得冗余。我们可以用 var 简化成 ColoredPoint(Point(int x, int y), var _),但嵌套的 var _ 仍显得过重。因此,进一步减少不必要的部分(如完全省略 Color),可以简化代码并提升可读性。 

说明(Description)

匿名变量是使用下划线字符_ (U+005F) 来声明的,它代替局部变量声明语句中的局部变量的名称、子句中的异常参数的名称catch或 lambda 表达式中的 lambda 参数的名称。

匿名模式变量是使用下划线字符来声明的,以代替类型模式中的模式变量

匿名模式用下划线字符表示,相当于匿名类型模式var _。它允许在模式匹配中省略记录组件的类型和名称。

以下类型的声明可以引入命名变量(用标识符表示)或匿名变量(用下划线表示):

  • 块中的局部变量声明语句(JLS§14.4.2) 
  • -with-resources 语句的资源规范try(JLS §14.20.3)
  • 基本for循环的头部(JLS §14.14.1)
  • 增强for循环的头部(JLS §14.14.2)
  • catch块的异常参数(JLS §14.20)
  • lambda表达式的形参(JLS §15.27.1)

声明匿名变量不会在作用域中设置名称,因此变量初始化后无法写入或读取。必须给局部变量声明或try-with-resources语句中声明的匿名变量初始化。 

匿名变量

对于使用增强for循环副作用的匿名变量:

static int count(Iterable<Order> orders) {
    int total = 0;
    for (Order _ : orders)    // Unnamed variable
        total++;
    return total;
}

简单for循环的初始化也可以声明未命名的局部变量:

for (int i = 0, _ = sideEffect(); i < 10; i++) { ... i ... } 

一个赋值语句,其中不需要右侧表达式的结果:

Queue<Integer> q = ... // x1, y1, z1, x2, y2, z2, ...
while (q.size() >= 3) {
   var x = q.remove();
   var y = q.remove();
   var _ = q.remove();        // Unnamed variable
   ... new Point(x, y) ...
}

如果程序只需要处理x1、x2等坐标,则可以在多个赋值语句中使用未命名的变量:

while (q.size() >= 3) {
    var x = q.remove();
    var _ = q.remove();       // Unnamed variable
    var _ = q.remove();       // Unnamed variable
    ... new Point(x, 0) ...
}

在catch块中:

String s = ...
try {
    int i = Integer.parseInt(s);
    ... i ...
} catch (NumberFormatException _) {        // Unnamed variable
    System.out.println("Bad number: " + s);
}

可以在多个catch块中使用:

try { ... }
catch (Exception _) { ... }                // Unnamed variable
catch (Throwable _) { ... }                // Unnamed variable

ry-with-resources中使用:

try (var _ = ScopedContext.acquire()) {    // Unnamed variable
    ... no use of acquired resource ...
}

一个参数无关的lambda中使用:

...stream.collect(Collectors.toMap(String::toUpperCase,
                                   _ -> "NODATA"))    // Unnamed variable

匿名模式变量 

匿名的模式变量可以出现在类型模式中,包括var类型模式,无论该类型模式是出现在顶层还是嵌套在record模式中。例如,Ball示例现在可以写成:

switch (ball) {
    case RedBall _   -> process(ball);          // Unnamed pattern variable
    case BlueBall _  -> process(ball);          // Unnamed pattern variable
    case GreenBall _ -> stopProcessing();       // Unnamed pattern variable
}

而Box和Ball示例为:

switch (box) {
    case Box(RedBall _)   -> processBox(box);   // Unnamed pattern variable
    case Box(BlueBall _)  -> processBox(box);   // Unnamed pattern variable
    case Box(GreenBall _) -> stopProcessing();  // Unnamed pattern variable
    case Box(var _)       -> pickAnotherBox();  // Unnamed pattern variable
}

通过使用匿名名称,匿名的模式变量使类型匹配在switch和instanceof中都更加清晰。 

 

至此,Java匿名变量到此为止了,参考文章:JEP456


http://www.kler.cn/news/312756.html

相关文章:

  • k8s自动清理pod脚本分享
  • Web网站常用测试工具
  • Java【代码 18】处理Word文档里的Excel表格数据(源码分享)
  • 【系统架构设计师-2013年真题】案例分析-答案及详解
  • Leetcode 和为 K 的子数组
  • 【深度学习 Transformer VIT】Transformer VIT:拆解“视觉变形金刚”,笑谈技术细节
  • 【Android源码】屏蔽系统通知出现在系统栏中
  • C++速通LeetCode中等第7题-和为K的子数组(巧用前缀和)
  • 视频服务器:GB28181网络视频协议
  • python使用argparse解析命令行,如何正确传入科学计数法形式的浮点数
  • 力扣100题——杂题
  • Java集合(一)
  • C++ 文件操作
  • 十、数字人IP应用方案
  • chromedriver下载与安装方法
  • react之jsx基础(2)高频使用场景
  • DEPLOT: One-shot visual language reasoning by plot-to-table translation论文阅读
  • Android14请求动态申请存储权限
  • WGCAT工单系统 v1.2.1 支持导出PDF和分享创建工单功能
  • JAVA 根据开始和结束ip,计算中间的所有ip
  • 【MySQL】MySQL和Workbench版本兼容问题
  • 力扣每日一题 公交站间的距离
  • 远程访问NAS速度慢??那是因为你没用对。。。
  • 2024年9月北京docker安装+nvidia-docker
  • Clang插件演示-直接调用AI模型定义的变量完成模型推理
  • IP Source Guard技术原理与应用
  • 如何在GitHub上克隆仓库:HTTPS、SSH和GitHub CLI的区别
  • 【机器学习(五)】分类和回归任务-AdaBoost算法-Sentosa_DSML社区版
  • 【算法题】300. 最长递增子序列-力扣(LeetCode)
  • 【资料分析】刷题日记3