Java语言是否有委托功能,类似于C#支持委托?
Java语言是否有委托功能,类似于C#支持委托?
不完全是这样。
你可以使用反射获取用于调用的Method对象来实现相同的效果,另一种方法是创建一个具有单个“invoke”或“execute”方法的接口,然后实例化它们以调用您感兴趣的方法(即使用匿名内部类)。
你可能会发现这篇文章有趣/有用:Java程序员看待C#委托(@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
}
}
那么无论你在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 */ });
微软Visual J++开发环境的最新版本支持一种语言结构,称为委托或绑定方法引用。这种结构和支持它的新关键字delegate
和multicast
不是Java编程语言的一部分,该语言由Java语言规范指定,并在JDKTM 1.1软件文档中包含内部类规范所修订。
Java编程语言不太可能包括这种结构。Sun公司在1996年已经仔细考虑过采用它,甚至建立并且放弃了工作原型。我们的结论是绑定方法引用是不必要的,对语言有害的。这个决定是与Borland International公司协商后做出的,他们之前在Delphi Object Pascal中使用了绑定方法引用。
我们认为绑定方法引用是不必要的,因为另一种设计选择——内部类提供了相同或更好的功能。特别是,内部类完全支持用户界面事件处理的要求,并且已被用于实现至少与Windows Foundation Classes一样全面的用户界面API。
我们认为绑定方法引用是有害的,因为它们削弱了Java编程语言的简单性和API的普遍面向对象的特性。绑定方法引用还会在语言语法和作用域规则中引入不规则性。最后,它们会削弱VM技术的投资,因为VM需要有效地处理附加和不同类型的引用和方法链接。
你有没有读过这个:
在基于事件的系统中,委托是一个有用的构造。实际上,委托是将指定对象上的方法调度进行编码的对象。本文展示了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构造不提供解决这个更普遍需求的方案,因此应该将其拒绝为基础设计。
我知道这篇文章有点旧了,但Java 8已经添加了Lambda表达式和函数接口的概念,其中函数接口是指只有一个方法的任何接口。这些功能提供类似于C#委托的功能。这里有更多信息,或者可以直接在Google搜索"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?"));
}
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()
方法是安全的。我使用反射在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;
}