Java代理?

224

Java语言是否有委托功能,类似于C#支持委托?


26
@Suma,如果你提到的那个问题是在这个问题发布一年后发布的,那么它怎么可能是重复的? - Ani
1
Java 8有一个类似于委托的功能,它被称为lambda。 - tbodt
4
Java 8有一个与委托类似的特性,更确切地说,它被称为函数式接口。Lambda表达式是创建这种委托实例(匿名地)的一种方式。 - nawfal
3
以下答案是针对Java 8之前的版本。Java 8及之后版本的答案请查看以下链接: https://dev59.com/cmIj5IYBdhLWcg3wYkRY - Michael Lloyd Lee mlk
@Pacerier:经过进一步的思考,我意识到你的意思了。我在这里提供了一个答案(https://dev59.com/cmIj5IYBdhLWcg3wYkRY#30925223),展示了如何在Java 7中实现。 - ToolmakerSteve
显示剩余3条评论
14个回答

169

不完全是这样。

你可以使用反射获取用于调用的Method对象来实现相同的效果,另一种方法是创建一个具有单个“invoke”或“execute”方法的接口,然后实例化它们以调用您感兴趣的方法(即使用匿名内部类)。

你可能会发现这篇文章有趣/有用:Java程序员看待C#委托(@blueskyprojects.com)


4
只有一个成员函数invoke()的接口方案非常不错。 - Stephane Rolland
1
但是请看我的示例这里,即使在Java 7中,也可以完成与C#委托相当的工作。 - ToolmakerSteve
链接已过期。 - duesterdust
谢谢提醒 - 我已经用 Wayback Machine 的链接替换了它,这个链接应该可以在未来继续使用。值得注意的是,在这些年里,Java 支持的内容发生了很多变化。 - Matt Sheppard

74

根据您的意思,可以使用策略模式实现类似的效果(传递一个方法)。

与其使用声明命名方法签名的行:

// C#
public delegate void SomeFunction();

声明一个接口:

// Java
public interface ISomeBehaviour {
   void SomeFunction();
}

为了具体实现该方法,需要定义一个实现该行为的类:

// Java
public class TypeABehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeA behaviour
   }
}

public class TypeBBehaviour implements ISomeBehaviour {
   public void SomeFunction() {
      // TypeB behaviour
   }
}

那么无论你在C#中使用SomeFunction委托的任何地方,现在改用ISomeBehaviour接口引用:

// C#
SomeFunction doSomething = SomeMethod;
doSomething();
doSomething = SomeOtherMethod;
doSomething();

// Java
ISomeBehaviour someBehaviour = new TypeABehaviour();
someBehaviour.SomeFunction();
someBehaviour = new TypeBBehaviour();
someBehaviour.SomeFunction();

使用匿名内部类,甚至可以避免声明独立的命名类,几乎像真正的委托函数一样处理它们。

// Java
public void SomeMethod(ISomeBehaviour pSomeBehaviour) {
   ...
}

...

SomeMethod(new ISomeBehaviour() { 
   @Override
   public void SomeFunction() {
      // your implementation
   }
});

只有在实现非常特定于当前上下文且不受重用的好处时,才应该使用这个。

当然,在Java 8中,这些基本上变成了lambda表达式:

// Java 8
SomeMethod(() -> { /* your implementation */ });

7
这是一个不错的解决方法,虽然有些冗长,但未来可以通过可维护性来弥补。 - nawfal
太好了...目前正在进行一个项目,由于项目限制我无法使用反射,但这种解决方法很好地完成了工作:) - Jonathan Camarena
Java是否有接口的I前缀命名约定?我以前没有见过这样的约定。 - Kyle Delaney

37

短篇小说:不。

介绍

微软Visual J++开发环境的最新版本支持一种语言结构,称为委托绑定方法引用。这种结构和支持它的新关键字delegatemulticast不是Java编程语言的一部分,该语言由Java语言规范指定,并在JDKTM 1.1软件文档中包含内部类规范所修订。

Java编程语言不太可能包括这种结构。Sun公司在1996年已经仔细考虑过采用它,甚至建立并且放弃了工作原型。我们的结论是绑定方法引用是不必要的,对语言有害的。这个决定是与Borland International公司协商后做出的,他们之前在Delphi Object Pascal中使用了绑定方法引用。

我们认为绑定方法引用是不必要的,因为另一种设计选择——内部类提供了相同或更好的功能。特别是,内部类完全支持用户界面事件处理的要求,并且已被用于实现至少与Windows Foundation Classes一样全面的用户界面API。

我们认为绑定方法引用是有害的,因为它们削弱了Java编程语言的简单性和API的普遍面向对象的特性。绑定方法引用还会在语言语法和作用域规则中引入不规则性。最后,它们会削弱VM技术的投资,因为VM需要有效地处理附加和不同类型的引用和方法链接。


