Java中与C#扩展方法对应的是什么?

220

我希望在一个对象列表中实现一种功能,就像在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_
7
当微软允许扩展时,他们肯定做得没错。如果我需要在其他地方返回一个类中的函数,那么通过子类添加新功能是行不通的,比如向String和Date添加方法。 - tggagne
4
java.lang.String是一个final类,所以无法继承。使用静态方法是一种方式,但有时会导致代码难以阅读。我认为C#已成为计算机语言的一代。扩展方法,部分类,LINQ等等... - Davut Gürbüz
7
@Roadrunner,rofl!对于缺失的语言功能最好的反击是称该功能为邪恶和不需要的。这是众所周知的。 - Kirk Woll
7
扩展方法并不是“邪恶”的,它们大大提高了代码的可读性。这只是 Java 中许多糟糕设计决策中的又一个。 - csauve
显示剩余8条评论
14个回答

253

Java不支持扩展方法。

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


85
使用扩展方法后,我变得有点挑剔了 - 但静态方法也能实现同样的效果。 - bbqchickenrobot
39
但是语法非常好,使程序更易于理解 :) 我也喜欢 Ruby 允许你做几乎相同的事情,除了你实际上可以修改内置类并添加新方法。 - knownasilya
29
@Ken:是的,这正是重点!为什么你要用Java编写代码而不是直接使用JVM字节码呢?这不仅仅是“语法问题”吗? - Fyodor Soikin
36
与其他类中的额外静态方法相比,扩展方法可以使代码更加优雅。几乎所有现代语言都允许某种类型的现有类扩展:C#、php、objective-c、javascript。在这里,Java显然表现出其年龄。想象一下你想将一个JSONObject写入磁盘。你会调用jsonobj.writeToDisk()还是someunrelatedclass.writeToDisk(jsonobj)? - woens
26
讨厌 Java 的原因越来越多。但几年前我就不再寻找它们了...... - John Demetriou
显示剩余18条评论

56

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

而且我认为在Java中不可能实现它们,因为Java的类型擦除语义使得泛型参数无法实例化。


2
它们还允许您继承多个行为(不包括多态性)。您可以实现多个接口,并伴随着它们的扩展方法。它们还允许您实现想要附加到类型的行为,而无需将其全局关联到整个类型系统。 - Clever Neologism
53
这整篇回答都是错误的。在C#中,扩展方法只是一种语法糖,编译器稍微重新排列一下代码,将方法调用的目标移动到静态方法的第一个参数上。你不能重写现有的方法。扩展方法不必遵循单子模式。它只是一种更方便的调用静态方法的方式,使得给类添加实例方法的外观成为可能。如果你赞同这个答案,请阅读这篇文章:https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/extension-methods - Matt Klein
3
在这种情况下,定义什么是语法糖。我认为,语法糖是内部宏语法,对于扩展方法,编译器必须至少查找静态类的位置以进行替换。答案中没有提到方法应该是单子的,这是无意义的。此外,您可以将其用于重载,但它不是扩展方法的特性,而是基于纯参数类型的重载方法,与直接调用方法的方式相同,并且由于Java泛型类型参数擦除的原因,在许多有趣的情况下它将无法工作。 - user1686250
2
@user1686250 可以在Java中实现(通过“Java”我假设您指的是在JVM上运行的字节码)... Kotlin编译为字节码具有扩展功能。它只是静态方法上的语法糖。您可以使用IntelliJ中的反编译器查看等效的Java代码。 - Jeffrey Blattman
4
那些认为这只是“语法糖”的人可能从未需要管理大量代码或进行代码审查。在编程中,可读性是最重要的事情,除非你要进行汇编语言或 GPU 级别的高性能计算。我有很多代码要维护,没有时间去跟随那些难以理解的静态方法调用。如果你将它们与 Java Lambda 结合使用,你的代码会看起来很糟糕。一些 Lambda 表达式看起来很丑陋和复杂,你最好还是使用旧的 “foreach” 循环。如果 Java 不改进,将来我甚至会将堆栈从Java更改为C#。 - djmj
显示剩余4条评论

38

18

从技术上讲,Java没有等价的C#扩展。但是如果您想要实现这样的函数以获得更清晰的代码和可维护性,则必须使用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();
  }
}

14

Java中没有扩展方法,但您可以通过 manifold 实现它,如下所示:

您可以按照以下示例为字符串定义“echo”方法,

@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

9
"

XTend 语言是Java的超集,可以编译为Java源代码1。该语言支持此操作。

"

当非Java代码编译为Java时,您是否有扩展方法?或者Java代码只是一个静态方法? - Fabio Milheiro
正如其他人所指出的,Java 没有扩展方法。因此,编译为 Java 的 XTend 代码不会创建 Java 扩展方法。但是,如果您完全使用 XTend 工作,您将不会注意到或关心这一点。但是,回答您的问题,您也不一定拥有静态方法。XTend 的主要作者在他的博客上有一篇关于这个问题的文章,请参见 http://blog.efftinge.de/2011/11/whats-so-special-about-xtends-extension.html。 - Erick G. Hagstrom
是的,不知道为什么我也没有想到。谢谢! - Fabio Milheiro
@Sam 感谢你向我介绍 XTend -- 我以前从未听说过它。 - jpaugh

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

5

看起来 Defender Methods(即默认方法)有一些小概率会进入 Java 8。然而,据我所知,它们只允许 interface作者 在后期扩展该接口,而不是任意用户。

如果加入 Interface Injection,Defender Methods 将能够完全实现类似于 C# 的扩展方法,但据我所知,Interface Injection 还没有出现在 Java 8 的路线图中。


3

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

122
那是完全没有用的。 - SLaks
3
为什么?这是由你自己建议的“编写自己的类”。 - Goran Jovic
27
这意味着你只能定义一个方法,然后立即调用它,仅限一次。 - SLaks
4
好的,我明白了。与那个有限的解决方案相比,编写一个命名类会更好。 - Goran Jovic
C#扩展方法和Java匿名类之间有很大的区别。在C#中,扩展方法只是静态方法的语法糖。IDE和编译器使扩展方法看起来像是扩展类的实例方法。(注意:在这个上下文中,“扩展”并不意味着像Java中通常意义上的“继承”) - HairOfTheDog
显示剩余2条评论

2
可以使用装饰器面向对象设计模式。 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上运行。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接