当前位置: 首页 > 后端技术 > Java

Java缺失的功能:扩展方法

时间:2023-04-01 17:38:15 Java

什么是扩展方法扩展方法能够将方法直接“添加”到现有类型,而无需创建新的派生类型、重新编译或以其他方式修改现有类型。调用扩展方法时,与调用在类型上实际定义的方法相比没有明显区别。为什么需要扩展方法来考虑实现这样的功能:从Redis中取出一个包含多个商品ID的字符串后(每个商品ID用逗号隔开),先对商品ID进行去重(并且能够保持顺序)theelements),最后用英文逗号连接各个产品ID。//"123,456,123,789"Stringstr=redisService.get(someKey)传统写法:StringitemIdStrs=String.join(",",newLinkedHashSet<>(Arrays.asList(str.split(","))));使用Stream写法:StringitemIdStrs=Arrays.stream(str.split(",")).distinct().collect(Collectors.joining(","));假设Java可以实现扩展方法,我们给数组添加扩展方法toList(把数组变成List),为List添加扩展方法toSet(把List变成LinkedHashSet),添加扩展Collection的join方法(使用给定的链接器将集合连接中的元素转换成字符串形式),那么我们就可以这样写代码:StringitemIdStrs=str.split(",").toList()。toSet().join(",");相信此时你已经有了为什么需要扩展方法的答案:你可以直接增强现有的类库,而不是使用工具类。与使用工具类相比,使用类型本身的方法写代码更流畅、更舒服。代码更易读,因为是链式调用而不是static方法嵌套套娃如何在Java中实现扩展方法先问一下最近流行的ChatGPT:嗯,ChatGPT认为Java中的扩展方法是工具类提供的静态方法:).所以接下来要介绍一个全新的黑科技:Manifold准备条件Manifold的原理和Lombok类似,也是在编译时通过注解处理器进行处理。所以要在IDEA中正确使用Manifold,需要安装ManifoldIDEA插件:然后在项目pom的maven-compiler-plugin中添加annotationProcessorPaths:...2023.1.3系统。manifoldmanifold-ext${manifold.version}...org.apache.maven.pluginsmaven-compiler-plugin3.8.188UTF-8-Xplugin:Manifoldno-bootstrapsystems.manifold歧管扩展<版本>${manifold.version}如果项目中使用了Lombok,需要在annotationProcessorPaths中添加Lombok:org.projectlomboklombok${lombok.version}systems.manifoldmanifold-ext<版本>${manifold.version}写JDK的扩展方法,String的split方法使用字符串作为参数,即String[]split(String)现在我们给String加上扩展方法String[]split(char):按给定字符拆分。基于Manifold,编写扩展方法:packagecom.alibaba.zhiye.extensions.java.lang.String;importmanifold.ext.rt.api.Extension;importmanifold.ext.rt.api.This;importorg.apache。commons.lang3.StringUtils;/***String的扩展方法*/@ExtensionpublicfinalclassStringExt{publicstaticString[]split(@ThisStringstr,charseparator){returnStringUtils.split(str,separator);}}可以发现本质上是工具类的静态方法,但是有一些要求:工具类需要使用Manifold的@Extension注解静态方法,目标类型的参数需要使用@This注解工具类的包名,需要以extensions.target类型的完全限定类名结尾——用过C#的同学应该会心一笑,这是C#模仿的扩展方法。关于第3点,之所以有这个需求,是因为Manifold希望能够在项目中快速找到扩展方法,避免对项目中的所有类扫描注解,提高处理效率。有了扩展方法的能力,我们现在可以这样称呼它:太棒了!您会发现System.out.println(numStrs.toString())实际上打印的是数组对象的字符串形式——而不是数组对象的地址。查看反编译的App.class,发现扩展方法调用换成了静态方法调用:而数组的toString方法使用了Manifold为数组定义的扩展方法ManArrayExt.toString(@ThisObjectarray):[Ljava.lang.String;@511d50c0什么的,再见了,再也见不到了~因为扩展方法的调用在编译期被静态方法的调用代替了,所以使用Manifold的扩展方法,即使调用方法的对象为null,没有问题,因为处理后的代码将null作为参数传递给了对应的静态方法。比如我们扩展Collection:packagecom.alibaba.zhiye.extensions.java.util.Collection;importmanifold.ext.rt.api.Extension;importmanifold.ext.rt.api.This;importjava.util.Collection;/***Collection的扩展方法*/@ExtensionpublicfinalclassCollectionExt{publicstaticbooleanisNullOrEmpty(@ThisCollectioncoll){returncoll==null||col.isEmpty();}}然后调用时:Listlist=getSomeNullableList();//如果list为null,则进入if块,不会触发空指针异常if(list.isNullOrEmpty()){//TODO}java.lang.NullPointerException,再见,再见~数组扩展方法在JDK中,数组没有具体对应的类型,那么为数组定义的扩展类应该放在什么包中呢?查看ManArrayExt的源码,发现Manifold提供了一个类manifold.rt.api.Array来表示数组。比如ManArrayExt中为数组提供的toList方法:我们看到List<@Self(true)Object>的写法:@Self用来表示注解的值应该是什么类型,如果是@Self,即,@Self(false),表示注解的值和@This注解是同一类型;@Self(true)表示它是数组中元素的类型。对于对象数组,我们可以看到toList方法返回的是对应的List(T是数组元素的类型):但是如果是基本类型的数组,IDEA指示的返回值是:但是我用的是Java,擦除泛型怎么可能有List这么棒的功能——所以你只能使用原生类型来接收这个返回值:)——我希望Valhalla项目能尽快成为GA。我们在各种项目中经常看到大家先把一个对象包装成一个Optional,然后进行filter,map等,通过@Self的类型映射,可以像这样给Object添加一个非常实用的方法:packagecom.alibaba。zhiye.extensions.java.lang.Object;importmanifold.ext.rt.api.Extension;importmanifold.ext.rt.api.Self;importmanifold.ext.rt.api.This;importjava.util.Optional;/***Object的扩展方法*/@ExtensionpublicfinalclassObjectExt{publicstaticOptional<@SelfObject>asOpt(@ThisObjectobj){returnOptional.ofNullable(obj);}}那么任何对象都会有asOpt()方法。对比之前不自然的需要包裹起来:Optional.ofNullable(someObj).filter(someFilter).map(someMapper).orElseGet(someSupplier);你现在可以自然地使用Optional:someObj.asOpt().filter(someFilter).map(someMapper).orElseGet(someSupplier);当然,Object是所有类的父类。这样做是否合适,需要慎重考虑。扩展静态方法我们都知道Java9给集合增加了工厂方法:Listlist=List.of("a","b","c");Setset=Set.of("a","b","c");Mapmap=Map.of("a",1,"b",2,"c",3);因为如果你不是用Java9及以上版本(Java8:报一下我的身份证),就得用Guava之类的库——不过ImmutableList.of毕竟没有List.of那么正统。自然。没关系,Manifold说:“没关系,我来做”。基于Manifold扩展静态方法就是在扩展类的静态方法中添加@Extension:packagecom.alibaba.zhiye.extensions.java.util.List;importmanifold.ext.rt.api.Extension;importmanifold.ext.rt.api.This;importjava.util.Arrays;importjava.util.Collections;importjava.util.List;/***列表扩展方法*/@ExtensionpublicfinalclassListExt{/***只返回包含一个元素的不可变列表*/@ExtensionpublicstaticListof(Eelement){returnCollections.singletonList(element);}/***返回一个包含多个元素的不可变列表*/@Extension@SafeVarargspublicstaticListof(E...elements){returnCollections.unmodifiableList(Arrays.asList(elements));然后你可以欺骗自己使用后Java8版本——你可以随意发送,我使用的是Java8。BTW,因为Object是所有类的父类,如果给Object加上一个静态扩展方法,就意味着你可以在任何地方直接访问这个静态方法而无需import——恭喜你,你解锁了“顶层功能”。关于Manifold的建议,我是2019年开始关注Manifold的,那时候ManifoldIDEA插件还是收费的,所以当时只是做了简单的尝试。最近在看,IDEA插件是完全免费的,迫不及待的想好好利用一下。目前我在一个项目中使用Manifold实现了扩展方法的功能——相关人士说很上瘾,离不开它。如果您有使用上的建议和问题,欢迎与我讨论。目前Aone的扫码插件不支持Manifold,扫码会失败——所以如果确定要用Manifold,可以找扫码插件相关的同学(你赢了),先关闭您项目的Aone应用程序的代码扫描。谨慎添加扩展方法如果你决定在你的项目中使用Manifold实现扩展方法,那么我们一定要“管住手”。首先,如上所述,在为Object或其他项目中广泛使用的类添加扩展方法时,必须非常谨慎。最好和项目组的同学一起商量,让大家一起决定,不然就容易了。混淆(发誓)。另外,如果要给某个类增加扩展方法,首先要仔细思考一个问题:“这个方法的逻辑是否在这个类的职责范围内,有没有混入业务自定义逻辑?”?”例如下面的方法(判断给定的字符串是否为合法参数):publicstaticbooleanisValidParam(Stringstr){returnStringUtils.isNotBlank(str)&&!"null".equalsIgnoreCase(str);}显然,isValidParam是不属于String类的职责范围,isValidParam应该继续放在XxxBizUtils中。当然,如果你把方法名改成isNotBlankAndNotEqualsIgnoreCaseNullLiteral,那也没关系:)——但我劝你不要这样做,很容易中招。