Java's Missing Feature: Extension Methods

Summary: This article introduces how to use Manifold to implement extension methods in Java to help improve development efficiency and code readability.
What is an extension method


An extension method is the ability to "add" a method directly to an existing type without creating a new derived type, recompiling, or otherwise modifying the existing type. When calling an extension method, there is no noticeable difference compared to calling a method actually defined on the type.



Why do you need extension methods


Consider implementing such a function: after fetching a string containing multiple product IDs from Redis (each product ID is separated by a comma), first deduplicate the product ID (and be able to maintain the order of the elements), and finally use English Commas connect the product IDs.

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

String itemIdStrs = String.join(",", new LinkedHashSet<>(Arrays.asList(str.split(","))));
Use the Stream notation:

String itemIdStrs = Arrays. stream(str. split(",")). distinct(). collect(Collectors. joining(","));
Assume that the extension method can be implemented in Java, and we add the extension method toList for the array (change the array into a List), add the extension method toSet for the List (change the List into a LinkedHashSet), and add the extension method join for the Collection (change the The string form of the elements in the collection is connected using the given connector), then we will be able to write code like this:

String itemIdStrs = str.split(",").toList().toSet().join(",");
I believe at this point you already have the answer to why you need extension methods:

You can directly enhance the existing class library instead of using tool classes
Compared with using tool classes, using the method of the type itself to write code is smoother and more comfortable
The code is easier to read because it is chained calls instead of static method nesting dolls


How to implement extension methods in Java


Let's first ask the ChatGPT of the recent fire:







Well, ChatGPT thinks that extension methods in Java are static methods provided by tool classes :).







So next I will introduce a brand new black technology: Manifold



preparation conditions


The principle of Manifold is the same as that of Lombok, and it is also processed through annotation processors during compilation. So to use Manifold correctly in IDEA, you need to install the Manifold IDEA plugin:







Then add annotationProcessorPaths to the maven-compiler-plugin of the project pom:

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...


2022.1.34




systems.manifold
manifold-ext
${manifold.version}

...






org.apache.maven.plugins
maven-compiler-plugin
3.8.1

8
8
UTF-8

-Xplugin:Manifold no-bootstrap



systems.manifold
manifold-ext
${manifold.version}







If you use Lombok in your project, you need to add Lombok to annotationProcessorPaths:



org.projectlombok
lombok
${lombok.version}


systems.manifold
manifold-ext
${manifold.version}




Write extension methods


In JDK, the split method of String uses a string as a parameter, namely String[] split(String). Let's now add an extension method String[] split(char) to String: split by a given character.



Based on Manifold, write extension methods:

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;

/**
* Extension method for String
*/
@Extension
public final class StringExt {
public static String[] split(@This String str, char separator) {
return StringUtils. split(str, separator);
}
}
It can be found that it is essentially a static method of a tool class, but there are some requirements:

Tool classes need to use Manifold's @Extension annotation
In the static method, the parameters of the target type need to be annotated with @This
The package name of the tool class, which needs to end with extensions. The fully qualified class name of the target type


—— Students who have used C# should smile knowingly, this is the extension method of C# imitated.



Regarding point 3, the reason for this requirement is that Manifold hopes to quickly find the extension methods in the project, avoid scanning annotations for all classes in the project, and improve processing efficiency.



With the ability to extend the method, we can now call it like this:

Xnip2023-01-11_10-54-36.png

Amazing! And you can find that System.out.println(numStrs.toString()) actually prints the string form of the array object—not the address of the array object. Looking at the decompiled App.class, it is found that the extension method call is replaced by a static method call:

image.png

The toString method of the array uses the extension method ManArrayExt.toString(@This Object array) defined by Manifold for the array:

image.png