正如Patrick所链接的那样,你应该使用内部类。 - SCdF
20
好的文章。我喜欢他们对“简单”的定义:“Java被设计成一种简单的语言,因为它需要容易编写编译器/虚拟机”,但却忽略了“Java需要容易被人编写/阅读”的方面。这解释了很多问题。 - Juozas Kontvainis
5
我认为 SUN 公司犯了一个很大的错误,他们并没有被功能式编程范式所说服,这就是全部。 - Stephane Rolland
7
Python的设计初衷是让程序员能够轻松编写和阅读代码,同时它确实实现了lambda函数/委托。 - Stephane Rolland
6
已用 archive.org 的链接替换。同时,这真的很愚蠢,Oracle。 - Patrick
显示剩余2条评论

21

你有没有读过这个

在基于事件的系统中,委托是一个有用的构造。实际上,委托是将指定对象上的方法调度进行编码的对象。本文展示了Java内部类如何提供更通用的解决方案。

什么是委托?它实际上与C++中使用的成员函数指针非常相似。但是,委托包含要调用的方法以及目标对象。理想情况下,我们希望能够这样说:

obj.registerHandler(ano.methodOne);

当接收到某个特定事件时,方法methodOne将在ano上调用。

这就是委托结构实现的功能。

Java内部类

有人认为Java通过匿名内部类提供了这种功能,因此不需要额外的委托构造。

obj.registerHandler(new Handler() {
        public void handleIt(Event ev) {
            methodOne(ev);
        }
      } );

乍一看这似乎是正确的,但同时也很麻烦。因为对于许多事件处理示例来说,委托语法的简洁性非常有吸引力。

通用处理程序

然而,如果事件驱动编程被更广泛地使用,例如作为一般异步编程环境的一部分,就会面临更多挑战。

在这种情况下,仅包括目标方法和目标对象实例是不足够的。通常可能需要其他参数,在注册事件处理程序时确定上下文。

在这种更一般的情况下,Java的方法可以提供非常优雅的解决方案,特别是与使用final变量相结合时:

void processState(final T1 p1, final T2 dispatch) { 
  final int a1 = someCalculation();

  m_obj.registerHandler(new Handler() {
    public void handleIt(Event ev) {
     dispatch.methodOne(a1, ev, p1);
    }
  } );
}

final * final * final

引起了你的注意吗?请注意,final变量可以从匿名类方法定义中访问。请仔细研究此代码以了解其影响。这是一种潜在的非常强大的技术。例如,在MiniDOM中注册处理程序和在更一般的情况下使用时,它可以产生良好的效果。

相比之下,Delegate构造不提供解决这个更普遍需求的方案,因此应该将其拒绝为基础设计。


13

我知道这篇文章有点旧了,但Java 8已经添加了Lambda表达式和函数接口的概念,其中函数接口是指只有一个方法的任何接口。这些功能提供类似于C#委托的功能。这里有更多信息,或者可以直接在Google搜索"Java Lambdas"。 http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html


5
不,但可以使用代理和反射伪造它们:
  public static class TestClass {
      public String knockKnock() {
          return "who's there?";
      }
  }

  private final TestClass testInstance = new TestClass();

  @Test public void
  can_delegate_a_single_method_interface_to_an_instance() throws Exception {
      Delegator<TestClass, Callable<String>> knockKnockDelegator = Delegator.ofMethod("knockKnock")
                                                                   .of(TestClass.class)
                                                                   .to(Callable.class);
      Callable<String> callable = knockKnockDelegator.delegateTo(testInstance);
      assertThat(callable.call(), is("who's there?"));
  }

这个习惯用语的好处是,您可以在创建委托者时(遗憾的是,虽然不是在编译时,但FindBugs插件可能会有所帮助),验证被委托的方法是否存在,并且具有所需的签名,然后安全地将其委派给各种实例。
请参见GitHub上的karg代码,以获取更多测试信息实现方案

2

Java 8没有像C#一样的明确的delegate关键字,但您可以通过使用函数式接口(即仅具有一个方法的任何接口)和lambda来实现类似的效果:

最初的回答:

Java 8没有像C#一样的明确的delegate关键字,但您可以通过使用函数式接口(即仅具有一个方法的任何接口)和lambda来实现类似的效果:

private interface SingleFunc {
    void printMe();
}

public static void main(String[] args) {
    SingleFunc sf = () -> {
        System.out.println("Hello, I am a simple single func.");
    };
    SingleFunc sfComplex = () -> {
        System.out.println("Hello, I am a COMPLEX single func.");
    };
    delegate(sf);
    delegate(sfComplex);
}

private static void delegate(SingleFunc f) {
    f.printMe();
}

