Java 使用 Lombok 的 @ExtensionMethod 注解实现向现有的类添加新的方法


文章目录
  • Java 使用 Lombok 的 @ExtensionMethod 注解实现向现有的类添加新的方法
  • 一、前言
  • 二、Dart的扩展方法(Extension Methods)
  • 示例
  • 三、Lombok的@ExtensionMethod注解
  • Java + Lombok
  • 1. 概述
  • 2. 什么是`@ExtensionMethod`?
  • 3. `@ExtensionMethod`如何工作?
  • 添加 Lombok 依赖
  • 示例:字符串反转
  • 示例:列表求和
  • 4. 结论
  • 四、其它示例代码


一、前言

我学习 Flutter 时发现 Dart 从2.7版本开始引入了扩展方法(Extension Methods)。扩展方法允许我们向现有的类添加新的方法,而无需修改原类或创建子类,这对于增强系统库类特别有用。当时我就想 Java 能否实现这种功能,后面也没想到自己实现的策略,直到今天我发现了Lombok的@ExtensionMethod注解!狂喜!

二、Dart的扩展方法(Extension Methods)

Dart从2.7版本开始引入了扩展方法(Extension Methods)。扩展方法允许我们向现有的类添加新的方法,而无需修改原类或创建子类。这对于增强系统库类特别有用!

示例

下面是一个简单的例子,演示如何为Dart的String类添加一个isPalindrome方法:

extension StringExtensions on String {
  bool isPalindrome() {
    return this == this.split('').reversed.join('');
  }
}

