友情提示:如果本网页打开太慢或显示不完整,请尝试鼠标右键“刷新”本网页!
富士康小说网 返回本书目录 加入书签 我的书架 我的书签 TXT全本下载 『收藏到我的浏览器』

Java编程思想第4版[中文版](PDF格式)-第111部分

快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部! 如果本书没有阅读完,想下次继续接着阅读,可使用上方 "收藏到我的浏览器" 功能 和 "加入书签" 功能!



过对数字进行调整,即可进行全面的修改。至于与这种群聚行为有关的更多的情况,大家可以访问Craig  

Reynold 的主页——在那个地方,甚至还提供了Boids 一个公开的 3D 展示版本:  

http://hmt。/cwr/boids。html  

为了将这个程序作为一个程序片运行,请在HTML 文件中设置下述程序片标志:  

  

  

  

  

  



17。4 总结  



通过本章的学习,大家知道运用Java 可做到一些较复杂的事情。通过这些例子亦可看出,尽管 Java 必定有 

自己的局限,但受那些局限影响的主要是性能(比如写好文字处理程序后,会发现C++的版本要快得多—— 

这部分是由于 IO 库做得不完善造成的;而在你读到本书的时候,情况也许已发生了变化。但Java 的局限也 

仅此而已,它在语言表达方面的能力是无以伦比的。利用 Java,几乎可以表达出我们想得到的任何事情。而 

与此同时,Java 在表达的方便性和易读性上,也做足了功夫。所以在使用 Java 时,一般不会陷入其他语言 

常见的那种复杂境地。使用那些语言时,会感觉它们象一个爱唠叨的老太婆,哪有Java 那样清纯、简练!而 

且通过 Java 1。2 的JFC/Swing 库,AWT 的表达能力和易用性甚至又得到了进一步的增强。  



17。5 练习  



(1)  (稍微有些难度)改写FieldOBeasts。java,使它的状态能够保持固定。加上一些按钮,允许用户保存 

和恢复不同的状态文件,并从它们断掉的地方开始继续运行。请先参考第 10章的CADState。java ,再决定具 

体怎样做。  

(2)  (大作业)以FieldOBeasts。java 作为起点,构造一个自动化交通仿真系统。  

(3)  (大作业)以ClassScanner。java 作为起点,构造一个特殊的工具,用它找出那些虽然定义但从未用过 

的方法和字段。  

(4)  (大作业)利用JDBC,构造一个联络管理程序。让这个程序以一个平面文件数据库为基础,其中包含了 

名字、地址、电话号码、E…mail 地址等联系资料。应该能向数据库里方便地加入新名字。键入要查找的名字 

时,请采用在第15章的VLookup。java 里介绍过的那种名字自动填充技术。  



                                                                                 649 


…………………………………………………………Page 651……………………………………………………………

                        附录 A 使用非 JAVA 代码  



  

JAVA 语言及其标准API (应用程序编程接口)应付应用程序的编写已绰绰有余。但在某些情况下,还是必须 

使用非 JAVA 编码。例如,我们有时要访问操作系统的专用特性,与特殊的硬件设备打交道,重复使用现有的 

非Java 接口,或者要使用“对时间敏感”的代码段,等等。与非 Java 代码的沟通要求获得编译器和“虚拟 

机”的专门支持,并需附加的工具将Java 代码映射成非 Java 代码(也有一个简单方法:在第 15章的“一个 

Web 应用”小节中,有个例子解释了如何利用标准输入输出同非Java 代码连接)。目前,不同的开发商为我 

们提供了不同的方案:Java 1。1 有“Java 固有接口”(Java Native Interface,JNI),网景提出了自己的 

 “Java 运行期接口”(Java Runtime Interface)计划,而微软提供了J/Direct、“本源接口”(Raw  

Native Interface,RNI)以及Java/ 集成方案。  

各开发商在这个问题上所持的不同态度对程序员是非常不利的。若Java 应用必须调用固有方法,则程序员或 

许要实现固有方法的不同版本——具体由应用程序运行的平台决定。程序员也许实际需要不同版本的Java 代 

码,以及不同的Java 虚拟机。  

另一个方案是CORBA (通用对象请求代理结构),这是由OMG (对象管理组,一家非赢利性的公司协会)开发 

的一种集成技术。CORBA 并非任何语言的一部分,只是实现通用通信总线及服务的一种规范。利用它可在由 

不同语言实现的对象之间实现“相互操作”的能力。这种通信总线的名字叫作ORB (对象请求代理),是由 

其他开发商实现的一种产品,但并不属于 Java 语言规范的一部分。  

本附录将对 JNI,J/DIRECT,RNI,JAVA/ 集成和 CORBA 进行概述。但不会作更深层次的探讨,甚至有时还 

假定读者已对相关的概念和技术有了一定程度的认识。但到最后,大家应该能够自行比较不同的方法,并根 

据自己要解决的问题挑选出最恰当的一种。  



A。1 Java 固有接口  



JNI 是一种包容极广的编程接口,允许我们从 Java 应用程序里调用固有方法。它是在 Java 1。1 里新增的, 

维持着与Java 1。0 的相应特性—— “固有方法接口”(NMI)——某种程度的兼容。NMI 设计上一些特点使 

其未获所有虚拟机的支持。考虑到这个原因,Java 语言将来的版本可能不再提供对NMI 的支持,这儿也不准 

备讨论它。  

目前,JNI 只能与用C 或C++写成的固有方法打交道。利用JNI,我们的固有方法可以:  

■创建、检查及更新Java 对象(包括数组和字串)  

■调用Java 方法  

■俘获和丢弃“异常”  

■装载类并获取类信息  

■进行运行期类型检查  

所以,原来在Java 中能对类及对象做的几乎所有事情在固有方法中同样可以做到。  



A。1。1  调用固有方法  



我们先从一个简单的例子开始:一个Java 程序调用固有方法,后者再调用Win32 的API 函数 

MessageBox(),显示出一个图形化的文本框。这个例子稍后也会与J/Direct 一志使用。若您的平台不是 

Win32,只需将包含了下述内容的C 头:  

#include   

替换成:  

#include   

并将对MessageBox()的调用换成调用 printf() 即可。  

第一步是写出对固有方法及它的自变量进行声明的 Java 代码:  

  

class ShowMsgBox {  

  public static void main(String '' args) {  

    ShowMsgBox app = new ShowMsgBox();  

    app。ShowMessage(〃Generated with JNI〃);  

  }  



                                                                        650 


…………………………………………………………Page 652……………………………………………………………

  private native void ShowMessage(String msg);  

  static {  

    System。loadLibrary(〃MsgImpl〃);  

  }  

}  

  

在固有方法声明的后面,跟随有一个 static 代码块,它会调用 System。loadLibrary() (可在任何时候调用 

它,但这样做更恰当)System。loadLibrary()将一个 DLL 载入内存,并建立同它的链接。DLL 必须位于您的 

系统路径,或者在包含了Java 类文件的目录中。根据具体的平台,JVM 会自动添加适当的文件扩展名。  

  

1。 C 头文件生成器:javah  

现在编译您的Java 源文件,并对编译出来的。class文件运行 javah。javah 是在 1。0版里提供的,但由于我 

们要使用Java 1。1 JNI,所以必须指定…jni 参数:  

javah …jni ShowMsgBox  

javah 会读入类文件,并为每个固有方法声明在 C 或C++头文件里生成一个函数原型。下面是输出结果—— 

ShowMsgBox。h 源文件(为符合本书的要求,稍微进行了一下修改):  

  

/* DO NOT EDIT THIS FILE   

   it is machine generated */  

#include   

/* Header for class ShowMsgBox */  

  

#ifndef _Included_ShowMsgBox  

#define _Included_ShowMsgBox  

#ifdef __cplusplus  

extern 〃C〃 {  

#endif  

/*  

 * Class:     ShowMsgBox  

 * Method:    ShowMessage  

 * Signature: (Ljava/lang/String;)V  

 */  

JNIEXPORT void JNICALL   

Java_ShowMsgBox_ShowMessage  

  (JNIEnv *; jobject; jstring);  

  

#ifdef __cplusplus  

}  

#endif  

#endif  

  

从“#ifdef_cplusplus”这个预处理引导命令可以看出,该文件既可由C 编译器编译,亦可由C++编译器编 

译。第一个#include 命令包括 jni。h——一个头文件,作用之一是定义在文件其余部分用到的类型; 

JNIEXPORT 和JNICALL 是一些宏,它们进行了适当的扩充,以便与那些不同平台专用的引导命令配合; 

JNIEnv,jobject 以及jstring则是JNI 数据类型定义。  

  

2。 名称管理和函数签名  

JNI 统一了固有方法的命名规则;这一点是非常重要的,因为它属于虚拟机将 Java 调用与固有方法链接起来 

的机制的一部分。从根本上说,所有固有方法都要以一个“Java ”起头,后面跟随 Java 方法的名字;下划线 

字符则作为分隔符使用。若Java 固有方法“过载”(即命名重复),那么也把函数签名追加到名字后面。在 

原型前面的注释里,大家可看到固有的签名。欲了解命名规则和固有方法签名更详细的情况,请参考相应的 

JNI 文档。  



                                                                                            651 


…………………………………………………………Page 653……………………………………………………………

  

3。 实现自己的 DLL  

此时,我们要做的全部事情就是写一个C 或 C++源文件,在其中包含由 javah 生成的头文件;并实现固有方 

法;然后编译它,生成一个动态链接库。这一部分的工作是与平台有关的,所以我假定读者已经知道如何创 

建一个DLL。通过调用一个Win32 API,下面的代码实现了固有方法。随后,它会编译和链接到一个名为 

MsgImpl。dll 的文件里:  

  

#include   

#include 〃ShowMsgBox。h〃  

  

BOOL APIENTRY DllMain(HANDLE hModule;   

  DWORD dwReason; void** lpReserved) {  

  return TRUE;  

}  

JNIEXPORT void JNICALL   

Java_ShowMsgBox_ShowMessage(JNIEnv * jEnv;   

  jobject this; jstring jMsg) {  

  const char * msg;  

  msg = (*jEnv)…》GetStringUTFChars(jEnv; jMsg;0);  

  MessageBox(HWND_DESKTOP; msg;   

    〃Thinking in Java: JNI〃;  

    MB_OK | MB_ICONEXCLAMATION);  

  (*jEnv)…》ReleaseStringUTFChars(jEnv; jMsg;msg);  

}  

  

若对Win32 没有兴趣,只需跳过MessageBox()调用;最有趣的部分是它周围的代码。传递到固有方法内部的 

自变量是返回Java 的大门。第一个自变量是类型 JNIEnv 的,其中包含了回调JVM 需要的所有挂钩(下一节 

再详细讲述)。由于方法的类型不同,第二个自变量也有自己不同的含义。对于象上例那样的非 static方法 

 (也叫作实例方法),第二个自变量等价于C++的“this ”指针,并类似于Java 的“this ”:都引用了调用 

固有方法的那个对象。对于static方法,它是对特定 Class 对象的一个引用,方法就是在那个 Class 对象里 

实现的。  

剩余的自变量代表传递到固有方法调用里的Java 对象。主类型也是以这种形式传递的,但它们进行的“按 

值”传递。  

在后面的小节里,我们准备讲述如何从一个固有方法的内部访问和控制JVM,同时对上述代码进行更详尽的 

解释。  



A。1。2  访问 JNI 函数:JNIEnv 自变量  



利用JNI 函数,程序员可从一个固有方法的内部与 JVM 打交道。正如大家在前面的例子中看到的那样,每个 

JNI 固有方法都会接收一个特殊的自变量作为自己的第一个参数:JNIEnv 自变量——它是指向类型为 

JNIEnv_的一个特殊 JNI 数据结构的指针。JNI 数据结构的一个元素是指向由 JVM 生成的一个数组的指针;该 

数组的每个元素都是指向一个 JNI 函数的指针。可从固有方法的内部发出对JNI 函数的调用,做法是撤消对 

这些指针的引用(具体的操作实际很简单)。每种 JVM 都以自己的方式实现了 JNI 函数,但它们的地址肯定 

位于预先定义好的偏移处。  

利用JNIEnv 自变量,程序员可访问一系列函数。这些函数可划分为下述类别:  

■获取版本信息  

■进行类和对象操作  

■控制对Java 对象的全局和局部引用  

■访问实例字段和静态字段  

■调用实例方法和静态方法  

■执行字串和数组操作  

■产生和控制Java 异常  

JNI 函数的数量相当多,这里不再详述。相反,我会向大家揭示使用这些函数时背后的一些基本原理。欲了 



                                                                               652 


…………………………………………………………Page 654……………………………………………………………

解更详细的情况,请参阅自己所用编译器的JNI 文档。  

若观察一下 jni。h头文件,就会发现在#ifdef _cplusplus 预处理器条件的内部,当由C++编译器编译时, 

JNIEnv_结构被定义成一个类。这个类包含了大量内嵌函数。通过一种简单而且熟悉的语法,这些函数让我们 

可以从容访问JNI 函数。例如,前例包含了下面这行代码:  

(*jEnv)…》ReleaseStringUTFChars(jEnv; jMsg;msg);  

它在C++里可改写成下面这个样子:  

jEnv…》ReleaseStringUTFChars(jMsg;msg);  

大家可注意到自己不再需要同时撤消对jEnv 的两个引用,相同的指针不再作为第一个参数传递给JNI 函数调 

用。在这些例子剩下的地方,我会使用C++风格的代码。  

  

1。 访问Java 字串  

作为访问JNI 函数的一个例子,请思考上述的代码。在这里,我们利用 JNIEnv 的自变量jEnv 来访问一个 

Java 字串。Java 字串采取的是Unicode 格式,所以假若收到这样一个字串,并想把它传给一个非 Unicode 函 

数(如printf() ),首先必须用JNI 函数GetStringUTFChars()将其转换成 ASCII 字符。该函数能接收一个 

Java 字串,然后把它转换成 UTF…8 字符(用 8 位宽度容纳 ASCII 值,或用 16 位宽度容纳 Unicode;若原始字 

串的内容完全由ASCII 构成,那么结果字串也是ASCII)。  

GetStringUTFChars 是JNIEnv 间接指向的那个结构里的一个字段,而这个字段又是指向一个函数的指针。为 

访问JNI 函数,我们用传统的C 语法来调用一个函数 (通过指针)。利用上述形式可实现对所有JNI 函数的 

访问。  



A。1。3  传递和使用 Java 对象  



在前例中,我们将一个字串传递给固有方法。事实上,亦可将自己创建的 Java 对象传递给固有方法。  

在我们的固有方法内部,可访问已收到的那些对象的字段及方法。  

为传递对象,声明固有方法时要采用原始的Java 语法。如下例所示,MyJavaClass 有一个 public (公共)字 

段,以及一个public 方法。UseObjects 类声明了一个固有方法,用于接收 MyJavaClass 类的一个对象。为 

调查固有方法是否能控制自己的自变量,我们设置了自变量的 public 字段,调用固有方法,然后打印出 

public 字段的值。  

  

class MyJavaClass {  

  public void divByTwo() { aValue /= 2; }  

  public int aValue;  

}  

  

public class UseObjects {  

  public static void main(String '' args) {  

    UseObjects app = new UseObjects();  

    MyJavaClass anObj = new MyJavaClass();  

    anObj。aValue = 2;  

    app。changeObject(anObj);  

    System。out。println(〃Java: 〃 + anObj。aValue);  

  }  

  private native void   

  changeObject(MyJavaClass obj);  

  static {  

    System。loadLibrary(〃UseObjImpl〃);  

  }  

}  

  

编译好代码,并将。class 文件传递给 javah 后,就可以实现固有方法。在下面这个例子中,一旦取得字段和 

方法 ID,就会通过 JNI 函数访问它们。  

  

JNIEXPORT void JNICALL  



                                                                                       653 


…………………………………………………………Page 655……………………………………………………………

Java_UseObjects_changeObject(  

  JNIEnv * env; jobject jThis; jobject obj) {  

  jclass cls;  

  jfieldID fid;  

  jmethodID mid;  

  int value;  

  cls = env…》GetObjectClass(obj);  

  fid = env…》GetFieldID(cls;  

        〃aValue〃; 〃I〃);  

  mid = env…》GetMethodID(cls;  

        〃divByTwo〃; 〃()V〃);  

  value = env…》GetIntField(obj; fid);  

  printf(〃Native: %dn〃; value);  

  env…》SetIntField(obj; fid; 6);  

  env…》CallVoidMethod(obj; mid);  

  value = env…》GetIntField(obj; fid);  

  printf(〃Native: %dn〃; value);  

}  

  

除第一个自变量外,C++函数会接收一个 jobject,它代表Java 对象引用“固有”的那一面——那个引用是 

我们从 Java 代码里传递的。我们简单地读取 aValue,把它打印出来,改变这个值,调用对象的divByTwo() 

方法,再将值重新打印一遍。  

为访问一个字段或方法,首先必须获取它的标识符。利用适当的JNI 函数,可方便地取得类对象、元素名以 

及签名信息。这些函数会返回一个标识符,利用它可访问对应的元素。尽管这一方式显得有些曲折,但我们 

的固有方法确实对Java 对象的内部布局一无所知。因此,它必须通过由 JVM 返回的索引访问字段和方法。这 

样一来,不同的JVM 就可实现不同的内部对象布局,同时不会对固有方法造成影响。  

若运行 Java 程序,就会发现从 Java 那一侧传来的对象是由我们的固有方法处理的。但传递的到底是什么 

呢?是指针,还是Java 引用?而且垃圾收集器在固有方法调用期间又在做什么呢?  

垃圾收集器会在固有方法执行期间持续运行,但在一次固有方法调用期间,我们的对象可保证不会被当作 

 “垃圾”收集去。为确保这一点,事先创建了“局部引用”,并在固有方法调用之后立即清除。由于它们的 

 “生命期”与调用过程息息相关,所以能够保证对象在固有方法调用期间的有效性。  

由于这些引用会在每次函数调用的时候创建和破坏,所以不可在static 变量中制作固有方法的局部副本(本 

地拷贝)。若希望一个引用在函数存在期间持续有效,就需要一个全局引用。全局引用不是由JVM 创建的, 

但通过调用特定的 JNI 函数,程序员可将局部引用扩展为全局引用。创建一个全局引用时,需对引用对象的 

 “生存时间”负责。全局引用(以及它引用的对象)会一直留在内存里,直到用特定的JNI 函数明确释放了 

这个引用。它类似于C 的malloc()和 free()。  



A。1。4 JNI 和 Java 异常  



利用 JNI,可丢弃、捕捉、打印以及重新丢弃Java 异常,就象在一个 Java 程序里那样。但对程序员来说, 

需自行调用专用的JNI 函数,以便对异常进行处理。下面列出用于异常处理的一些JNI 函数:  

■Throw():丢弃一个现有的异常对象;在固有方法中用于重新丢弃一个异常。  

■ThrowNew():生成一个新的异常对象,并将其丢弃。  

■ExceptionOccurred():判断一个异常是否已被丢弃,但尚未清除。  

■ExceptionDescribe():打印一个异常和堆栈跟踪信息。  

■ExceptionClear():清�
返回目录 上一页 下一页 回到顶部 赞(11) 踩(15)
快捷操作: 按键盘上方向键 ← 或 → 可快速上下翻页 按键盘上的 Enter 键可回到本书目录页 按键盘上方向键 ↑ 可回到本页顶部!
温馨提示: 温看小说的同时发表评论,说出自己的看法和其它小伙伴们分享也不错哦!发表书评还可以获得积分和经验奖励,认真写原创书评 被采纳为精评可以获得大量金币、积分和经验奖励哦!