每个新的SingleFunc类型的对象都必须实现printMe()方法,因此将其传递给另一个方法(例如delegate(SingleFunc))调用printMe()方法是安全的。

2

我使用反射在Java中实现了回调/委托支持。详情和可工作的源码可以在我的网站上找到。

工作原理

有一个名为Callback的主类,其中嵌套了一个名为WithParms的类。需要回调的API将会把一个Callback对象作为参数,并且如果必要,创建一个Callback.WithParms作为方法变量。由于这个对象的许多应用都是递归的,因此这样做非常干净。

出于对性能的高度重视,我不想被要求为每次调用创建一个一次性的对象数组来保存参数-毕竟在大型数据结构中可能会有数千个元素,在消息处理场景中我们可能会每秒处理数千个数据结构。

为了线程安全,参数数组需要为每个API方法调用独立存在,为了效率,每个回调调用应该使用相同的参数数组;我需要第二个便宜的对象,以便绑定回调与参数数组进行调用。但是,在某些情况下,调用者已经拥有参数数组,出于这两个原因,参数数组不属于Callback对象。而且,调用方式(将参数作为数组或单独对象传递)应该由使用回调的API控制,使其能够使用最适合其内部工作方式的调用。

因此,WithParms嵌套类是可选的,并且具有两个目的:它包含回调调用所需的参数对象数组,并且提供10个重载的invoke()方法(从1到10个参数),这些方法将加载参数数组,然后调用回调目标。

接下来是一个使用回调处理目录树中文件的示例。这是一个初始验证步骤,只计数要处理的文件并确保没有超过预定的最大大小。在这种情况下,我们只需在API调用中内联创建回调。但是,我们将目标方法反射为静态值,以避免每次都进行反射。

static private final Method             COUNT =Callback.getMethod(Xxx.class,"callback_count",true,File.class,File.class);

...

IoUtil.processDirectory(root,new Callback(this,COUNT),selector);

...

private void callback_count(File dir, File fil) {
    if(fil!=null) {                                                                             // file is null for processing a directory
        fileTotal++;
        if(fil.length()>fileSizeLimit) {
            throw new Abort("Failed","File size exceeds maximum of "+TextUtil.formatNumber(fileSizeLimit)+" bytes: "+fil);
            }
        }
    progress("Counting",dir,fileTotal);
    }

IoUtil.processDirectory():

/**
 * Process a directory using callbacks.  To interrupt, the callback must throw an (unchecked) exception.
 * Subdirectories are processed only if the selector is null or selects the directories, and are done
 * after the files in any given directory.  When the callback is invoked for a directory, the file
 * argument is null;
 * <p>
 * The callback signature is:
 * <pre>    void callback(File dir, File ent);</pre>
 * <p>
 * @return          The number of files processed.
 */
static public int processDirectory(File dir, Callback cbk, FileSelector sel) {
    return _processDirectory(dir,new Callback.WithParms(cbk,2),sel);
    }

static private int _processDirectory(File dir, Callback.WithParms cbk, FileSelector sel) {
    int                                 cnt=0;

    if(!dir.isDirectory()) {
        if(sel==null || sel.accept(dir)) { cbk.invoke(dir.getParent(),dir); cnt++; }
        }
    else {
        cbk.invoke(dir,(Object[])null);

        File[] lst=(sel==null ? dir.listFiles() : dir.listFiles(sel));
        if(lst!=null) {
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(!ent.isDirectory()) {
                    cbk.invoke(dir,ent);
                    lst[xa]=null;
                    cnt++;
                    }
                }
            for(int xa=0; xa<lst.length; xa++) {
                File ent=lst[xa];
                if(ent!=null) { cnt+=_processDirectory(ent,cbk,sel); }
                }
            }
        }
    return cnt;
    }

这个例子展示了这种方法的优美之处 - 应用特定的逻辑被抽象成回调函数,而递归遍历目录树的繁琐工作则完全隐藏在一个可重用的静态工具方法中。我们不必为每个新的使用定义和实现接口,也不必反复付出代价。当然,对于接口的论点是它更加明确要实现什么(它是强制性的,而不仅仅是文档化)- 但在实践中,我发现将回调定义正确并不是一个问题。
定义和实现接口并不是太糟糕的事情(除非你正在分发小程序,因为避免创建额外的类实际上很重要),但这真正闪耀的地方是当你有多个回调函数在同一个类中时。不仅强制将它们推入单独的内部类会增加部署应用程序的开销,而且编程起来非常乏味,所有那些样板代码只是“噪音”。

2

是的和不是,但在Java中委托模式可以这样考虑。这个视频教程是关于活动-片段之间数据交换的,它具有使用接口的委托模式的精髓。

Java Interface


0

不,Java没有那个惊人的功能。但是您可以使用观察者模式手动创建它。以下是一个示例: 在Java中编写C#委托


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