Java等价于c#扩展方法

我希望在对象列表中实现一个功能,因为我会在c#中使用扩展方法。

就像这样:

List<DataObject> list;
// ... List initialization.
list.getData(id);

在Java中怎么做呢?

127497 次浏览

Java不支持扩展方法。

相反,您可以创建一个常规的静态方法,或者编写自己的类。

Java没有这个特性。 相反,您可以创建列表实现的常规子类或创建匿名内部类:

List<String> list = new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
};

问题是调用这个方法。你可以“就地”做:

new ArrayList<String>() {
public String getData() {
return ""; // add your implementation here.
}
}.getData();

看起来防御方法(即默认方法)有可能加入Java 8。然而,据我所知,它们只允许interface作者追溯扩展它,不允许任意用户。

防御方法+接口注入将能够完全实现c#风格的扩展方法,但是AFAICS,接口注入甚至还没有出现在Java 8的路线图中。

另一种选择是使用google-guava库中的ForwardingXXX类。

这个问题有点晚了,但如果有人发现它有用,我只是创建了一个子类:

public class ArrayList2<T> extends ArrayList<T>
{
private static final long serialVersionUID = 1L;


public T getLast()
{
if (this.isEmpty())
{
return null;
}
else
{
return this.get(this.size() - 1);
}
}
}

Java 8现在支持默认方法,它类似于C#的扩展方法。

XTend语言 它是Java的超集,并编译为Java源代码__abc1  —支持这一点。

扩展方法不仅仅是静态方法,也不仅仅是方便的语法糖,实际上它们是非常强大的工具。主要的事情是基于不同泛型的参数实例化重写不同方法的能力。这类似于Haskell的类型类,事实上,它们看起来像是在c#中支持c#的单子(即LINQ)。即使放弃LINQ语法,我仍然不知道如何在Java中实现类似的接口。

而且我认为在Java中实现它们是不可能的,因为Java的泛型参数的类型清除语义。

Project Lombok提供了一个注释@ExtensionMethod,可以用来实现你想要的功能。

可以使用装饰器面向对象的设计模式。在Java的标准库中使用这种模式的一个例子是DataOutputStream

下面是一些增强List功能的代码:

public class ListDecorator<E> implements List<E>
{
public final List<E> wrapee;


public ListDecorator(List<E> wrapee)
{
this.wrapee = wrapee;
}


// implementation of all the list's methods here...


public <R> ListDecorator<R> map(Transform<E,R> transformer)
{
ArrayList<R> result = new ArrayList<R>(size());
for (E element : this)
{
R transformed = transformer.transform(element);
result.add(transformed);
}
return new ListDecorator<R>(result);
}
}

附注:我是Kotlin的忠实粉丝。它有扩展方法,也运行在JVM上。

从技术上讲,c#扩展在Java中没有对等的扩展。但是如果你确实想要实现这样的函数以获得更清晰的代码和可维护性,你必须使用Manifold框架。

package extensions.java.lang.String;


import manifold.ext.api.*;


@Extension
public class MyStringExtension {


public static void print(@This String thiz) {
System.out.println(thiz);
}


@Extension
public static String lineSeparator() {
return System.lineSeparator();
}
}
我们可以通过使用Java 8以来可用的默认方法实现来模拟Java中c#扩展方法的实现。 首先定义一个接口,允许我们通过base()方法访问支持对象,如下所示:

public interface Extension<T> {


default T base() {
return null;
}
}

我们返回null,因为接口不能有状态,但是稍后必须通过代理来修复这个问题。

扩展的开发人员必须通过一个包含扩展方法的新接口来扩展这个接口。 假设我们想在List接口上添加一个forEach消费者:

public interface ListExtension<T> extends Extension<List<T>> {


default void foreach(Consumer<T> consumer) {
for (T item : base()) {
consumer.accept(item);
}
}


}

因为我们扩展了Extension接口,所以我们可以在扩展方法中调用base()方法来访问我们所附加的支持对象。

Extension接口必须有一个工厂方法,该方法将创建给定支持对象的扩展:

public interface Extension<T> {


...


static <E extends Extension<T>, T> E create(Class<E> type, T instance) {
if (type.isInterface()) {
ExtensionHandler<T> handler = new ExtensionHandler<T>(instance);
List<Class<?>> interfaces = new ArrayList<Class<?>>();
interfaces.add(type);
Class<?> baseType = type.getSuperclass();
while (baseType != null && baseType.isInterface()) {
interfaces.add(baseType);
baseType = baseType.getSuperclass();
}
Object proxy = Proxy.newProxyInstance(
Extension.class.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
handler);
return type.cast(proxy);
} else {
return null;
}
}
}
我们创建一个代理来实现扩展接口和支持对象类型实现的所有接口。 给代理的调用处理程序将把所有调用分派给支持对象,除了“base”方法,它必须返回支持对象,否则它的默认实现将返回null:

public class ExtensionHandler<T> implements InvocationHandler {


private T instance;


private ExtensionHandler(T instance) {
this.instance = instance;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("base".equals(method.getName())
&& method.getParameterCount() == 0) {
return instance;
} else {
Class<?> type = method.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup()
.in(type);
Field allowedModesField = lookup.getClass().getDeclaredField("allowedModes");
makeFieldModifiable(allowedModesField);
allowedModesField.set(lookup, -1);
return lookup
.unreflectSpecial(method, type)
.bindTo(proxy)
.invokeWithArguments(args);
}
}


private static void makeFieldModifiable(Field field) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField
.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}


}
然后,我们可以使用extension .create()方法将包含扩展方法的接口附加到支持对象。 结果是一个可以强制转换为扩展接口的对象,通过该对象,我们仍然可以访问调用base()方法的支持对象。 将引用转换为扩展接口后,现在可以安全地调用可以访问支持对象的扩展方法,因此现在可以将新方法附加到现有对象,但不能附加到其定义类型:

public class Program {


public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
ListExtension<String> listExtension = Extension.create(ListExtension.class, list);
listExtension.foreach(System.out::println);
}


}

因此,这是一种通过向Java对象中添加新契约来模拟扩展对象的能力的方法,这允许我们对给定的对象调用额外的方法。

下面你可以找到扩展接口的代码:

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;


public interface Extension<T> {


public class ExtensionHandler<T> implements InvocationHandler {


private T instance;


private ExtensionHandler(T instance) {
this.instance = instance;
}


@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if ("base".equals(method.getName())
&& method.getParameterCount() == 0) {
return instance;
} else {
Class<?> type = method.getDeclaringClass();
MethodHandles.Lookup lookup = MethodHandles.lookup()
.in(type);
Field allowedModesField = lookup.getClass().getDeclaredField("allowedModes");
makeFieldModifiable(allowedModesField);
allowedModesField.set(lookup, -1);
return lookup
.unreflectSpecial(method, type)
.bindTo(proxy)
.invokeWithArguments(args);
}
}


private static void makeFieldModifiable(Field field) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}


}


default T base() {
return null;
}


static <E extends Extension<T>, T> E create(Class<E> type, T instance) {
if (type.isInterface()) {
ExtensionHandler<T> handler = new ExtensionHandler<T>(instance);
List<Class<?>> interfaces = new ArrayList<Class<?>>();
interfaces.add(type);
Class<?> baseType = type.getSuperclass();
while (baseType != null && baseType.isInterface()) {
interfaces.add(baseType);
baseType = baseType.getSuperclass();
}
Object proxy = Proxy.newProxyInstance(
Extension.class.getClassLoader(),
interfaces.toArray(new Class<?>[interfaces.size()]),
handler);
return type.cast(proxy);
} else {
return null;
}
}


}

你可以通过(RE)实现Collections接口来创建一个类似c#的扩展/助手方法,并添加Java Collection的示例:

public class RockCollection<T extends Comparable<T>> implements Collection<T> {
private Collection<T> _list = new ArrayList<T>();


//###########Custom extension methods###########


public T doSomething() {
//do some stuff
return _list
}


//proper examples
public T find(Predicate<T> predicate) {
return _list.stream()
.filter(predicate)
.findFirst()
.get();
}


public List<T> findAll(Predicate<T> predicate) {
return _list.stream()
.filter(predicate)
.collect(Collectors.<T>toList());
}


public String join(String joiner) {
StringBuilder aggregate = new StringBuilder("");
_list.forEach( item ->
aggregate.append(item.toString() + joiner)
);
return aggregate.toString().substring(0, aggregate.length() - 1);
}


public List<T> reverse() {
List<T> listToReverse = (List<T>)_list;
Collections.reverse(listToReverse);
return listToReverse;
}


public List<T> sort(Comparator<T> sortComparer) {
List<T> listToReverse = (List<T>)_list;
Collections.sort(listToReverse, sortComparer);
return listToReverse;
}


public int sum() {
List<T> list = (List<T>)_list;
int total = 0;
for (T aList : list) {
total += Integer.parseInt(aList.toString());
}
return total;
}


public List<T> minus(RockCollection<T> listToMinus) {
List<T> list = (List<T>)_list;
int total = 0;
listToMinus.forEach(list::remove);
return list;
}


public Double average() {
List<T> list = (List<T>)_list;
Double total = 0.0;
for (T aList : list) {
total += Double.parseDouble(aList.toString());
}
return total / list.size();
}


public T first() {
return _list.stream().findFirst().get();
//.collect(Collectors.<T>toList());
}
public T last() {
List<T> list = (List<T>)_list;
return list.get(_list.size() - 1);
}
//##############################################
//Re-implement existing methods
@Override
public int size() {
return _list.size();
}


@Override
public boolean isEmpty() {
return _list == null || _list.size() == 0;
}

在Java中没有扩展方法,但是你可以通过得到它,如下所示:

你定义“回声”;方法用于以下示例的字符串,

@Extension
public class MyStringExtension {
public static void echo(@This String thiz) {
System.out.println(thiz);
}
}

在那之后,你可以在任何地方对字符串使用这个方法(echo),

"Hello World".echo();   //prints "Hello World"
"Welcome".echo();       //prints "Welcome"
String name = "Jonn";
name.echo();            //prints "John"


当然,你也可以有这样的参数,

@Extension
public class MyStringExtension {
public static void echo(@This String thiz, String suffix) {
System.out.println(thiz + " " + suffix);
}
}

像这样使用,

"Hello World".echo("programming");   //prints "Hello World programming"
"Welcome".echo("2021");              //prints "Welcome 2021"
String name = "John";
name.echo("Conor");                  //prints "John Conor"

你也可以看看这个例子,Manifold-sample