首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

Java 21将发布,将实现优雅的函数式编程

2023年9月19日大家都期待的Java 21即将发布(有在线发布会,UTC时间13:00-21:00)。

Java 21带来的功能中 switch块中记录模式和表达式,这种语法对Java来说具有里程碑意义。此后,Java可以具有与C#、Kotlin和Rust类似的方式正确支持函数式编程模式。今天我们就来说这方面内容,管中窥豹以了解一下Java 21中的进步。

Switch表达式历史

switch表达式,在Java 14语法稳定,包括了16个记录和instanceof模式匹配,17个sealed类,在Java 21中还实现记录模式和切换模式匹配。

这样可以实现Java函数式编程的基础之一代数数据类型以及使用它们的惯用方式

模式匹配是一种静态(即在编译时,即编写代码时)验证正在处理的数据中是否存在某些模式的方法。

让我们来看一个例子:

注意,数据A是一个 Record实例,并且可以包含任何记录类型。首先尝试打印r的内容与普通Java if语句一起使用,然后再使用switch模式匹配进行操作。

可以看出switch 块的结构显然if-else更好,更优雅。switch模式非常强大,当想要快速轻松地提取深度嵌套的数据时,无需instanceof检查和繁琐的类型转换。

当需要一组有限的替代方案可供选择时,会想到什么? Java枚举符合这个要求;它们由一组静态变体组成,不能更改它们内部包含的数据。

上面的枚举定义了三种颜色:红色、绿色和蓝色,并为各个字段设置了值,并且不可能在不弄乱使用该枚举的每个地方的情况下更改其中的颜色值(这些值是最终的)在上面的代码中,但想象一下如果不是的话)。

现在,想象一个不同的问题。需要不同的颜色表示形式,例如RGB、HSL和CMYK。也许只是为其创建一个枚举?

这提供了一组很好的、有限的可供选择的值。但使用起来比较麻烦;如果想为不同的表示形式使用多个颜色值,则需要单独存储实际的颜色数据并保留 ColorRepresentation里面的枚举值。

显然,这不是任何了解Java的人都会设计的方式Color类。在不牺牲可读性的情况下实现多种颜色表示的更好方法是使用多态性。

现在,给定一个Color实例,只需要检查它是否是instanceof想要的颜色表示形式,并且可以从该表示形式访问数据。但该实现有一个缺陷。如何限制类层次结构中的颜色呢?类中任何用户都可以创建新的RYB或者RGB继承自的类Color, 例如 当库不需要任何新的变体时,这就会成为一个问题Color存在或者如果它不期望具体 Color变体来改变他们的行为。除非API设计为可扩展的,否则创建新的表示形式在最好的情况下可能会导致崩溃或者其他一些微妙的错误。

为了解决这个问题,可以做一些事情:

所有变体final。虽然这有帮助,但并不排除直接从Color,因为这是不可能的Color本身就是最终的。

确保所有内部逻辑对于无法识别的变体始终具有默认情况。这将降低库的可扩展性,但如果这不是目标,它会有所帮助。

但这也更容易出错; 如果即使逻辑的一部分忘记考虑坏情况,也会出现问题。

或者,

使用sealed类或接口,一石二鸟。

Sum类型

Java 17的sealed类支持基于Sum类型概念的设计模式。如果乘积类型的值范围是其组成类型的值范围的乘积,则求和类型的值范围是sum。从名字上就很明显了。但是值的范围是一个总和意味着什么?

总和类型编码一个类型可以任何一个同时是其组成部分。它们也称为标记联合类型,因为在类型理论中,它们通常表示为一种类型,其值范围是其组件的联合集,其中每个组件类型都用标签“标记”。

使用伪类型理论符号,可以表达这样的总和类型:

T = A + B + C

T中的值集可以表示为:

T = { x | x ∈ A ⋃ B ⋃ C }

可能会想起C的unions当听到联合类型一词时。

MyUnion由三种组件类型组成,并且C允许处理一个值MyUnion作为这三种类型中任何一种的类型

请注意,联合的值在第二次赋值中被覆盖doubleValue。如果在第二次赋值后要打印myUnion.intValue就会乱码(字节doubleValue被切成两半并解释为整数)。

