1. Introduction
1.绪论
Derive4J is an annotation processor that enables various functional concepts in Java 8.
Derive4J是一个注释处理器,能够在Java 8中实现各种功能概念。
In this tutorial, we’ll introduce Derive4J and the most important concepts enabled by the framework:
在本教程中,我们将介绍Derive4J和该框架启用的最重要的概念。
- Algebraic data types
- Structural pattern matching
- First class laziness
2. Maven Dependency
2.Maven的依赖性
To use Derive4J, we need to include the dependency to our project:
为了使用Derive4J,我们需要将依赖性纳入我们的项目。
<dependency>
<groupId>org.derive4j</groupId>
<artifactId>derive4j</artifactId>
<version>1.1.0</version>
<optional>true</optional>
</dependency>
3. Algebraic Data Types
3.代数数据类型
3.1. Description
3.1.描述
Algebraic data types (ADTs) are a kind of composite type – they are combinations of other types or generics.
代数数据类型(ADT)是一种复合类型–它们是其他类型或泛型的组合。
ADTs generally fall into two main categories:
ADT一般分为两大类。
- sum
- product
Algebraic data types are present by default in many languages like Haskell and Scala.
代数数据类型在许多语言中都是默认存在的,比如Haskell和Scala。
3.2. Sum Type
3.2 总和类型
Sum is the data type representing the logical OR operation. This means it can be one thing or another but not both of them. Simply speaking, sum type is a set of different cases. The name “sum” comes from the fact that the total number of distinct values is the total number of cases.
Sum是代表逻辑OR操作的数据类型。这意味着它可以是一种东西,也可以是另一种东西,但不能同时是两种东西。简单地说,和类型是一组不同的情况。总和 “这个名字来自于一个事实,即不同值的总数就是案例的总数。
Enum is the closest thing in Java to the sum type. Enum has a set of possible values but can have only one of them at a time. However, we can’t associate any additional data with Enum in Java, which is the main advantage of algebraic data types over Enum.
Enum是Java中最接近于和类型的东西。Enum有一组可能的值,但每次只能有其中一个。然而,在Java中我们不能将任何额外的数据与Enum相关联,这是代数数据类型比Enum的主要优势。
3.3. Product Type
3.3.产品类型
Product is the data type representing the logical AND operation. It’s the combination of several values.
Product是代表逻辑AND操作的数据类型。它是几个值的组合。
Class in Java can be considered as a product type. Product types are defined by the combination of their fields altogether.
Java中的Class可以被看作是一种产品类型。产品类型是由其字段的组合共同定义的。
We can find more information on ADTs in this Wikipedia article.
3.4. Usage
3.4.使用方法
One of the commonly used algebraic data types is Either. We can think of Either as a more sophisticated Optional that can be used when there is a possibility of missing values or the operation can result in an exception.
常用的代数数据类型之一是Either。我们可以把Either看作是一个更复杂的Optional,当有可能出现缺失值或操作可能导致异常时,可以使用它。
We need to annotate an abstract class or interface with at least one abstract method that will be used by Derive4J to generate the structure of our ADT.
我们需要为一个抽象类或接口加上注解,至少有一个抽象方法将被Derive4J用来生成我们的ADT结构。
To create the Either data type in Derive4J we need to create an interface:
为了在Derive4J中创建Either数据类型,我们需要创建一个interface。
@Data
interface Either<A, B> {
<X> X match(Function<A, X> left, Function<B, X> right);
}
Our interface is annotated with @Data, which will allow Derive4J to generate the proper code for us. The generated code contains factory methods, lazy constructors, and various other methods.
我们的接口被注解为@Data,这将允许Derive4J为我们生成适当的代码。生成的代码包含工厂方法、懒惰构造函数和其他各种方法。
By default, the generated code gets the name of the annotated class, but in plural form. But, there is a possibility to configure that via the inClass parameter.
默认情况下,生成的代码会得到被注释的class的名字,但是是复数形式。但是,有可能通过inClass参数来配置它。
Now, we can use the generated code to create the Either ADT and verify that it’s working properly:
现在,我们可以使用生成的代码来创建Either ADT,并验证它是否正常工作。
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");
}
We can also use the generated match() method to execute a function depending on which side of Either is present:
我们还可以使用生成的match() 方法,根据Either 的哪一方来执行一个函数。
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
4.模式匹配
One of the features enabled by the use of algebraic data types is pattern matching.
使用代数数据类型的一个特点是模式匹配。
Pattern matching is the mechanism for checking a value against a pattern. Basically, pattern matching is a more powerful switch statement, but without limitations on the matching type or the requirement for patterns to be constant. For more information, we can check this Wikipedia article on pattern matching.
模式匹配是根据模式来检查一个值的机制。基本上,模式匹配是一个更强大的switch语句,但没有对匹配类型的限制,也没有对模式的要求是常数。欲了解更多信息,我们可以查看维基百科上关于模式匹配的这篇文章。
To use pattern matching, we’ll create a class that will model the HTTP request. The users will be able to use one of the given HTTP methods:
为了使用模式匹配,我们将创建一个类,对HTTP请求进行建模。用户将能够使用给定的HTTP方法中的一种。
- GET
- POST
- DELETE
- PUT
Let’s model our request class as an ADT in Derive4J, starting with the HTTPRequest interface:
让我们在Derive4J中把我们的请求类建模为一个ADT,从HTTPRequest接口开始。
@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);
}
The generated class, HttpRequests (note the plural form), will now allow us to perform pattern matching based on the type of request.
生成的类,HttpRequests(注意复数形式),现在将允许我们根据请求的类型来执行模式匹配。
For this purpose, we’ll create a very simple HTTPServer class that will respond with different Status depending on the type of request.
为此,我们将创建一个非常简单的HTTPServer类,它将根据请求的类型以不同的Status来响应。
First, let’s create a simple HTTPResponse class that will serve as a response from our server to our client:
首先,让我们创建一个简单的HTTPResponse 类,它将作为从服务器到客户端的响应。
public class HTTPResponse {
int statusCode;
String responseBody;
public HTTPResponse(int statusCode, String responseBody) {
this.statusCode = statusCode;
this.responseBody = responseBody;
}
}
Then we can create the server that will use pattern matching to send the proper response:
然后我们可以创建服务器,它将使用模式匹配来发送适当的响应。
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));
}
}
The acceptRequest() method of our class uses pattern matching on the type of the request and will return different responses based on the type of request:
我们的class的acceptRequest() 方法对请求的类型使用模式匹配,并将根据请求的类型返回不同的响应。
@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
5.一流的懒惰
Derive4J allows us to introduce the concept of laziness, meaning that our objects will not be initialized until we perform an operation on them. Let’s declare the interface as LazyRequest and configure the generated class to be named LazyRequestImpl:
Derive4J允许我们引入懒惰的概念,也就是说,在我们对对象进行操作之前,我们的对象将不会被初始化。让我们将接口声明为LazyRequest,并将生成的类配置为LazyRequestImpl。
@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);
}
We can now verify that the generated lazy constructor is working as it should:
我们现在可以验证生成的懒惰构造函数是否在正常工作。
@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");
}
}
We can find more information about first class laziness and examples in the Scala documentation.
我们可以在Scala文档中找到更多关于第一类懒惰的信息和例子。
6. Conclusion
6.结语
In this tutorial, we introduced the Derive4J library and used it to implement some functional concepts, like Algebraic Data Types and pattern matching, which are normally not available in Java.
在本教程中,我们介绍了Derive4J库,并使用它来实现一些函数式概念,如代数数据类型和模式匹配,这些通常在Java中是不存在的。
We can find more information about the library can be found in the official Derive4J documentation.
我们可以在官方Derive4J文档中找到关于该库的更多信息。
As always, all code samples can be found over on GitHub.
一如既往,所有的代码样本都可以在GitHub上找到。。