Java 缺失的特性:扩展方法

什么是扩展方法

扩展方法,就是能够向现有类型直接“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改现有类型。调用扩展方法的时候,与调用在类型中实际定义的方法相比没有明显的差异。

为什么需要扩展方法

考虑要实现这样的功能:从 Redis 取出包含多个商品ID的字符串后(每个商品ID使用英文逗号分隔),先对商品ID进行去重(并能够维持元素的顺序),最后再使用英文逗号将各个商品ID进行连接。

传统写法:

使用 Stream 写法:

假设在 Java 中能实现扩展方法,并且我们为数组添加了扩展方法 toList(将数组变为 List),为 List 添加了扩展方法 toSet(将 List 变为 LinkedHashSet),为 Collection 添加了扩展方法 join(将集合中元素的字符串形式使用给定的连接符进行连接),那我们将可以这样写代码:

相信此刻你已经有了为什么需要扩展方法的答案:

可以对现有的类库,进行直接增强,而不是使用工具类

相比使用工具类,使用类型本身的方法写代码更流畅更舒适

代码更容易阅读,因为是链式调用,而不是用静态方法套娃

在 Java 中怎么实现扩展方法

我们先来问问最近大火的 ChatGPT:

好吧,ChatGPT 认为 Java 里面的扩展方法就是通过工具类提供的静态方法 :)。所以接下来我将介绍一种全新的黑科技:

Manifold(https://github.com/manifold-systems/manifold)

准备条件

Manifold 的原理和 Lombok 是一样的,也是在编译期间通过注解处理器进行处理。所以要在 IDEA 中正确使用 Manifold,需要安装 Manifold IDEA 的插件:

然后再在项目 pom 的 maven-compiler-plugin 中加入 annotationProcessorPaths:

如果你的项目中使用了 Lombok,需要把 Lombok 也加入 annotationProcessorPaths:

编写扩展方法

JDK 中,String 的 split 方法,使用的是字符串作为参数,即 String[] split(String)。我们现在来为 String 添加一个扩展方法 String[] split(char):按给定的字符进行分割。

基于 Manifold,编写扩展方法:

可以发现本质上还是工具类的静态方法,但是有一些要求:

工具类需要使用 Manifold 的 @Extension 注解

静态方法中,目标类型的参数,需要使用 @This 注解

工具类所在的包名,需要以 extensions.目标类型全限定类名 结尾

—— 用过 C# 的同学应该会会心一笑,这就是模仿的 C# 的扩展方法。

关于第 3 点,之所以有这个要求,是因为 Manifold 希望能快速找到项目中的扩展方法,避免对项目中所有的类进行注解扫描,提升处理的效率。

具备了扩展方法的能力,现在我们就可以这样调用了:

Amazing!而且你可以发现,System.out.println(numStrs.toString()) 打印的居然是数组对象的字符串形式 —— 而不是数组对象的地址。查看反编译后的 App.class,发现是将扩展方法的调用,替换为静态方法调用:

而数组的 toString 方法,使用的是 Manifold 为数组定义的扩展方法 ManArrayExt.toString(@This Object array):

图片正在加载中,请稍后

[Ljava.lang.String;@511d50c0 什么的,Goodbye,再也不见~

因为是在编译期将扩展方法的调用替换为静态方法调用,所以使用 Manifold 的扩展方法,即使调用方法的对象是 null 也没有问题,因为处理后的代码是把 null 作为参数传递到对应的静态方法。比如我们对 Collection 进行扩展:

然后调用的时候:

java.lang.NullPointerException,Goodbye,再也不见~

数组扩展方法

图片正在加载中,请稍后

我们看到 List<@Self(true) Object> 这样的写法:@Self 是用来表示被注解的值应该是什么类型,如果是 @Self,即 @Self(false),表示被注解的值和 @This 注解的值是同一个类型;@Self(true) 则表示是数组中元素的类型。

对于对象数组,我们可以看到 toList 方法返回的就是对应的 List(T 为数组元素的类型):

图片正在加载中,请稍后

但如果是原始类型数组,IDEA 指示的返回值是:

图片正在加载中,请稍后

但是我用的是 Java 啊,擦除法泛型怎么可能拥有 List 这么伟大的功能 —— 所以你只能用原生类型来接收这个返回值 :)

图片正在加载中,请稍后

—— 许个愿:Project Valhalla 会在 Java21 中 GA。

我们经常在各个项目中看到,大家先把某个对象包装成 Optional,然后进行 filter、map 等。通过 @Self 的类型映射,你可以这样为 Object 加入一个非常实用的办法:

那么任何对象,都将拥有 asOpt() 方法。

相比于之前的需要包装一下的不自然:

你现在可以自然而然的使用 Optional:

当然,Object 是所有的类的父类,这样做是否合适,还是需要谨慎的思考一下。

扩展静态方法

我们都知道 Java9 给集合添加了工厂方法:

是不是很眼馋?因为如果用的不是 Java9 及以上版本(Java8:直接报我身份证就行),你就得用 Guava 之类的库 —— 然而 ImmutableList.of 用起来终究是比不上 List.of 这样的正统来的自然。

没关系,Manifold 说:“无所谓,我会出手”。基于 Manifold 扩展静态方法,就是在扩展类的静态方法上,也加上 @Extension:

然后你就可以欺骗自己已经用上了 Java8 之后的版本 —— 你发任你发,我用 Java8。

BTW,因为 Object 是所有类的父类,如果你给 Object 添加静态扩展方法,那么意味着你可以在任何地方直接访问到这个静态方法,而不需要 import —— 恭喜你,解锁了 “顶级函数”。

建议

我从 2019 年开始关注 Manifold,那时候 Manifold IDEA 插件还是收费的,所以当时只是做了简单的尝试。最近再看,IDEA 插件已经完全免费,所以迫不及待地想要物尽其用。目前我已经在一个项目中使用了 Manifold 来实现扩展方法的功能 —— 当事人表示非常上瘾,已经离不开了。如果你有使用上的建议和疑问,欢迎和我一起讨论。

谨慎添加扩展方法

如果决定在项目中使用 Manifold 实现扩展方法,那么我们一定要做到 “管住自己的手”

首先,就是上文说的,给 Object 或者其他在项目中使用非常广泛的类添加扩展方法,一定要非常的慎重,最好是要和项目组的同学一起讨论,让大家一起决定,否则很容易让人迷惑。

另外,如果要给某个类添加扩展方法,一定要先认真思考一个问题:“这个方法的逻辑是不是在这个类的职责范围内,是否有掺杂业务自定义逻辑”。例如下面这个方法(判断给定的字符串是不是一个合法的参数):

很明显,isValidParam 不是 String 这个类的职责范围,应该把 isValidParam 继续放在 XxxBizUtils 里面。当然,如果你把方法名改成 isNotBlankAndNotEqualsIgnoreCaseNullLiteral,那是可以的 :) —— 不过劝你别这么做,容易被打。