这暴露了CUnion中最显著的缺陷;在没有外部信息的情况下,没有内置方法可以知道联合值中包含哪个变体。因此,需要一个外部判别器来确定里面的内容。Java 的多态性使用instanceof可以为做到这一点。但Java的类层次结构过于开放;没有办法限制Java“联合”的变体数量。

我们需要的是标记的union,这正是sealed类型允许表示的。sealed修饰符的存在是为了清楚地表明不能将密封类扩展到允许从其继承的类之外。这允许开发人员控制用户如何与其库的API交互。

注意sealed类Color的语法。有一个sealed修饰符和一个permits包含所有子类名称的子句Color。permits用于指定哪些类可以从特定类继承,并用于防止任何不需要的继承。另请注意,每个实施Color被标记为final,因此只能获得此处看到的四种颜色表示;

这个类的层次结构是被锁定的。没有新的类可以继承Color,无论它们是否位于同一个包中。

如果类层次结构以sealed类,必须将所有继承者(直接或间接)标记为 sealed, non-sealed,或者final。如果继承类没有这些修饰符,则会出现编译错误。

以下是每个修饰符的含义

sealed- 除非后面提到继承者的名字,否则该类不能被继承permits。

non-sealed- 类可以正常继承。 有助于控制自定义行为的范围。

final- 类是继承树中的“叶子”;不能再继续继承延长。

用这组修饰符可用于精确控制API类的行为方式及其使用方式。这避免了这样的情况:唯一阻止一切规则只能由同意不做错误事情的开发人员来执行。

想象一下,正在编写代码来根据颜色的格式处理颜色,已经有一个很好的、有限的方法来确保不会发生有趣的事情。但是如何构建处理密封类实例的代码呢?

早期的Java版本没有选择,只能使用if-else样式:

如果我们使用Java 16+,可以做得更好一点if模式匹配:

但是,像Rust那样打开颜色变量并同时提取内容不是很好吗?

在Rust可以很优雅的使用:

请注意,这些颜色值在match上面的块。如何通过密封类来完成这个任务,具有解构的切换模式匹配仅适用于记录,并且记录不能从任何类继承。

解决方案是仅使用sealed接口。它们的工作方式与sealed类完全相同,只是记录和枚举也可以实现。

下面代码巧妙地进行模式匹配并从Color实例,如果只想从类中获取数据而不调用其任何方法:

Java 21允许switch现在块和表达式在使用null,所以无需预先检查 null在。

可能会注意到在这里没有使用默认情况。Java通常会引发错误,指出尚未涵盖所有情况。但Color是一个sealed类,Java可以知道每种情况都已被处理。

Guard 子句允许在switch语句和表达式中优雅地表达复杂的条件,而嵌套if条件。

考虑一种情况,需要特殊情况RGB颜色,其中red> 200是真的:

这并不是最丑陋的事情,但从长远来看,它仍然意味着最终会嵌套更多的代码。通常,解析垂直延伸的代码比水平延伸的代码更容易,因为需要跟踪的范围较少。

Java 21允许将该条件使用when关键词集成到case标签中。

Java仍然会首先评估true的情况,因此请确保首先放置更具体的情况(受保护或不受保护),然后是不太具体的情况。

exceptions

要处理一类新的异常。java.lang.MatchException。当模式匹配出错时会发生什么? 考虑不良记录 getter 实现的情况:

上面的开关块会抛出一个MatchException因为当调用i的getter时,ArithmeticException被抛出。

如果没有指定的变体可以与选择器匹配,则详尽的switch块将抛出异常。

A MatchException如果保护子句在执行时引发异常,也会抛出异常。

可以很容易地静态地确定这里存在被零整除的错误。然而,如果被整除是动态的(可能除数为零),情况可能会更加混乱。

总结

在本文中,研究了Java 21中带来的switch 模式以及一些优雅的用法。当然这只是Java 21中很小一个部分功能,更多的功能,可以看其现场发布会或者后续发布文档。

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OdgbHynZ2jzzP7OZEQjClQXg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券