Java 语言是否具有委托功能,类似于 C# 对委托的支持?
15 回答
不是真的,不。
你也许可以通过使用反射来获取可以调用的 Method 对象来达到相同的效果,另一种方法是使用单个“invoke”或“execute”方法创建一个接口,然后实例化它们以调用该方法您对(即使用匿名内部类)感兴趣。
您可能还会发现这篇文章很有趣/有用:A Java Programmer Looks at C# Delegates (@blueskyprojects.com)
根据您的意思,您可以使用策略模式实现类似的效果(传递方法)。
而不是像这样声明命名方法签名的行:
// 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
}
}
SomeFunction
然后,无论您在 C# 中是否有委托,请改用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 */ });
介绍
最新版本的 Microsoft Visual J++ 开发环境支持称为委托或绑定方法引用的语言结构。该结构以及为支持它
delegate
而multicast
引入的新关键字不是 Java TM 编程语言的一部分,Java TM 编程语言由Java 语言规范指定,并由JDKTM 1.1 软件文档中包含的内部类规范进行了修改。Java 编程语言不太可能包含这种结构。Sun 已经在 1996 年仔细考虑过采用它,以构建和丢弃工作原型。我们的结论是绑定方法引用是不必要的并且对语言有害。这一决定是在与 Borland International 协商后做出的,Borland International 以前在 Delphi Object Pascal 中具有绑定方法引用的经验。
我们认为绑定方法引用是不必要的,因为另一种设计替代方案,内部类,提供了相同或更好的功能。特别是,内部类完全支持用户界面事件处理的要求,并且已被用于实现至少与 Windows 基础类一样全面的用户界面 API。
我们认为绑定方法引用是有害的,因为它们有损 Java 编程语言的简单性和 API 普遍的面向对象的特性。绑定方法引用还会在语言语法和范围规则中引入不规则性。最后,它们稀释了对 VM 技术的投资,因为 VM 需要有效地处理额外和不同类型的引用和方法链接。
你读过这个:
委托是基于事件的系统中的有用构造。本质上,委托是对指定对象上的方法调度进行编码的对象。本文档展示了 java 内部类如何为此类问题提供更通用的解决方案。
什么是代表?实际上,它与 C++ 中使用的指向成员函数的指针非常相似。但是委托包含目标对象以及要调用的方法。理想情况下,能够说:
obj.registerHandler(ano.methodOne);
..并且方法 methodOne 将在收到某些特定事件时在 ano 上调用。
这就是委托结构所实现的。
Java 内部类
有人认为 Java 通过匿名内部类提供此功能,因此不需要额外的 Delegate 构造。
obj.registerHandler(new Handler() {
public void handleIt(Event ev) {
methodOne(ev);
}
} );
乍一看,这似乎是正确的,但同时也令人讨厌。因为对于许多事件处理示例,Delegates 语法的简单性非常有吸引力。
通用处理程序
但是,如果以更普遍的方式使用基于事件的编程,例如,作为通用异步编程环境的一部分,则风险更大。
在这种一般情况下,仅包括目标方法和目标对象实例是不够的。通常,可能需要其他参数,这些参数是在注册事件处理程序时在上下文中确定的。
在这种更一般的情况下,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);
}
} );
}
最终*最终*最终
引起你的注意了吗?
请注意,可以从匿名类方法定义中访问最终变量。请务必仔细研究此代码以了解其后果。这可能是一种非常强大的技术。例如,在 MiniDOM 和更一般的情况下注册处理程序时,可以使用它来获得良好的效果。
相比之下,Delegate 构造并没有为这个更一般的要求提供解决方案,因此应该拒绝作为设计可以基于的习语。
我知道这篇文章很旧,但 Java 8 添加了 lambda,以及函数式接口的概念,它是任何只有一种方法的接口。这些共同提供了与 C# 委托类似的功能。有关更多信息,请参见此处,或者只是谷歌 Java Lambdas。 http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-final.html
不,但它们可以使用代理和反射来伪造:
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 代码。
我已经使用反射在 Java 中实现了回调/委托支持。详细信息和工作源可在我的网站上找到。
这个怎么运作
有一个名为 Callback 的原理类和一个名为 WithParms 的嵌套类。需要回调的 API 将 Callback 对象作为参数,如果需要,创建一个 Callback.WithParms 作为方法变量。由于该对象的许多应用程序将是递归的,因此它非常干净。
由于性能对我来说仍然是一个高优先级,我不想被要求创建一个一次性对象数组来保存每次调用的参数 - 毕竟在大型数据结构中可能有数千个元素,并且在消息处理中在这种情况下,我们最终可能每秒处理数千个数据结构。
为了线程安全,每次调用 API 方法的参数数组都需要唯一存在,并且为了提高效率,每次调用回调都应该使用相同的数组;我需要第二个创建成本低廉的对象,以便将回调与参数数组绑定以进行调用。但是,在某些情况下,由于其他原因,调用者可能已经拥有一个参数数组。由于这两个原因,参数数组不属于回调对象。此外,调用的选择(将参数作为数组或作为单个对象传递)属于 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;
}
这个例子说明了这种方法的美妙之处——应用程序特定的逻辑被抽象到回调中,递归遍历目录树的苦差事被很好地隐藏在一个完全可重用的静态实用程序方法中。而且我们不必为每次新用途重复定义和实现接口的代价。当然,接口的论点是它更明确地说明了要实现的内容(它是强制执行的,而不是简单地记录在案) - 但实际上我没有发现正确定义回调是一个问题。
定义和实现一个接口并不是那么糟糕(除非你像我一样分发小程序,避免创建额外的类实际上很重要),但是当你在一个类中有多个回调时,这真的很重要。不仅被迫将它们各自推入一个单独的内部类中,这在部署的应用程序中增加了开销,而且编程非常乏味,所有样板代码实际上只是“噪音”。
Yes & No,但是 Java 中的委托模式可以这样考虑。这个视频教程是关于activity-fragment之间的数据交换,它具有使用接口的委托排序模式的精髓。
它没有delegate
像 C# 那样的显式关键字,但您可以在 Java 8 中通过使用函数式接口(即任何只有一种方法的接口)和 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()
方法是安全的。
使用类路径上的安全镜像,您可以获得类似于 C# 的委托和事件的内容。
项目自述文件中的示例:
Java的代表!
Delegate.With1Param<String, String> greetingsDelegate = new Delegate.With1Param<>();
greetingsDelegate.add(str -> "Hello " + str);
greetingsDelegate.add(str -> "Goodbye " + str);
DelegateInvocationResult<String> invocationResult =
greetingsDelegate.invokeAndAggregateExceptions("Sir");
invocationResult.getFunctionInvocationResults().forEach(funInvRes ->
System.out.println(funInvRes.getResult()));
//prints: "Hello sir" and "Goodbye Sir"
活动
//Create a private Delegate. Make sure it is private so only *you* can invoke it.
private static Delegate.With0Params<String> trimDelegate = new Delegate.With0Params<>();
//Create a public Event using the delegate you just created.
public static Event.With0Params<String> trimEvent= new Event.With0Params<>(trimDelegate)
另请参阅此SO 答案。
虽然它远没有那么干净,但您可以使用 Java Proxy实现诸如 C# 委托之类的东西。
不,但它在内部具有类似的行为。
在 C# 中,委托用于创建单独的入口点,它们的工作方式与函数指针非常相似。
在 java 中没有函数指针(从上层看),但在内部,Java 需要做同样的事情才能实现这些目标。
例如,在 Java 中创建线程需要类扩展 Thread 或实现 Runnable,因为类对象变量可以用作内存位置指针。
不,Java 没有那个惊人的特性。但是您可以使用观察者模式手动创建它。这是一个例子: 在java中编写C#委托
所描述的代码提供了 C# 委托的许多优点。方法,无论是静态的还是动态的,都可以以统一的方式处理。通过反射调用方法的复杂性降低了,代码可重用,在用户代码中不需要额外的类的意义上。请注意,我们正在调用另一个便捷版本的调用,其中可以调用具有一个参数的方法而无需创建对象数组。Java 代码如下:
class Class1 {
public void show(String s) { System.out.println(s); }
}
class Class2 {
public void display(String s) { System.out.println(s); }
}
// allows static method as well
class Class3 {
public static void staticDisplay(String s) { System.out.println(s); }
}
public class TestDelegate {
public static final Class[] OUTPUT_ARGS = { String.class };
public final Delegator DO_SHOW = new Delegator(OUTPUT_ARGS,Void.TYPE);
public void main(String[] args) {
Delegate[] items = new Delegate[3];
items[0] = DO_SHOW .build(new Class1(),"show,);
items[1] = DO_SHOW.build (new Class2(),"display");
items[2] = DO_SHOW.build(Class3.class, "staticDisplay");
for(int i = 0; i < items.length; i++) {
items[i].invoke("Hello World");
}
}
}
Java 没有委托,并以此为荣 :)。从我在这里读到的内容中,我发现了 2 种伪造代表的方法: 1. 反射;2.内部类
反射很慢!内部类不涵盖最简单的用例:排序函数。不想详述,但是内部类的解决方案基本上是为要按升序排序的整数数组创建一个包装类,为要按降序排序的整数数组创建一个类。