标签: 代码 java

最新资讯

文档百科

土上有竹林,土下一寸金,猜一个字|字谜及答案

老资料网之小知识:猜谜方法的具体玩法.谜语的猜法多种多样,比较常见的有二十多种。

杨国忠外出一年多,妻子以外怀孕他居然不生气?

当了太后的芈月空闺寂寞冷,就与英俊潇洒的义渠君勾搭在了一起,孤男寡女,干柴遇烈火,后来芈月不小心怀了孕。杨国忠出差之后不到一个月,这裴氏就按捺不住了,浑身燥热难耐,后来她就与隔壁老王大哥搞上了,而且还不小心怀孕了。杨国忠略一琢磨,感觉不对,我一年没回来,老婆怀孕七个月,这时间不对啊,莫非我被绿了?杨国忠一把抱起了老婆!

钓马口用什么鱼钩最好,用什么假饵合适

钓马口鱼用伊势尼钩最好。钓马口鱼之前需要准备工具,鱼钩就是其中之一,需要根据马口鱼的体型以及嘴巴大小决定。马口鱼的体型不大,嘴巴也不大,不必用太大的钩。在钓马口鱼的时候,可以采取路亚钓这种方式,因而也可以用假饵这类饵料。

画圣吴道子为什么要断手:没有自断手臂(电影中剧情)

不过历史上的吴道子并没有砍断自己的手,这些都是电影中虚构出来的情节而已,所以并不是真实的。

云南天文台发展了求解辐射转移过程的新方法和计算程序

辐射转移过程是天体物理研究中最基本和最常见的物理过程,它不仅直接参与天体系统的演化,而且与观测结果直接相关,因此对其研究在理论和观测上都有重要意义。在求解辐射转移过程的各种方法中,蒙特卡罗方法是最重要、应用最为广泛的一类数值方法。

二维非厄米系统奇异点及费米点的Nielson-Nanomiya定理

过去的十多年,Nielson-Nanomiya定理在拓扑能带理论的发展过程中发挥了非常重要的作用,它保证布里渊区中拓扑荷总是成对出现的。如图1所示,Nielson-Nanomiya定理保证了这些拓扑荷在整个布里渊区中求和一定等于零。有趣的是,对拓扑材料来说,Nielson-Nanomiya定理可以在材料的表面被破坏,这个破坏恰好反应了材料的拓扑结构。

CopyRight © 2000~2023 一和一学习网 Inc.All Rights Reserved.
一和一学习网:让父母和孩子一起爱上学习