1. 简介

Derive4J 是一个注解处理器,它为 Java 8 引入了多种函数式编程的概念。

在本篇文章中,我们将介绍 Derive4J,并重点讲解它支持的几个核心概念:

  • 代数数据类型(Algebraic Data Types)
  • 结构化模式匹配(Structural Pattern Matching)
  • 首类惰性(First Class Laziness)

2. Maven 依赖配置

要使用 Derive4J,我们需要在项目中添加如下 Maven 依赖

<dependency>
    <groupId>org.derive4j</groupId>
    <artifactId>derive4j</artifactId>
    <version>1.1.0</version>
    <optional>true</optional>
</dependency>

⚠️ 注意:该依赖是 optional 的,因为它是一个注解处理器,只在编译期生效。

3. 代数数据类型(ADT)

3.1. 描述

代数数据类型(Algebraic Data Types,简称 ADT)是一种复合类型 —— 它由其他类型或泛型组合而成。

ADT 主要分为两类:

  • Sum Type(和类型)
  • Product Type(积类型)

这类类型在 Haskell、Scala 等语言中是原生支持的,而在 Java 中需要借助 Derive4J 等工具来模拟。

3.2. 和类型(Sum Type)

✅ 和类型表示“逻辑或”的关系:它可以是这个,也可以是那个,但不能同时是两者。

在 Java 中,最接近和类型的是 enum,但它有个致命缺陷:无法携带额外的数据

而 ADT 的优势就在于:每个“case”都可以携带不同类型的数据。

3.3. 积类型(Product Type)

✅ 积类型表示“逻辑与”的关系:它是多个值的组合。

Java 中的类(class)就可以看作是积类型,因为它的状态是由多个字段共同组成的。

3.4. 使用示例

常见的 ADT 之一是 Either 类型,它比 Optional 更强大,可以用于处理成功或失败两种情况。

我们通过一个接口定义 ADT:

@Data
interface Either<A, B> {
    <X> X match(Function<A, X> left, Function<B, X> right);
}

📌 注解 @Data 告诉 Derive4J 自动生成代码,包括工厂方法、匹配器等。

默认情况下,生成的类名是接口名的复数形式(如 Eithers),但我们可以通过 inClass 参数自定义。

创建一个 Either 实例并验证其行为:

public void testEitherIsCreatedFromRight() {
    Either<Exception, String> either = Eithers.right("Okay");
    Optional<Exception> leftOptional = Eithers.getLeft(either);
    Optional<String> rightOptional = Eithers.getRight(either);
    Assertions.assertThat(leftOptional).isEmpty();
    Assertions.assertThat(rightOptional).hasValue("Okay");
}

使用 match() 方法进行模式匹配:

public void testEitherIsMatchedWithRight() {
    Either<Exception, String> either = Eithers.right("Okay");
    Function<Exception, String> leftFunction = Mockito.mock(Function.class);
    Function<String, String> rightFunction = Mockito.mock(Function.class);
    either.match(leftFunction, rightFunction);
    Mockito.verify(rightFunction, Mockito.times(1)).apply("Okay");
    Mockito.verify(leftFunction, Mockito.times(0)).apply(Mockito.any(Exception.class));
}

4. 模式匹配(Pattern Matching)

✅ 模式匹配是 ADT 的一个重要特性,它允许我们根据值的结构执行不同的逻辑。

我们可以把它理解为一个更强大的 switch,没有类型限制,也不要求常量匹配。

下面通过一个 HTTP 请求的例子来演示模式匹配的使用。

首先定义一个 ADT 来表示 HTTP 请求方法:

@Data
interface HTTPRequest {
    interface Cases<R>{
        R GET(String path);
        R POST(String path);
        R PUT(String path);
        R DELETE(String path);
    }

    <R> R match(Cases<R> method);
}

生成的类 HttpRequests 提供了 caseOf() 方法来实现模式匹配。

接着定义一个简单的响应类:

public class HTTPResponse {
    int statusCode;
    String responseBody;

    public HTTPResponse(int statusCode, String responseBody) {
        this.statusCode = statusCode;
        this.responseBody = responseBody;
    }
}

然后创建一个 HTTP 服务器类,根据请求类型返回不同的响应:

public class HTTPServer {
    public static String GET_RESPONSE_BODY = "Success!";
    public static String PUT_RESPONSE_BODY = "Resource Created!";
    public static String POST_RESPONSE_BODY = "Resource Updated!";
    public static String DELETE_RESPONSE_BODY = "Resource Deleted!";

    public HTTPResponse acceptRequest(HTTPRequest request) {
        return HTTPRequests.caseOf(request)
          .GET((path) -> new HTTPResponse(200, GET_RESPONSE_BODY))
          .POST((path,body) -> new HTTPResponse(201, POST_RESPONSE_BODY))
          .PUT((path,body) -> new HTTPResponse(200, PUT_RESPONSE_BODY))
          .DELETE(path -> new HTTPResponse(200, DELETE_RESPONSE_BODY));
    }
}

测试方法:

@Test
public void whenRequestReachesServer_thenProperResponseIsReturned() {
    HTTPServer server = new HTTPServer();
    HTTPRequest postRequest = HTTPRequests.POST("http://test.com/post", "Resource");
    HTTPResponse response = server.acceptRequest(postRequest);
    Assert.assertEquals(201, response.getStatusCode());
    Assert.assertEquals(HTTPServer.POST_RESPONSE_BODY, response.getResponseBody());
}

5. 首类惰性(First Class Laziness)

Derive4J 支持“首类惰性” —— 对象不会立即初始化,直到我们真正访问它。

定义一个支持惰性的接口:

@Data(value = @Derive(
  inClass = "{ClassName}Impl",
  make = {Make.lazyConstructor, Make.constructors}
))
public interface LazyRequest {
    interface Cases<R>{
        R GET(String path);
        R POST(String path, String body);
        R PUT(String path, String body);
        R DELETE(String path);
    }

    <R> R match(LazyRequest.Cases<R> method);
}

验证惰性构造是否生效:

@Test
public void whenRequestIsReferenced_thenRequestIsLazilyContructed() {
    LazyRequestSupplier mockSupplier = Mockito.spy(new LazyRequestSupplier());
    LazyRequest request = LazyRequestImpl.lazy(() -> mockSupplier.get());
    Mockito.verify(mockSupplier, Mockito.times(0)).get();
    Assert.assertEquals(LazyRequestImpl.getPath(request), "http://test.com/get");
    Mockito.verify(mockSupplier, Mockito.times(1)).get();
}

class LazyRequestSupplier implements Supplier<LazyRequest> {
    @Override
    public LazyRequest get() {
        return LazyRequestImpl.GET("http://test.com/get");
    }
}

6. 总结

本文介绍了 Derive4J 库,并展示了如何使用它在 Java 中实现一些函数式编程的核心概念,如代数数据类型、模式匹配和惰性求值。

更多细节可以参考 Derive4J 官方文档

代码示例可在 GitHub 仓库 获取。


原始标题:Intro to Derive4J | Baeldung