By Mi Zhou (Zhiye)
An extension method means to add methods to an existing type without creating a new derived type, recompiling, or modifying the existing type. When calling an extension method, there is no significant difference compared to calling the method defined in the type.
To implement this function: After extracting strings containing multiple product IDs from Redis (each product ID is separated by English commas), deduplicate product IDs (still can maintain the order of elements) and then use English commas to connect each product ID.
// "123,456,123,789"
String str = redisService.get(someKey)
Traditional Syntax:
String itemIdStrs = String.join(",", new LinkedHashSet<>(Arrays.asList(str.split(","))));
Use Stream
syntax:
String itemIdStrs = Arrays.stream(str.split(",")).distinct().collect(Collectors.joining(","));
Assume extension methods can be implemented in Java. For arrays, we add extension method toList
(turning arrays into List
); for List, we add extension methods toSet
(turning List
into LinkedHashSet
); for Collection
, we add extension methods join
(connecting the string forms of elements in the collection with given connectors). Then, we can write code like this:
String itemIdStrs = str.split(",").toList().toSet().join(",");
Now, you know why we need extension methods:
I will introduce a new way: Manifold.
The principle of Manifold is similar to Lombok, which is processed by the annotation processor during compilation. We need to install the Manifold IDEA plug-in to properly use Manifold in IDEA.
Then, add annotationProcessorPaths to the maven-compiler-plugin of the project pom.
<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>2023.1.10</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</arg>
</compilerArgs>
<annotationProcessorPaths>
<path>
<groupId>systems.manifold</groupId>
<artifactId>manifold-ext</artifactId>
<version>${manifold.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
If you use Lombok in your project, you need to add Lombok to annotationProcessorPaths.
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>systems.manifold</groupId>
<artifactId>manifold-ext</artifactId>
<version>${manifold.version}</version>
</path>
</annotationProcessorPaths>
In JDK, the split
method of String
uses a string as a parameter, which is String[] split(String)
. Now let's add an extension method for String, String[] split(char)
: split by the given character.
Based on Manifold, write an extension method.
package com.alibaba.zhiye.extensions.java.lang.String;
import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.This;
import org.apache.commons.lang3.StringUtils;
/**
* String extension method
*/
@Extension
public final class StringExt {
public static String[] split(@This String str, char separator) {
return StringUtils.split(str, separator);
}
}
We can find they are essentially static methods of tool class, but there are some requirements.
@Extension
annotation of Manifold.@This
annotation.Those familiar with C# should know this is an imitation of the C# extension method.
Regarding point 3, the reasons for this requirement are that Manifold wants to quickly find extension methods in the project, avoid scanning all classes in the project for annotations, and improve processing efficiency.
With the ability of extension methods, we can call them like this:
Amazing! Also, System.out.println(numStrs.toString())
prints the string form of the array object instead of the address of the array object. Looking at the decompiled App.class, it is found that the extension method call is replaced with the static method call.
The toString
method of the array uses the extension method ManArrayExt.toString(@This Object array)
defined by Manifold for arrays.
No more [Ljava.lang.String;@511 d50c0
Since the extension method call is replaced with the static method call at compile time, there is no problem with using Manifold's extension method even if the method calling object is null
. The processed code passes null
as a parameter to the corresponding static method. For example, let's extend Collection
:
package com.alibaba.zhiye.extensions.java.util.Collection;
import manifold.ext.rt.api.Extension;
import manifold.ext.rt.api.This;
import java.util.Collection;
/**
* Collection extension method
*/
@Extension
public final class CollectionExt {
public static boolean isNullOrEmpty(@This Collection<?> coll) {
return coll == null || coll.isEmpty();
}
}
When calling:
List<String> list = getSomeNullableList();
// If list is null, it will enter the if block without triggering the null pointer exception.
if (list.isNullOrEmpty()) {
// TODO
}
No more java.lang.NullPointerException
.
Arrays do not have a specific corresponding type in JDK. What package should the extension class defined for arrays be put into? Looking at the ManArrayExt
source code, we find that Manifold specifically provides a class manifold.rt.api.Array
to represent the array. For example, the method of toList
provided for the array in ManArrayExt
:
We see List<@Self(true) Object>
syntax like this: @Self
is used to indicate what type the annotated value should be. If it is @Self
, which is @Self(false)
, the annotated value and the value of the @This
annotation are the same types. @Self(true)
indicates the type of element in the array.
For object arrays, we can see the toList
method returns the corresponding List<T>
(T is the type of the array element).
However, if it is a primitive type of array, the return value indicated by IDEA is:
But I'm using Java. How can type erasure generics have such a great feature like List<char>
? You can only use native types to receive this return value. :)
We often see in various projects that we package an object into an Optional
and then filter
, map
, etc. With @Self
type mapping, you can add a practical method for 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 method
*/
@Extension
public final class ObjectExt {
public static Optional<@Self Object> asOpt(@This Object obj) {
return Optional.ofNullable(obj);
}
}
Then, any object will have asOpt()
methods. Previously, you need to package it.
Optional.ofNullable(someObj).filter(someFilter).map(someMapper).orElseGet(someSupplier);
Now, you can use Optional
.
someObj.asOpt().filter(someFilter).map(someMapper).orElseGet(someSupplier);
Object is the parent class of all classes. You still need to consider whether this is appropriate.
We all know Java 9 added factory methods to collections:
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);
If you're not using Java 9 or above, you'll have to use a library like Guava. However, ImmutableList.of
can't compare to List.of
.
Never mind. Manifold can do it. Extending the static method based on Manifold means adding @Extension
to the static method of the extended class.
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 method
*/
@Extension
public final class ListExt {
/**
* Returns an immutable List containing only one element.
*/
@Extension
public static <E> List<E> of(E element) {
return Collections.singletonList(element);
}
/**
* Returns an immutable List containing multiple elements.
*/
@Extension
@SafeVarargs
public static <E> List<E> of(E... elements) {
return Collections.unmodifiableList(Arrays.asList(elements));
}
}
Then, you can deceive yourself that you have used the version after Java 8.
Also, since Object
is the parent class of all classes, if you add static extension methods to Object
, it means you can access this static method anywhere without import.
I started to pay attention to Manifold in 2019. At that time, the Manifold IDEA plug-in was not free yet, so I only made a simple attempt at that time. Recently, the IDEA plug-in has become free to use, so I can't wait to make the most of it. I have used Manifold in a project to implement the function of extension methods. If you have any suggestions or questions about its use, please discuss them with me.
If we decide to use Manifold to implement extension methods in our project, we must be cautious.
First of all, as mentioned above, it is necessary to be careful when adding extension methods to Object
or other classes widely used in the project. It is best to discuss with the project team and let everyone decide together. Otherwise, it can be confusing.
In addition, if you want to add an extension method to a class, you must consider – Is the logic of this method included in the scope of the responsibility of this class? Is there any business custom logic? For example, the following method (determines whether a given string is a valid parameter).
public static boolean isValidParam(String str) {
return StringUtils.isNotBlank(str) && !"null".equalsIgnoreCase(str);
}
isValidParam
is not in the scope of responsibility of String, and isValidParam
should continue to be placed in XxxBizUtils
. If you change the method name to isNotBlankAndNotEqualsIgnoreCaseNullLiteral
, it is ok. :) However, I advise you not to do so.
mizhou - June 15, 2023
Alibaba Cloud Community - June 16, 2023
ApsaraDB - July 26, 2018
Alibaba Clouder - November 8, 2016
Alibaba Cloud Community - March 16, 2023
OpenAnolis - April 11, 2022
A low-code development platform to make work easier
Learn MoreHelp enterprises build high-quality, stable mobile apps
Learn MoreAlibaba Cloud (in partnership with Whale Cloud) helps telcos build an all-in-one telecommunication and digital lifestyle platform based on DingTalk.
Learn MoreMore Posts by mizhou