[Ljava.lang.String;@511d50c0 or something, Goodbye, never see you again~

Because the call of the extension method is replaced with a call of the static method at compile time, using the extension method of Manifold, even if the object of the calling method is null, there is no problem, because the processed code passes null as a parameter to the corresponding static method . For example, we 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;

/**
* Extension method of Collection
*/
@Extension
public final class CollectionExt {
public static boolean isNullOrEmpty(@This Collection coll) {
return coll == null || coll.isEmpty();
}
}
Then when calling:

List list = getSomeNullableList();

// If the list is null, it will enter the if block without triggering a null pointer exception
if (list. isNullOrEmpty()) {
//TODO
}
java.lang.NullPointerException, Goodbye, never see you again~



Array extension method


In JDK, arrays do not have a specific corresponding type, so what package should the extension classes defined for arrays be placed in? look at The source code of ManArrayExt found that Manifold specially provides a class manifold.rt.api.Array to represent arrays. For example, the toList method provided for arrays in ManArrayExt:

image.png

We see that List<@Self(true) Object> is written like this: @Self is used to indicate what type the annotated value should be. If it is @Self, that is, @Self(false), it means the annotated value and @ The value of the This annotation is the same type; @Self(true) indicates the type of the elements in the array.



For object arrays, we can see that the toList method returns the corresponding List (T is the type of array elements):

image.png

But if it is an array of primitive types, the return value indicated by IDEA is:

image.png

But I'm using Java, how can erasure generics have such a great function as List - so you can only use primitive types to receive this return value :)

image.png

— Make a wish: Project Valhalla will be GA in Java21.

We often see in various projects that everyone first wraps an object into an Optional, and then performs filter, map, etc. Through the type mapping of @Self, you can add a very practical method to Object like this:

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;

/**
* Extension method for Object
*/
@Extension
public final class ObjectExt {
public static Optional<@Self Object> asOpt(@This Object obj) {
return Optional.ofNullable(obj);
}
}
Then any object will have asOpt() method. Compared with the unnaturalness that needs to be packaged before:

Optional.ofNullable(someObj).filter(someFilter).map(someMapper).orElseGet(someSupplier);
You can now use Optional naturally:

someObj.asOpt().filter(someFilter).map(someMapper).orElseGet(someSupplier);
Of course, Object is the parent class of all classes. Whether it is appropriate to do so requires careful consideration.



Extend static method


We all know that Java9 added factory methods to collections:

List list = List.of("a", "b", "c");
Set set = Set.of("a", "b", "c");
Map map = Map.of("a", 1, "b", 2, "c", 3);
Are you hungry? Because if you are not using Java9 and above (Java8: just report my ID card), you have to use a library such as Guava - but ImmutableList.of is not as orthodox as List.of after all. nature.



Never mind, Manifold said: "It's okay, I'll do it". To extend the static method based on Manifold, add @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 List of(E element) {
return Collections. singletonList(element);
}

/**
* Returns an immutable List containing multiple elements
*/
@Extension
@SafeVarargs
public static List of(E... elements) {
return Collections. unmodifiableList(Arrays. asList(elements));
}
}
Then you can fool yourself that you have used a version after Java8-you send it as you like, I use Java8.



BTW, because Object is the parent class of all classes, if you add a static extension method to Object, it means that you can directly access this static method anywhere without import——Congratulations, you have unlocked the "top-level function" .



suggestion


About Manifold


I started to pay attention to Manifold in 2019. At that time, the Manifold IDEA plug-in was still charged, so I just made a simple attempt at that time. Looking at it recently, the IDEA plugin is completely free, so I can't wait to make the most of it. At present, I have used Manifold in a project to realize the function of the extension method - the person concerned said that it is very addictive and cannot do without it. If you have suggestions and questions about usage, welcome to discuss with me.



Add extension methods with caution


If we decide to use Manifold to implement extension methods in the project, then we must "hold our own hands".



First of all, as mentioned above, you must be very cautious when adding extension methods to Object or other classes that are widely used in the project. It is best to discuss with the students in the project team and let everyone decide together, otherwise it will be easy. To confuse (swear).



In addition, if you want to add an extension method to a certain class, you must first seriously think about a question: "Is the logic of this method within the scope of responsibility of this class, and is there any business custom logic mixed in?" For example, the following method (judging whether the given string is a legal parameter):

public static boolean isValidParam(String str) {
return StringUtils.isNotBlank(str) && !"null".equalsIgnoreCase(str);
}
Obviously, isValidParam is not the scope of responsibility of the String class, and isValidParam should continue to be placed in XxxBizUtils. Of course, if you change the method name to isNotBlankAndNotEqualsIgnoreCaseNullLiteral, that’s okay :) —— But I advise you not to do this, it’s easy to get hit.

Related Articles

Explore More Special Offers

  1. Short Message Service(SMS) & Mail Service

    50,000 email package starts as low as USD 1.99, 120 short messages start at only USD 1.00

phone Contact Us