void main() {
  String str = "radar";
  print(str.isPalindrome());  // 输出: true
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

在这个例子中,我们通过使用extension关键字,为String类添加了一个isPalindrome方法。这样,我们就可以在不改变String类的情况下,直接调用这个新方法。

三、Lombok的@ExtensionMethod注解

Java + Lombok

第一个参数是对应的类型,便可支持直接调用!

1. 概述

Lombok是一个流行的Java库,通过减少样板代码来简化代码编写。其中一个强大的功能就是@ExtensionMethod注解,它可以增强代码的可读性和简洁性。

在本教程中,我们将深入探讨@ExtensionMethod注解是什么、如何工作,以及如何有效地使用它。

2. 什么是@ExtensionMethod

@ExtensionMethod注解允许我们向现有类添加静态方法扩展。这意味着我们可以将其他类中定义的方法作为原始类的一部分来调用。这对于增强第三方库或现有类的功能而不修改其源代码非常有用。

3. @ExtensionMethod如何工作?

要使用@ExtensionMethod,我们需要在类上添加@ExtensionMethod注解,并指定包含我们要扩展的静态方法的类。Lombok会生成必要的代码,使这些方法看起来像是被注解的类的一部分。

假设我们有一个工具类StringUtils,其中有一个方法reverse()用于反转字符串。我们希望使用这个方法,就像它是String类的方法一样。Lombok的@ExtensionMethod可以帮助我们实现这一点。

添加 Lombok 依赖

首先,我们需要将Lombok依赖添加到项目中。如果我们使用Maven,可以在pom.xml中添加以下内容:

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.24</version>
    <scope>provided</scope>
</dependency>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
示例:字符串反转

现在,让我们创建一个包含reverse()方法的StringUtils类:

public class StringUtils {
    public static String reverse(String str) {
        return new StringBuilder(str).reverse().toString();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

接下来,创建一个测试类并使用@ExtensionMethod注解:

import lombok.experimental.ExtensionMethod;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtensionMethod(StringUtils.class)
public class StringUtilsUnitTest {
    @Test
    public void givenString_whenUsingExtensionMethod_thenReverseString() {
        String original = "Lombok Extension Method";
        String reversed = original.reverse();
        assertEquals("dohteM noisnetxE kobmoL", reversed);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

在上述代码中,StringUtils类包含一个静态方法reverse(),该方法接收一个字符串并返回其反转版本。StringUtilsUnitTest类使用@ExtensionMethod注解,这告诉Lombok将StringUtils的静态方法视为其他类的扩展方法。在测试方法中,我们调用了original.reverse()

尽管String类没有reverse()方法,但由于StringUtils有一个静态方法reverse(),Lombok允许这种调用。

如果我们查看Lombok生成的类,Lombok会在编译过程中将original.reverse()调用重写为StringUtils.reverse(original)。这种转换表明original.reverse()是Lombok提供的语法糖,以增强代码可读性:

private static String reverse(String str) {
    return StringUtils.reverse(str);
}
  • 1.
  • 2.
  • 3.

让我们也看看测试用例,并看看Lombok转换了哪一部分:

@Test
public void givenString_whenUsingExtensionMethod_thenReverseString() {
    String original = "Lombok Extension Method";
    String reversed = reverse(original); 
    assertEquals("dohteM noisnetxE kobmoL", reversed);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

Lombok转换了reversed变量赋值的那部分代码。

假设我们在上述示例中不使用@ExtensionMethod注解。在这种情况下,我们需要直接从工具类调用工具方法,使代码更加冗长且不直观。

public class StringUtilsWithoutAnnotationUnitTest {
    @Test
    public void givenString_whenNotUsingExtensionMethod_thenReverseString() {
        String original = "Lombok Extension Method";
        String reversed = StringUtils.reverse(original);
        assertEquals("dohteM noisnetxE kobmoL", reversed);
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

在上述代码中,我们使用StringUtils来调用reverse()方法。

示例:列表求和

让我们创建一个使用列表的示例,并演示如何使用@ExtensionMethod注解来添加操作列表对象的工具方法。

我们将创建一个包含sum()方法的工具类ListUtils,该方法计算整数列表中所有元素的和。然后,我们将使用@ExtensionMethod注解将此方法用作List类的一部分:

public class ListUtils {
    public static int sum(List<? extends Number> list) {
        return list.stream().mapToInt(Number::intValue).sum();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

现在,让我们看看相应的测试类,以测试我们的sum()方法对整数和双精度类型的效果:

import lombok.experimental.ExtensionMethod;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.Arrays;
import java.util.List;

@ExtensionMethod(ListUtils.class)
public class ListUtilsUnitTest {
    @Test
    public void givenIntegerList_whenUsingExtensionMethod_thenSum() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        int total = numbers.sum();
        assertEquals(15, total, "The sum of the list should be 15");
    }

    @Test
    public void givenDoubleList_whenUsingExtensionMethod_thenSum() {
        List<Double> numbers = Arrays.asList(1.0, 2.0, 3.0);
        int total = numbers.sum();
        assertEquals(6, total, "The sum of the list should be 6");
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

测试类使用@ExtensionMethod(ListUtils.class)注解将sum()视为List类的扩展方法。这允许直接调用numbers.sum()

这也突显了泛型在Lombok中如何完全应用于确定扩展方法。这使得@ExtensionMethod注解能够与特定类型的集合一起工作。

4. 结论

在本文中,我们看到,通过使用Lombok的@ExtensionMethod注解,我们可以在不修改源代码的情况下增强现有类的功能。这使得我们的代码更具表达性且更易于维护。示例展示了如何将自定义工具方法应用于字符串和列表。我们可以将相同的原理应用于任何类和任何一组静态方法,为我们的Java项目提供了极大的灵活性。

四、其它示例代码

package cn.com.mfish.web.main;

import java.io.IOException;
import java.util.List;
import lombok.experimental.ExtensionMethod;

/**
 * @author zibo
 * @date 2024/8/11 下午1:21
 * @slogan 慢慢学,不要停。
 */
@ExtensionMethod({
    StringUtils.class, ListUtils.class
})
public class Main {

    public static void main(String[] args) throws IOException {
        String a = "Hello, ";
        String b = "World!";
        System.out.println(a.add(b));

        System.out.println(a.log());

        a.myPrint("==============", b);

        List<String> list = List.of("a", "b", "c");
        list.printList();
    }

}

class StringUtils {

    public static String add(String a, String b) {
        return a + b;
    }

    public static String log(String a) {
        return "log: " + a;
    }

    public static void myPrint(String a, String b, String c) {
        System.out.println(a + b + c);
    }

}

class ListUtils {

    public static void printList(List<?> list) {
        list.forEach(System.out::println);
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.