Java语言是否具有委托功能,类似于C#对委托的支持方式?
Java语言是否具有委托功能,类似于C#对委托的支持方式?
Answers:
不是,不是
通过使用反射来获取可以调用的Method对象,您可能能够实现相同的效果,另一种方法是使用单个“调用”或“执行”方法创建一个接口,然后实例化它们以调用该方法。您感兴趣的(即使用匿名内部类)。
您可能还会发现本文有趣/有用:Java程序员研究C#委托(@ archive.org)
精确地根据您的意思,您可以使用策略模式实现类似的效果(绕过一种方法)。
而不是像这样的行声明命名方法签名:
// 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语言规范和修订的内部类规范包括在对JDKTM 1.1软件文档。Java编程语言不太可能会包含此构造。Sun早在1996年就已经认真考虑过采用它,以至于可以建造和丢弃工作原型。我们的结论是,绑定方法引用是不必要的,并且不利于语言。该决定是在与Borland International协商后做出的,Borland International以前在Delphi Object Pascal中有绑定方法引用的经验。
我们认为绑定方法引用是不必要的,因为另一个设计替代方法内部类提供了相同或更高的功能。特别是,内部类完全支持用户界面事件处理的要求,并且已用于实现至少与Windows Foundation类一样全面的用户界面API。
我们认为绑定方法引用是有害的,因为它们有损Java编程语言的简单性和API的普遍面向对象的特性。绑定方法引用还将不规则性引入语言语法和作用域规则中。最后,它们稀释了对VM技术的投资,因为需要VM才能有效地处理其他类型的引用和方法链接。
你读过这个:
委托是基于事件的系统中的有用构造。本质上,委托是对在指定对象上调度的方法进行编码的对象。该文档说明了Java内部类如何为此类问题提供更通用的解决方案。
什么是代表?实际上,它非常类似于C ++中使用的指向成员函数的指针。但是委托包含目标对象以及要调用的方法。理想情况下,可以说:
obj.registerHandler(ano.methodOne);
..并在收到某些特定事件时在ano上调用methodOne方法。
这就是委托结构所实现的。
Java内部类
有人争辩说Java通过匿名内部类提供此功能,因此不需要附加的Delegate构造。
obj.registerHandler(new Handler() {
public void handleIt(Event ev) {
methodOne(ev);
}
} );
乍看之下,这似乎是正确的,但同时又令人讨厌。因为对于许多事件处理示例,Delegates语法的简单性非常吸引人。
总经理
但是,如果以更普遍的方式使用基于事件的编程,例如,将其作为通用异步编程环境的一部分,则会带来更多风险。
在这种一般情况下,仅包含目标方法和目标对象实例是不够的。通常,可能需要其他参数,这些参数是在注册事件处理程序时在上下文中确定的。
在这种更一般的情况下,java方法可以提供非常优雅的解决方案,尤其是在结合使用最终变量时:
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#委托相似的功能。有关更多信息,请参见此处,或者仅查看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?"));
}
关于这个习惯用法的好处是,您可以在创建委托者的时候验证委托方法是否存在,并具有必需的签名(尽管不幸的是,尽管不是在编译时,但FindBugs插件可能帮助),然后安全地将其委派给各种实例。
我已经使用反射在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;
}
这个例子说明了这种方法的优点-将特定于应用程序的逻辑抽象到回调中,并用完全可重用的静态实用程序方法很好地解决了繁琐地递归遍历目录树的工作。而且,我们不必为每种新用途重复付出定义和实现接口的代价。当然,参数为一个接口,这是迄今为止关于如何落实更明确(它是强制执行的,不是简单的记录) -但在实践中我还没有发现它是得到回调定义权的问题。
定义和实现接口并不是真的很糟糕(除非像我一样分发小程序,否则避免创建额外的类实际上很重要),但是真正引人注目的是当您在一个类中有多个回调时。不仅被迫将它们每个都推入一个单独的内部类中,这在部署的应用程序中增加了开销,而且编程非常繁琐,而且所有样板代码实际上只是“噪音”。
它没有delegate
像C#那样的显式关键字,但是您可以通过使用功能接口(即具有唯一一种方法的任何接口)和lambda在Java 8中实现类似的目的:
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 Proxy实现类似C#委托的功能。
不,但是在内部具有相似的行为。
在C#中,委托用于创建单独的入口点,它们的工作原理类似于函数指针。
在Java中,没有什么作为函数指针(从外观上看),但是Java内部需要做相同的事情才能实现这些目标。
例如,在Java中创建线程需要扩展Thread的类或实现Runnable,因为可以将类对象变量用作内存位置指针。
不,Java没有那么神奇的功能。但是您可以使用观察者模式手动创建它。这是一个示例: 用Java编写C#委托
所描述的代码提供了C#委托人的许多优点。静态或动态方法都可以用统一的方式处理。在不需要用户代码中附加类的意义上,通过反射调用方法的复杂性降低了,并且代码可重用。请注意,我们调用了invoke的另一种便捷版本,在该版本中,可以在不创建对象数组的情况下调用具有一个参数的方法。以下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没有委托,并为此而感到自豪:)。从我在这里阅读的内容中,我实质上发现了两种伪造代表的方式:1.反思;2.内部阶层
倒影是slooooow!内部类不涵盖最简单的用例:排序功能。不想赘述,但是内部类的解决方案基本上是为要按升序排序的整数数组创建包装类,并为要按降序排序的整数数组创建包装类。