Java等同于C#扩展方法


176

我希望在对象列表中实现功能,就像在C#中使用扩展方法一样。

像这样:

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

如何在Java中做到这一点?


8
检查一下:github.com/nicholas22/jpropel,例如:new String [] {“ james”,“ john”,“ john”,“ eddie”} .where(startsWith(“ j”))。distinct(); 它使用lombok-pg提供扩展方法的优点。
NT_

6
当他们允许扩展时,Microsoft绝对正确。如果我需要在其他地方返回给我的类中的功能,则添加新功能的子类将无法正常工作。就像向String和Date添加方法。
tggagne 2012年

3
即java.lang.String是最终类,因此您不能扩展它。使用静态方法是一种方法,但是有时它会显示代码不可读。我认为C#离开了计算机语言时代。扩展方法,局部类,LINQ等..
DavutGürbüz2012年

7
@ Roadrunner,rofl!对缺失语言功能的最好反驳是,所说的缺失语言功能是邪恶的和有害的。这是众所周知的。
柯克·沃尔

6
扩展方法不是“邪恶的”。它们极大地提高了代码的可读性。Java中许多糟糕的设计决策中只有一个。
csauve

Answers:


196

Java不支持扩展方法。

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


63
在使用扩展方法后,我宠坏了-但是静态方法也可以解决问题。
bbqchickenrobot 2012年

31
但是语法非常好,使程序更易于理解:)我也喜欢Ruby如何允许您做几乎相同的事情,除了您实际上可以修改内置类并添加新方法。
已知

18
@Ken:是的,这就是重点!为什么用Java而不是直接用JVM字节码编写?是不是“只是语法问题”?
Fyodor Soikin

30
与其他类的额外静态方法相比,扩展方法可使代码更加优雅。几乎所有更现代的语言都允许某种现有的类扩展:C#,php,objective-c,javascript。Java肯定在这里显示了它的年龄。假设您要将JSONObject写入磁盘。您调用jsonobj.writeToDisk()还是someunrelatedclass.writeToDisk(jsonobj)?
woens

7
讨厌Java的原因在不断增长。几年前,我不再寻找他们了……
约翰·德米特里

54

扩展方法不仅是静态方法,还不仅仅是便利的语法糖,实际上,它们是非常强大的工具。最主要的是能够基于不同的泛型参数实例化覆盖不同的方法。这类似于Haskell的类型类,实际上,看起来它们在C#中支持C#的Monad(即LINQ)。即使删除LINQ语法,我仍然不知道有什么方法可以在Java中实现类似的接口。

而且我认为不可能用Java实现它们,因为Java的泛型参数具有类型擦除语义。


它们还允许您继承多种行为(无多态)。您可以实现多个接口,它们的扩展方法随之而来。它们还允许您实现要附加到类型的行为,而无需使其与系统范围的类型全局关联。
聪明的新词

25
整个答案是错误的。C#中的扩展方法只是语法糖,编译器对其进行了一些重新排列,以将方法调用的目标移至静态方法的第一个参数。您不能覆盖现有方法。不要求扩展方法是monad。从字面上看,这只是调用静态方法的一种更方便的方法,它看起来像是将实例方法添加到类中。如果您同意此答案,请阅读此内容
Matt Klein

3
好,在这种情况下,定义语法糖是什么,我将语法糖称为内部宏语法,对于扩展方法,编译器必须至少查找扩展方法所替代的静态类。答案中没有关于该方法应为无意义的monad的信息。另外,您可以将其用于重载,但它不是扩展方法功能,它是基于普通参数类型的重载,与直接调用该方法时的工作方式相同,并且在Java中的许多有趣情况下均不起作用由于泛型类型参数擦除。
user1686250'7

1
@ user1686250可以用Java实现(通过“ Java”,我假设您是指在JVM上运行的字节码)... Kotlin,编译为字节码具有扩展名。它只是静态方法上的语法糖。您可以在IntelliJ中使用反编译器来查看等效Java的外观。
杰弗里·布拉特曼

@ user1686250您能否开发您正在编写的有关泛型的内容(或提供链接),因为我绝对不了解泛型。它与通常的静态方法有何关系?
C.Champagne


10

从技术上讲,C#Extension在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();
  }
}

7

