函数指针的进化论(2)
函数指针
不管是 C++ 编译器、或是 Java VM、或是 .NET CLR,内部都是以此方式来实现多态。尽管如此,这只能算是 black magic,对于 C++、Java 与 .NET 语言来说,函数指针「并未因此」和语言本身有直接相关。换句话说,C++ 和 Java 与 .NET 语言,就算语法本身不支持函数指针,照样也能实现多态。事实上,C++ 固然支持函数指针,但不是为了多态的关系,而是为了和 C 兼容 (毕竟 C++ 是 C 的 superset);IL Asm (.NET 上的汇编语言) 固然支持函数指针,但由于安全的理由,使用上受到相当大的限制,且不是为了多态的关系。至于 Java 与 C# 则都不支持函数指针。
没错,Java 与 C# 都不支持函数指针。虽然刚刚解释过,这不会影响对于多态的支持,但是这会不会影响对于多线程 (multithreading) 与回调 (call-back) 机制的支持呢?答案是:不会!因为 Java 可以利用多态或反射 (reflection) 来实现多线程与回调,而 C#可 以利用多态或反射或委托 (delegate) 来实现多线程与回调。
反射
顾名思义,反射 (reflection) 机制就像是在吴承恩所写的西游记中所提及的「照妖镜」,可以让类别或对象 (object) 在执行时期「现出原形」。我们可以利用反射机制来深入了解某类别 (class) 的构造函数 (constructor)、方法 (method)、字段 (field),甚至可以改变字段的值、呼叫方法、建立新的对象。有了反射机制,程序员即使对所想使用的类别所知不多,也能照样写程序。反射机制能够用来呼叫方法,这正是反射机制能够取代函数指针的原因。
以 Java 来说,java.lang.reflect.Method (以下简称 Method) 类别是用来表示某类别的某方法。我们可以透过 java.lang.Class (以下简称 Class) 类别的许多方法来取得 Method 对象。Method 类别提供 invoke() 方法,透过 invoke(),此 Method 对象所表示的方法可以被呼叫,所有的参数则是被组织成一个数组,以方便传入 invoke()。
举个例子,下面是一个名为 Invoke 的程序,它会将命令列的 Java 类别名称和要呼叫的方法名称作为参数。为了简单起见,我假定此方法是静态的,且没有参数:
import java.lang.reflect.*;class Invoke { public static void main(String[] args ) { try { Class c = Class.forName( args[0] ); Method m = c.getMethod( args[1], new Class [] { } ); Object ret = m.invoke( null, null ); System.out.println(args[0] + "." + args[1] +"() = " + ret ); } catch ( ClassNotFoundException ex ) {System.out.println("找不到此类别");
} catch (NoSuchMethodException ex ) {System.out.println("此方法不存在");
} catch (IllegalAccessException ex ) {System.out.println("没有权限调用此方法");
} catch (InvocationTargetException ex ) {System.out.println("调用此方法时发生下列例外:\n" +
ex.getTargetException() ); } }}
我们可以执行 Invoke 来取得系统的时间:
java Invoke java.lang.System CurrentTimeMillis
执行的结果如下所示:
java.lang.System.currentTimeMillis() = 1049551169474
我们的第一步就是用名称去寻找指定的 Class。我们用类别名称 (命令列的第一个参数) 去呼叫 forName() 方法,然后用方法名称 (命令列的第二个参数) 去取得方法。getMethod() 方法有两个参数:第一个是方法名称 (命令列的第二个参数),第二个是 Class 对象的数组,这个阵例指明了方法的 signature (任何方法都可能会被多载,所以必须指定 signature 来分辨。) 因为我们的简单程序只呼叫没有参数的方法,我们建立一个 Class 对象的匿名空数组。如果我们想要呼叫有参数的方法,我们可以传递一个类别数组,数组的内容是各个类别的型态,依顺序排列。
一旦我们有了 Method 对象,就呼叫它的 invoke() 方法,这会造成我们的目标方法被调用,并且将结果以 Object 对象传回。如果要对此对象做其它额外的事,你必须将它转型为更精确的型态。
invoke() 方法的第一个参数就是我们想要呼叫目标方法的对象,如果该方法是静态的,就没有对象,所以我们把第一个参数设为 null,这就是我们范例中的情形。第二个参数是要传给目标方法作为参数的对象数组,它们的型态要符合呼叫 getMethod() 方法中所指定的型态。因为我们呼叫的方法没有参数,所以我们传递 null 作为 invoke() 的第二个参数。
以上是 Java 的例子,事实上,.NET 的反射机制也相去不远,不再赘述。反射机制是最动态的机制,比多态的功能更强大。然而,反射的速度比多态慢许多 (而且多态又比函数指针稍慢),所以若非必要,应该少用反射机制。事实上,不管是 Java API 或 .NET Framework,都不使用反射机制来实现回调与多线程。
Java 的多线程
Java没有函数指针(为了系统安全),也不用反射机制来处理多线程(一方面为了效率,二方面反射机制是在JDK1.1才开始支持),而是使用多态的机制来处理多线程,作法如下:(另一种作法是实作java.lang.Runnable接口,与下面的作法雷同,不另说明。)
将执行线程的程序写在下面的run()方法中:
class MyThread extends java.lang.Thread { public void run() { // ... }}
再利用下面的方式来激活此执行线程:
MyThread thread = new MyThread();thread.start();
start() 方法定义在 java.lang.Thread 类别内,start() 方法会请操作系统建立一个执行线程,再呼叫 run(),此时调用的并非在 java.lang.Thread 内定义的 run() (它是空的),而是利用多态机制,调用到 MyThread 内定义的 run()。
Java的回调
通常回调机制都是使用 publisher/subscriber (出版者/订阅者) 的方式,必须先向系统注册:
· 何事件:我对何种事件感兴趣
· 何函数:当事件发生时,请呼叫我的函数,以为通知。此函数即为回调函数 (call-back function)。
Java 也是使用类似的作法,差别在于 Java 无法利用函式指针,且采用对象导向的作法。Java 将出版者 (publisher) 称为事件来源 (event source),将订阅者 (subscriber) 称为事件倾听者 (event listener)。大致的作法如下:
· 向事件来源注册 (registry) 事件倾听者
· 该事件发生时,事件来源通知 (notify) 事件倾听者
事件来源提供名为 addXxxListener() 的方法来让事件倾听者注册之用,此方法需要传入事件倾听者当参数。至于是何种事件,则由 Xxx 以为识别。例如:addMouseListener() 表示注册「鼠标事件」的事件倾听者。
利用 addXxxListener(),事件来源就可以将事件倾听者记录在字段 (field) 中。当事件发生时,事件来源就可以从字段中知道该通知和对象。可是应该呼叫该对象的那个方法呢?如果该对象没有提供该方法呢?
想要解决此问题,事件来源就必须过滤注册的对象,addXxxListener() 所需要的参数不可以是笼统的 java.lang.Object,而必须是一个实作 XxxListener 接口 (interface) 的对象。只要在 XxxListener 接口内宣告一个比方说 XxxEventHappened(),那么任何实作 XxxListener 的对象,都必定有实现 XxxEventHappened(),所以事件来源就可以在事件发生时,呼叫事件倾听者的 XxxEventHappened()。这正是依靠多态机制才能达成。
事件来源通知事件倾听者时,往往需要夹带一些额外的讯息,例如:事件来源是谁、事件发生于何时、事件发生的原因为何…。这些讯息被封装成事件对象,当作参数传给 XxxEventHappened()。
Java AWT/Swing 规定,所有的事件都必须继承自 java.util.EventObject 类别;所有的事件倾听者都必须实现 java.util.EventListener 接口。图二是 Java AWT 的事件继承阶层图:
例如,我要向一个名为 jButton1 的 javax.swing.JButton 对象注册,成为它的事件倾听者,那么就必须实作 java.awt.event.ActionListener 接口,提供 actionPerformed() 方法,如下所示:
class MyActionListener implements ActionListener { public void actionPerformed(ActionEvent evt) { // ... }}
注册的方式如下:
MyActionListener mal = new MyActionListener();jButton1.addActionListener(mal);
使用多态的机制来实现多线程和回调,不但麻烦 (必须继承),也不能使用静态方法,因为静态方法本来就没有多态的机制 (static 一定是 non-virtual)。顺便一提,前面所提到的反射机制,可以支持静态方法 (static method)。
作者:蔡學鏞 更新日期:2004-11-07
来源:MSDN台湾
浏览次数:
相关文章
相关评论 发表评论
- No Comments