7.4 可移植程序的约定
Java 最早使用的宣传语之一是:“一次编写,到处运行。”这个宣传语强调了,使用 Java 可以轻松写出可移植的程序,但 Java 程序仍然有可能无法自动在所有 Java 平台中成功运行。下述技巧有助于避免移植性问题。
- 本地方法
可移植的 Java 代码可以使用 Java 核心 API 中的任何方法,包括本地方法。但是,在可移植的代码中不能定义本地方法。就其本质而言,本地方法必须移植到每一种新平台中,因此直接违背了 Java“一次编写,到处运行”的承诺。
Runtime.exec()方法
可移植的代码很少允许调用 Runtime.exec() 方法派生进程,或者在本地系统中执行外部命令,因为无法保证执行的操作系统本地命令在所有平台中都存在或表现一致。在可移植的代码中只有一种情况能使用 Runtime.exec() 方法——允许用户指定要执行的命令,可以在运行时输入,也可以在配置文件或首选项对话框中指定。
System.getenv()方法
使用 System.getenv() 方法的代码一定不可移植。
- 没有文档的类
可移植的 Java 代码只能使用 Java 平台中有文档的类和接口。多数 Java 实现都包含了一些没有文档的公开类,这些类虽是实现的一部分,但不是 Java 平台规范的一部分。没什么能阻止程序使用并依赖这些没有文档的类,但这么做可能会导致程序不可移植,因为无法保证所有 Java 实现和所有平台中都有这些类。
在这些类中要特别注意 sun.misc.Unsafe 类,这个类提供了一些“不安全”的方法,可以让开发者避开 Java 平台的一些重要限制。在任何情况下,开发者都不应该直接使用 Unsafe 类。
java.awt.peer包
java.awt.peer 包中的接口是 Java 平台的一部分,但其文档只说明了如何在 AWT 系统中使用。直接使用这些接口的应用不可移植。
- 某个实现特有的特性
可移植的代码绝对不能依赖某个实现特有的特性。例如,微软提供了一个 Java 运行时系统,这个系统包含一些 Java 平台规范中没有定义的方法。使用这些扩展功能的程序显然不能移植到其他平台。
- 个实现特有的缺陷
就像不能依赖某个实现特有的特性一样,可移植的代码也绝对不能依赖某个实现特有的缺陷。如果类或方法的表现和规范中所述的有所不同,可移植的程序就不能依赖这种行为,因为在不同的平台可能有不同的表现,而且最终可能会被修复。
- 某个实现特有的行为
有时,不同的平台和不同的实现会有不同的行为,根据 Java 规范,这种差异是合法的。可移植的代码绝对不能依赖某种特定的行为。例如,Java 规范没有规定具有相同优先级的程序能否共享 CPU,也没有规定长时间运行的线程能不能排挤具有相同优先级的其他线程。如果应用假定某种行为,可能无法在全部平台中正常运行。
- 标准扩展
可移植的代码可以依赖 Java 平台的标准扩展,不过,如果这么做,要清楚地指出用了哪些扩展,而且在没有安装这些扩展的系统中运行时要输出适当的错误消息,利落地退出。
- 完整的程序
所有可移植的 Java 程序都必须是完整的,而且要自成一体:除了核心平台和标准扩展类之外,必须提供用到的所有类。
- 定义系统类
可移植的 Java 代码决不能在任何系统包或标准扩展包中定义类。这么做会破坏包的保护界线,而且会暴露包可见的实现细节。
- 硬编码文件名
可移植的程序不能使用硬编码的文件名或目录名,因为不同的平台使用十分不同的文件系统组织方式,而且使用不同的目录分隔符。如果要使用文件或目录,让用户指定文件名,至少也要让用户指定文件所在的基目录。这个操作可在运行时完成,在配置文件或程序的命令行参数中指定文件名。需要把文件名或目录名连接到目录名上时,要使用 File() 构造方法或 File.separator 常量。
- 换行符
不同的系统使用不同的字符或字符序列做换行符。在程序中不要把换行符硬编码成 \n、\r 或 \r\n,而要使用 PrintStream 或 PrintWriter 类中的 println(),这个方法换行时会自动使用适用于当前平台的换行符,或者使用系统属性 line.separator 的值也行。在 java.util.Formatter 及相关类的 printf() 和 format() 方法中,还可以使用“%n”格式化字符串。
