【Java】扩展方法(java9以上才能享受的)

让java8也能用上扩展函数

什么是扩展方法

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

为什么需要扩展方法

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

// "123,456,123,789"
String str = redisService.get(someKey)

传统写法

String itemIdStrs = String.join(",", new LinkedHashSet<>(Arrays.asList(str.split(","))));

使用 Stream 写法:

String itemIdStrs = Arrays.stream(str.split(",")).distinct().collect(Collectors.joining(","));

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

String itemIdStrs = str.split(",").toList().toSet().join(",");

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

  • 可以对现有的类库,进行直接增强,而不是使用工具类
  • 相比使用工具类,使用类型本身的方法写代码更流畅更舒适
  • 代码更容易阅读,因为是链式调用,而不是用静态方法套娃

在 Java 中怎么实现扩展方法

黑科技登场 manifold

准备条件

Manifold 的原理和 Lombok 是类似的(还有一个类似的是mapstruct,用来进行实体转换的),也是在编译期间通过注解处理器进行处理。所以要在 IDEA 中正确使用 Manifold,需要安装 Manifold IDEA 的插件:
在这里插入图片描述
然后再在项目 pom 的 maven-compiler-plugin 中加入 annotationProcessorPaths:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    ...
  
    <properties>
        <manifold.version>2022.1.19</manifold.version>
    </properties>
  
    <dependencies>
        <dependency>
            <groupId>systems.manifold</groupId>
            <artifactId>manifold-ext</artifactId>
            <version>${manifold.version}</version>
        </dependency>
        ...
    </dependencies>
  
    <!--Add the -Xplugin:Manifold argument for the javac compiler-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                    <encoding>UTF-8</encoding>
                    <compilerArgs>
                        <arg>-Xplugin:Manifold no-bootstrap</arg>
                    </compilerArgs>
                    <annotationProcessorPaths>
                        <path>
                            <groupId>systems.manifold</groupId>
                            <artifactId>manifold-ext</artifactId>
                            <version>${manifold.version}</version>
                        </path>
                        <!--如果使用了lombok,这段就需要加上-->
                        <path>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                            <version>1.18.16</version>
                        </path>
                    </annotationProcessorPaths>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

编写扩展方法

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

package manifoldsampleproject.extensions.java.lang.String;

import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.This;

import java.lang.String;

/**
 * MyStringExt
 *
 * @author Rex
 * @since 2023/7/20 14:10
 */
@Extension
public class MyStringExt {
  public static void echo( @This String thiz) {
    System.out.println(thiz + " rex");
  }
}

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

1、工具类需要使用 Manifold 的 @Extension 注解
2、静态方法中,目标类型的参数,需要使用 @This 注解
3、工具类所在的包名,需要以 extensions.目标类型全限定类名 结尾

用过 C# 的同学应该会会心一笑,这就是模仿的 C# 的扩展方法。
关于第 3 点,之所以有这个要求,是因为 Manifold 希望能快速找到项目中的扩展方法,避免对项目中所有的类进行注解扫描,提升处理的效率。所以包名的后缀,一定要是这个原始类型的,比如说要写一个List的扩展,则包名就是package manifoldsampleproject.extensions.java.util.List;,如果不是,写了也不生效
具备了扩展方法的能力,现在我们就可以这样调用了:

package com.rex;

/**
 * RunMe 测试类
 *
 * @author Rex
 * @since 2023/7/20 14:11
 */
public class RunMe {
  public static void main(String[] args) {
    test();
  }

  static void test() {
    String str = "123456";
    str.echo();
  }
}
// 123456 rex

数组扩展方法

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 这么伟大的功能 —— 所以你只能用原生类型来接收这个返回值 😃
在这里插入图片描述
—— 许个愿:Project Valhalla 会在 Java21 中 GA。
我们经常在各个项目中看到,大家先把某个对象包装成 Optional,然后进行 filter、map 等。通过 @Self 的类型映射,你可以这样为 Object 加入一个非常实用的办法:

package com.alibaba.zhiye.extensions.java.lang.Object;
import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.Self;
import manifold.ext.rt.api.This;
import java.util.Optional;

/**
 * Object 的扩展方法
 */