XTEND语言-这是一个超级的Java,并编译成Java源代码1  -支持这一点。


当将非Java的代码编译为Java时,您有扩展方法吗?还是Java代码只是静态方法?
Fabio Milheiro 2014年

@Bomboca正如其他人指出的那样,Java没有扩展方法。因此,编译为Java的XTend代码不会以某种方式创建Java扩展方法。但是,如果您只在XTend工作,您将不会注意到或关心。但是,要回答您的问题,您也不一定要使用静态方法。XTend的主要作者在blog.efftinge.de/2011/11/…上有关于此的博客条目。
Erick G.

是的,不知道为什么我也不这么认为。谢谢!
法比奥·米尔海罗

@Sam感谢您向我介绍XTend-我从未听说过。
jpaugh



5

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();

112
完全没用。
SLaks 2010年

2
@Slaks:为什么要这样?这是您自己建议的“编写自己的课程”。
Goran Jovic 2010年

23
@Goran:所有这些允许您做的是定义一个方法,然后立即调用一次
SLaks 2010年

3
@Slaks:好吧,点了。与有限的解决方案相比,编写命名类会更好。
Goran Jovic 2010年

C#扩展方法和Java匿名类之间有很大的不同。在C#中,扩展方法是语法糖,实际上只是一种静态方法。IDE和编译器使扩展方法看起来像是扩展类的实例方法。(注意:在这种情况下,“扩展”并不像在Java中通常那样意味着“继承”。)
HairOfTheDog

4

Defender方法(即默认方法)可能会使其进入Java 8的可能性很小。但是,据我所知,它们仅允许an 的作者interface追溯扩展它,而不是任意用户。

这样,Defender方法+接口注入就可以完全实现C#样式的扩展方法,但是AFAICS接口注入甚至还没有出现在Java 8路线图上。


3

在这个问题上晚了一点,但是万一有人觉得有用,我就创建了一个子类:

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);
        }
    }
}

4
扩展方法通常用于无法像最终/密封类那样被修改或继承的代码,其主要功能是扩展接口,例如扩展IEnumerable <T>。当然,它们只是静态方法的语法糖。目的是使代码更具可读性。简洁的代码意味着更好的可维护性/可扩展性。
mbx 2012年

1
不只是@mbx。扩展方法对于扩展非密封类的类功能也很有用,但是您不能扩展它,因为您无法控制返回实例的任何内容,例如,作为抽象类的HttpContextBase。
法比奥·米尔海罗

@FabioMilheiro在这种情况下,我慷慨地将抽象类包含为“接口”。自动生成的类(xsd.exe)属于同一类:您可以但不应通过修改生成的文件来扩展它们。通常,您可以使用“ partial”来扩展它们,这要求它们驻留在同一程序集中。如果不是,那么扩展方法将是一个不错的选择。最终,它们只是静态方法(如果查看生成的IL代码,没有什么区别)。
mbx 2012年

是的...尽管我理解您的慷慨,但是HttpContextBase是一个抽象。将接口称为抽象可能看起来更自然。无论如何,我并不是说它必须是抽象的。我只是给出了一个类的示例,为此我编写了许多扩展方法。
Fabio Milheiro

2

我们可以使用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中添加新协定来模拟在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;
        }
    }

}

1
那真是一个地狱!
德米特里·阿夫托诺莫夫

1

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

这是一些用于增强列表功能的代码:

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);
    }
}

PS:我是Kotlin的忠实粉丝。它具有扩展方法,并且可以在JVM上运行。


0

您可以通过(RE)实现Collections接口并为Java Collection添加示例来创建类似于C#的扩展/帮助器方法:

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;
}

-7

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


8
错误; 这个问题的例子仍然是不可能的。
Slaks

@SLaks Java和C#扩展之间有什么区别?
Fabio Milheiro 2014年

3
默认方法只能在接口内定义。 docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html
Slaks

DarVar,此注释线程是唯一提到默认方法的地方,我非常想记住这一点。感谢您提及他们,如果不是名字!:-)(感谢@SLaks提供链接)
jpaugh

我会回答这个问题,因为接口中的静态方法的最终结果将提供与C#扩展方法相同的用法,但是,您仍然需要在类中实现接口,这与您将(this)关键字作为参数传递的C#不同
zaPlayer
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.