@Extension
public final class ObjectExt {
    public static Optional<@Self Object> asOpt(@This Object obj) {
        return Optional.ofNullable(obj);
    }
}

那么任何对象,都将拥有 asOpt() 方法。相比于之前的需要包装一下的不自然:

Optional.ofNullable(someObj).filter(someFilter).map(someMapper).orElseGet(someSupplier);

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

someObj.asOpt().filter(someFilter).map(someMapper).orElseGet(someSupplier);

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

扩展静态方法

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

List<String> list = List.of("a", "b", "c");
Set<String> set = Set.of("a", "b", "c");
Map<String, Integer> map = Map.of("a", 1, "b", 2, "c", 3);

是不是很眼馋?因为如果用的不是 Java9 及以上版本(Java8:直接报我身份证就行),你就得用 Guava 之类的库 —— 然而 ImmutableList.of 用起来终究是比不上 List.of 这样的正统来的自然。
没关系,Manifold 说:“无所谓,我会出手”。基于 Manifold 扩展静态方法,就是在扩展类的静态方法上,也加上 @Extension:

package com.alibaba.aladdin.app.extensions.java.util.List;
import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.This;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

/**
 * List 扩展方法
 */
@Extension
public final class ListExt {
    
    /**
     * 返回只包含一个元素的不可变 List
     */
    @Extension
    public static <E> List<E> of(E element) {
        return Collections.singletonList(element);
    }
    
    /**
     * 返回包含多个元素的不可变 List
     */
    @Extension
    @SafeVarargs
    public static <E> List<E> of(E... elements) {
        return Collections.unmodifiableList(Arrays.asList(elements));
    }
}

然后你就可以欺骗自己已经用上了 Java8 之后的版本 —— 你发任你发,我用 Java8。
在这里插入图片描述
这里发现一个问题,就是加完了对应的方法,是可以运行的,但是编译器显示爆红,然后给idea关闭了,再打开就好了😂

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

建议

关于 Manifold

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

谨慎添加扩展方法

如果决定在项目中使用 Manifold 实现扩展方法,那么我们一定要做到 “管住自己的手”。
首先,就是上文说的,给 Object 或者其他在项目中使用非常广泛的类添加扩展方法,一定要非常的慎重,最好是要和项目组的同学一起讨论,让大家一起决定,否则很容易让人迷惑(骂人)。
另外,如果要给某个类添加扩展方法,一定要先认真思考一个问题:“这个方法的逻辑是不是在这个类的职责范围内,是否有掺杂业务自定义逻辑”。例如下面这个方法(判断给定的字符串是不是一个合法的参数):

public static boolean isValidParam(String str) {
    return StringUtils.isNotBlank(str) && !"null".equalsIgnoreCase(str);
}

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Java 1.8和Java 15是Java编程语言的不同版本。Java 1.8,也被称为Java 8,是Java平台的第8个主要版本,它于2014年发布。Java 15,也被称为Java SE 15,是Java平台的第15个主要版本,它于2020年发布。 Java 1.8引入了许多重要的特性和改进。其中最显著的是Lambda表达式和函数式编程的支持,这使得编写简洁、高效的代码变得更加容易。它还引入了Stream API,这是一种用于处理集合数据的强大工具,可以实现并行处理和提高性能。此外,Java 1.8还引入了新的日期和时间API,提供了更好的日期和时间处理功能。 Java 15作为后续版本,引入了许多新特性和改进。其中之一是Sealed类,它可以限制其他类对其进行扩展,从而增加了代码的可维护性和安全性。Java 15还引入了Pattern Matching,这是一种增强的instanceof操作符,可以用更简洁的方式进行类型匹配和转换。此外,Java 15还改进了G1垃圾收集器和ZGC垃圾收集器,提高了性能和吞吐量。 总的来说,Java 1.8和Java 15都是Java平台的重要版本,它们各自引入了许多新特性和改进,为Java开发者提供了更多的选择和能力。使用Java 1.8可以享受Lambda表达式、Stream API和新的日期和时间API的优势,而使用Java 15则可以受益于Sealed类、Pattern Matching和改进的垃圾收集器等新功能。根据项目需求和个人喜好,开发者可以选择适合自己的版本。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

时光不等仁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值