您是一位正在寻求将 .net 电子商务应用程序移植到 Java 2 Platform EntERPrise Edition(J2EE)的 .NET 开发人员吗?或者,您是否需要从头编写您的第一个 J2EE 应用程序?不管是哪一种情况,本路标图都是为您准备的。它是专门为想要迅速转到 J2EE 的 .NET 开发人员准备的,J2EE 是使用 Java 语言开发 Web 和其他类型的分布式应用程序的一个平台无关的开放标准集。我们将向您介绍如何使用 Java 语言来编程和开发 J2EE Web 应用程序。更好的是,我们将把这些信息与您已从编写 .NET 应用程序的经验中知道的概念和技术联系起来。 为何要转向 J2EE? 如果您不是十分渴望冒险投入一种新的开发环境,可以考虑以下这些 J2EE 好处: 选择,更多的选择:由于 J2EE 是一个定义良好的标准集,您在部署自己的代码时有许多 J2EE 实现可供选择。只要坚持使用标准 API 和避免厂商专用的扩展,应用程序无需编码变更就能在各种各样的 J2EE 实现上运行。 我们是在说选择吗?:J2EE 实现在从大型机到 Wintel、Unix 和 Linux 的各种平台上可用。您可以编写应用程序一次,然后在各种不同的平台上部署它。 我们不能就安于现状吗?:J2EE 包括一个用于访问许多诸如 CICS、IMS、ERP 和 CRM 这样的遗留 EIS 系统的标准 API。它还包括 Web 服务支持,因此您可以集成 .NET 系统和支持行业 Web 服务标准的其他系统。J2EE 还包括标准消息 API 支持(Java Message Service,JMS),以及一个用于访问关系数据库的 API(Java Database Connectivity,JDBC)。这样广泛的选择允许您集成各种现有的系统,而不会损失您对它们的投资。 机房不再烟雾缭绕:来自世界各地的专家通过 Java Community Process(JCP)开发 J2EE 规范。JCP 发布了初步的规范草案以供公众评论。即使您不主动参与,也总是会知道哪些未来的规范正在筹备之中。该规范还包括一个参考实现,您可以在决定实现它之前使用它来检查新技术。 J2EE 简介 J2EE、即 Java 2 Enterprise Edition,是一个由许多与使用 Java 语言开发分布式应用程序相关的组件组成的规范。您可以使用 J2EE 组件来编写基于 Web 的应用程序和传统的客户机-服务器应用程序,以及使用标准的 API 来连接到诸如关系数据库等遗留资源。如果您来自 asp.NET 开发背景, Java Servlets和 JavaServer Pages(JSP)技术就是对您最有用的组件。 Java Servlets Java Servlets 是作为诸如 IIS 或 Apache Web Server 等 Web 服务器的扩展来运行的 Java 类。Java Servlet 类似于 ISAPI 过滤器,即 ASP.NET HttpHandler 类,或类似于 CGI-bin 程序/脚本。Java Servlet 在客户端浏览器直接或间接地调用一个专门配置的 URL 时运行。servlet 能够访问 HTTP 请求中的所有信息,并且能够通过提供返回给客户端的内容来直接处理该请求。或者,servlet 能够将客户端浏览器重定向到另一个资源。大多数 J2EE Web 应用程序都主要把 servlet 用作 HTML 表单的目标以处理用户输入,然后作相应的处理。响应页面的生成通常委托给 JSP 页面。 JavaServer Pages 技术 JSP 页面类似于 ASP.NET 页面。也就是说,它们也是包含脚本元素的 HTML 页面,这些脚本在用户请求该页面时在服务器上运行。ASP.NET 页面和 JSP 页面之间的一个关键区别在于,ASP.NET 页面使用某种 .NET 语言(比如 C# 和 VB.NET)作为脚本语言,而 JSP 页面使用 Java 语言。典型的 JSP 页面包含 Java 代码片断和 JSP 规范中定义的一些特殊的类 HTML 标签,它们与标准 HTML 交织在一起以提供静态和动态内容的组合。Java Servlet 和 JSP 页面之间的区别在概念上类似于 ASP.NET HttpHandler 类和 ASP.NET 页面之间的区别。在两种情况下,前者都是可用于直接或间接地向其他资源发送 HTML 的一段代码,后者都是一个可以包含嵌入代码的 HTML 文档。 Web 服务器和应用服务器 如果熟悉 ASP.NET 页面,您应该知道运行这些页面的 ASP.NET 运行库――它配合 IIS 工作。您还会向 Web 应用程序添加自己的 HttpHandler 类以及托管的和非托管的组件,ASP.NET 运行库也会调用这些组件。这使得在 IIS 上部署 Web 应用程序很容易。但是它把您限制在 Windows 平台上,即唯一能够运行 IIS 的平台。J2EE 使用一种不同的方法,因为它的设计目标就是运行在各种不同的操作系统上(包括 Windows)。与尝试将运行 Java Servlet 和 JSP 页面的代码直接嵌入 Web 服务器不同,它使用一个称为 应用服务器的单独服务器组件来运行它们。大多数应用服务器(比如 IBM WebSphere Application Server)还有一个单独的插入组件,用于桥接应用服务器和特定的 Web 服务器。例如,WebSphere Application Server 附带了针对 IIS 和 Apache Web server 的单独插件。这样允许您在运行 J2EE 组件时使用自己选择的 Web 服务器。 应用服务器作为单独的可插入服务器组件这种角色带来了多个优点: Web 服务器选择:您不会被限定使用某个 Web 服务器来提供 HTML 页面服务。您可以继续使用自己最喜欢的 Web 服务器来用于此目的,并且使用任何应用服务器来处理 Java Servlet 和 JSP 页面。这种能力在您将 ASP.NET 应用程序移植到 J2EE 时特别有用。您可以继续运行 IIS 和 ASP.NET 运行库,并且分阶段地移植应用程序。您不需要一下子改写整个应用程序。 平台选择:你可以编写 J2EE 应用程序一次,然后在能够运行应用服务器的各种不同操作系统上部署它——包括 Windows、AIX 和 Linxu。您不会被限定于某个能够运行特定 Web 服务器的平台。 应用服务器厂商选择:由于行业标准规范定义了 Java Servlets 和 JavaServer Pages 技术,您可以编写 J2EE 应用程序一次,然后在多种应用服务器环境中部署它,比如 WebSphere Express 或 Apache Tomcat,Apache Tomcat 是一个流行的开放源代码应用服务器。J2EE 还定义了必须如何打包 Web 应用程序,因此您可以将自己开发的应用程序引入某个 J2EE 环境,无需更改代码或重新编译应用程序,就能将它重新部署到另一个应用服务器中。将应用程序部署到多个平台也是如此。 应用服务器如何运行 servlet 和 JSP 代码 正如前面提到过的,J2EE 规范强制一种部署 Java Servlet 和其他 J2EE 组件的标准格式。一个称为 部署描述符的 XML 文档就是这种标准格式的一部分。部署描述符包含从每个 servlet 到用于调用特定 servlet 的 URL 的映射。应用服务器使用部署描述符中的信息来决定应该针对给定的请求调用哪个 servlet。 应用服务器调用 JSP 页面的方式类似于 ASP.NET 运行库调用 ASP.NET 页面的方式。 J2EE 应用服务器将每个 JSP 页面转换为一个单独的特殊 servlet,它在该页面被请求时编译和运行。这个特殊的 servlet 保持加载在内存中,直至 JSP 文件改变。这样最大限度地降低了必须为每个 JSP 页面创建和编译一个类所导致的性能影响。 模型-视图-控制器(MVC)体系结构 J2EE 是在考虑到一个特定的应用程序结构的情况下开发的,这个结构称为 模型-视图-控制器(Model-View-Controller,MVC)。MVC 定义了三个应用程序层之间的清楚分离: 模型:应用程序的数据和业务规则集——通常称为应用程序的业务逻辑。 视图:应用程序的用户界面。 控制器:定义了应用程序如何对用户输入或模型层的变化作出反应——通常称为应用逻辑。 MVC 体系结构的优点 J2EE 中没有任何东西强迫您使用 MVC 体系结构来组织应用程序,但是存在这样做的许多很好理由。通过定义三个层之间的清楚分离,MVC 允许构成每个层的组件之间的松散耦合。这样使得组件更加可复用和更灵活。例如,假设您的需求之一是支持某个 Web 应用程序中相同数据的不同类型的视图,因为不同的部门需要数据库中相同数据的不同子集。您需要开发特定于每个必需子集的新的视图组件。如果视图逻辑和数据库访问代码是紧密耦合的——ASP.NET 页面就是将数据库访问代码和 HTML 交织在一起,那么每个视图都要包含数据库访问代码。维护重复的代码不仅需要大量的工作,而且还可能导致出错。MVC 体系结构在这种场景中使用数据库代码作为该模型的一部分,而不同的视图组件可以复用它。 J2EE 组件和 MVC 图 1 显示了我们到目前为止所讨论的 J2EE 组件如何映射到一个 MVC 体系结构。注意模型和视图之间不存在任何联系。控制器的功能是充当两者之间的中转站。 图 1. MVC 与 J2EE Web 应用程序 在典型场景中,用户提交一个其目标是一个 servlet 的 HTML。servlet 分析输入,并使用模型中的类来调用业务逻辑以满足该请求。然后 servlet 将结果传递给一个 JSP 页面,以便向用户显示那些结果。 JavaServer Faces 您或许使用过 Web Forms 来开发 ASP.NET 应用程序的用户界面和 UI 控制逻辑。 JavaServer Faces(JSF)规范提供了 J2EE 编程世界中的等价功能。像 Web Forms 一样,JSF 提供运行库组件,这些组件允许工具厂商为基于 Web 的 UI 开发提供拖放功能。它还允许厂商开发可供他们的开发工具使用的自定义组件。 要看到 JSF 的实际应用,可考察一下 WebSphere Studio 5.11 版中的系列工具(请参阅 参考资料)。WebSphere Studio 还有一个名为 Page Designer 的完全集成的工具,可以使用它通过拖放操作来可视化地开发 HTML 页面和 JSP 页面。Page Designer 已实现了 JavaServer Faces 规范,您在使用它时应该会看到一些熟悉的组件,比如 HTML 表单、DataGrids 和 DataList。您还会看到熟悉的“代码分离(code behind)文件(使用 Java 语言),它们包含特定页面的 UI 事件处理代码。 JSF 环境的一个杰出特性在于,它允许您将页面分组为符合逻辑的树形结构。例如,如果有一组包含多个表单的页面,那么 JSF 树形结构就很理想——特别是在用户可能基于先前的输入或基于某些用户特征,通过一条不同的路径经历那些页面的情况下。该结构为树中的每个页面指定一个合理的名称。您在自己的事件处理代码中使用这些名称来导航不同的分支,具体取决于特定的运行时条件。 其他 J2EE 技术 Java Servlets 和 JSP 技术为您提供用 Java 语言开发平台无关的 Web 应用程序所需要的工具。其他一些 J2EE 规范和组件为您带来更高级的功能: Enterprise JavaBeans(EJB)技术:企业组件(或者说 bean)存在三种形式: 会话 bean:类似于 .NET 中的 COM+ 服务的特殊 Java 类。像 COM+ 服务一样,会话 bean 在容器中运行,容器提供诸如声明事务管理、基于角色的安全、分布式环境中的无缝访问以及根据需要激活等服务。会话 bean 又存在两种形式: 无状态的:方法调用之间没有维护状态,因此您必须提供通过参数来调用某个方法时所需要的全部信息。无状态会话 bean 的优点在于,容器可以使用任何实例来服务于任何客户机调用。 有状态的:方法调用之间的状态得到保持,以便客户机总是与特定的实例相关联。有状态的会话 bean 的优点在于,客户机可以使用对话模式来与有状态的会话 bean 交互。当重新创建中间状态信息的成本很昂贵时,这是特别有用的。 实体 bean:可看作是 ADO.NET DataSets 的更高级实现的特殊 Java 类。虽然概念上类似于 DataSets,但是实体 bean 的实现更像是 COM+ 服务。像 DataSets 一样,它们是存储在关系数据库或其他持久存储中的持久数据的对象表示,并且可以封装数据模型中的表之间的关系。像 COM+ 服务一样,它们在容器中运行,容器提供诸如声明事务管理、基于角色的安全、分布式环境中的无缝访问等服务。实体 bean 是共享对象,因此容器还要处理并发控制,并确保底层的持久数据保持其 ACID(Atomicity、Consistency、Isolation 和 Durability——原子性、一致性、隔离性和持久性)属性。(与会话 bean 不同,实体 bean 是共享对象,因此多个客户机可以并发地访问单个实例。)简而言之,实体 bean 防止您直接访问底层的持久存储。无需作出任何应用程序更改,就可以将它们部署到各种不同的持久存储中。(也就是说,无需改动任何代码,就可以在部署时将实体 bean 映射到它的持久存储。) 消息驱动的 bean:充当 JMS 相容的消息中间件的监听器的特殊 Java 类。JMS 是用于访问消息队列的标准 Java API。可以将消息驱动的 bean 配置为指向特定的消息队列;容器会在消息到达该队列中时激活它们。消息驱动的 bean 给应用程序提供了在消息到达时调用的应用逻辑。每种 J2EE 1.3 相容的应用服务器都必须提供一个 JMS 实现,不过您也可以使用诸如 WebSphere MQ(以前名为 MQSeries)这样的流行消息中间件。 Java 连接器体系结构(Java Connector Architecture,JCA):用于访问许多诸如 CICS、IMS、ERP 和 CRM 这样的遗留 EIS 系统的标准 API。JCA 把您解放出来,从此不必再学习针对每种 EIS 系统的单独 API。 Java 编程基础 在深入某些 J2EE 编程概念之前,我们首先向您介绍 Java 编程语言。可以使用 Java 语言来编写服务器端应用程序以及具有 GUI 的桌面应用程序。本文假设您想要在服务器端使用 Java 语言来补充一个基于 Web 的界面,因此我们将跳过 CUI 编程环境,而是重点关注该平台的非可视化方面。我们首先介绍 Java 软件开发包(Java Software Development Kit,SDK),然后向您展示如何使用 Java 代码来编写历史悠久的 Hello World 应用程序。之后,我们将更深入地介绍两种最流行的 .NET 语言(Visual Basic .NET 和 C#)与 Java 语言之间的区别。感谢 Scott Stricker 对本节和下面两节所做的贡献,这些内容节选自他的教程“Java programming for C/C++ developers”(请参阅 参考资料)。 Java SDK 简介 Java SDK 是编写和运行 Java 程序所需要的一组命令行工具和包。Java 程序通过即时(Just In Time,JIT)编译器编译为平台无关的字节代码,然后该字节代码可以在运行时编译为本机代码。其中最重要的工具是 Java 编译器(javac.exe)和 Java 解释器(java.exe),后者用于运行 Java 程序。该 SDK 还包括基础的类(称为 Java 平台),它们提供您开始编写应用程序所需要的基本功能和 API。 Sun Microsystems 已发布了针对其每个主要 Java 平台版本的 SDK。我们推荐您获取最新的 SDK 版本(Java 1.4.2)来完成本教程的学习。Java SDK 是免费可用的。如果您还没有拥有它,请马上下载它(请参阅 参考资料)。 你可以在线参考 Java 2 Standard Edition(J2SE)API 文档(请参阅 参考资料)。它是一个 HTML 文档集合,您可以在标准的 Web 浏览器中浏览它们。该 API 文档是必备的参考资料,您或许会频繁地使用它。 安装 SDK 在下载 SDK 之后,您需要将它安装到机器上。安装过程很简单。如果安装程序让您在典型安装和自定义安装之间的选择,请选择典型安装。(仅当您准确知道自己在做什么,并且不希望给机器增加负载时,才应该选择自定义安装。)安装过程通常提供安装标准 Java 平台类的源代码的选项。如果机器上有充足的磁盘空间,我们推荐您接受这个选项。这些文件将为您提供一个机会,让您考察组成 Java 语言和标准 API 的类的实现。它们设计和实现得特别好,您可以从中学到很多知识。 在安装 SDK 之后,您可能需要配置它,以使它能在您的系统上工作。如何配置 SDK 取决于您的操作系统和所使用的 SDK 版本。该 SDK 包括完整的安装和配置说明。 第一个 Java 程序 现在您可以编写自己的第一个 Java 程序——无处不在的 Hello World 程序。打开文本编辑器,输入您从清单 1 中准确看到的源代码。 清单 1. Hello World 程序
Java 语言是大小写敏感的,诸如 class 和 public 这样的关键字总是小写的。可以使用任意大小写字母组合来表示变量和方法名称,只要您在整个给定的类中一致地使用它们。当您完成键入时,请把代码保存为一个名为 HelloWorld.java 的文件。对于这个文件名,您没有其他的选择。Java 源文件使用 .java 扩展名,并且每个 Java 源代码文件都 必须与您在其中定义的类具有完全相同的名称。我们已经多次重申了:大小写是很重要的,因此像 HELLOWORLD.JAVA 和 Helloworld.java 这样的文件名将导致编译错误。 您可以将 HelloWorld.java 保存在机器上任何适当的目录中。您需要转到这个目录来使用命令行工具,因此要确保该目录便于访问。 编译程序 现在就准备好可以编译 HelloWorld 程序了。SDK 附带的 Java 语言编译器是一个名为 javac.exe 的命令行应用程序。要编译一个 Java 源代码文件,您只需将 .java 文件的名称传递给 javac.exe 程序。要编译这个 HelloWorld 程序,请打开一个命令提示符窗口,将目录切换到您保存 HelloWorld.java 文件的位置。然后执行下面这个命令:
像 Visual Basic .NET 或 C# 编译器一样,Java 编译器可能生成任意数目的错误。自然地,您需要纠正所有错误,Java 编译器才会成功地编译 HelloWorld 程序。成功的编译将生成一个名为 HelloWorld.class 的类文件。这个文件代表您将在 Java 解释器中运行的可执行文件。 运行程序 SDK 附带的 Java 语言解释器是一个名为 java.exe 的命令行应用程序。要运行一个 Java 字节代码可执行文件,您只需将该 java 程序的名称传递给 java 解释器。在使用 Java 解释器时不要指定 .class 扩展名。解释器仅接受类文件,因此添加 .class 扩展名将产生一个错误。要运行这个 HelloWorld 程序,请打开一个命令提示符窗口,将目录切换到您编译 HelloWorld.java 文件的位置。这个字节代码可执行文件 HelloWorld.class 应该就在该目录中。然后执行下面这个命令:
Java 解释器尝试执行 HelloWorld 程序的 main() 方法。Java 程序的 main() 方法等价于控制台应用程序使用的 Visual Basic .NET Sub Main() 或 C# Main() 方法。返回类型为 void 的 Java 方法等价于 Visual Basic .NET 中的 Sub 。具有其他返回类型的方法等价于 Visual Basic .NET 中的 Function 。 Java 解释器可能会报告运行时错误,这通常会终止程序执行。与在 Visual Basic .NET 和 C# 中一样,Java 运行时错误要比编译时错误更难于调试,不过没有编译时错误出现得那么频繁。像 .NET 程序一样,Java 程序在一个托管环境中执行,因此您可以宽限地处理运行时错误。 Java 语言与 Visual Basic .NET 之比较 现在让我们考察一下 Java 语言与 Visual Basic .NET 之间的区别。(如果您纯粹是个 C# 开发人员,可以跳到本文后面的 Java 语言与 C# 之比较小节。) 类型 Java 语言和 Visual Basic .NET 都是单继承的面向对象语言,它们具有一个作为其他所有类的基类的类:Visual Basic .NET 中的 System.Object 和 Java 语言中的 java.lang.Object 。这意味着对于您开发的类层次,两种语言是相似的。如果沿着层次树往上,您最终会到达对应的根类。 Java 语言使用了原始类型(primitive type)的概念,它们非常类似 C 和 C++ 中的对应概念。它们不是任何类层次的一部分,也不具有任何方法。此外,当使用它们作为参数时,它们总是按值传递。表 1 列出了 Java 语言中的原始类型和它们在 Visual Basic .NET 中的等价类型: 表 1 Java 语言中的原始类型和它们在 Visual Basic .NET 中的等价类型
在 Java 语言中,每种原始类型具有一个对应的包装类,可以使用它将该类型作为对象而不是作为原始类型来处理。每个包装类具有一个构造函数,允许您根据原始类型中的数据创建该包装类型的一个实例。在 Visual Basic .NET 中,您可以隐式地将对应的类型转换为 Object 的一个实例,因此不需要在这种情形下使用包装类。清单 2 中的例子突出了它们之间的区别。 清单 2. 原始类型和它们的包装类
清单 2 中的 Java 原始类型被显式地包装在 Object 的一个派生类中,而在 Visual Basic .NET 中,这种转换是隐式的。 注意在 Java 语言中,原始类型是按值传递的。对象实例内部地使用指针来表示,这些指针也是按值传递的(也就是指针的值,而不是指针所指向的内容)。在 Visual Basic .NET 中,默认的行为是值类型按值传递,引用类型按引用传递。Java 语言中没有等价于 Visual Basic .NET ByRef 和 ByVal 的关键字。表 2 显示了等价于 Visual Basic .NET 类型但是没有映射到 Java 原始类型的 Java 语言类型。 表 2 常用的 Java 系统类和它们在 Visual Basic .NET 中的等价类型
Java 语言与 Visual Basic .NET 不同,它没有结构和模块。所有 Java 代码都必须是某个类或接口的一部分。如果要移植包含模块和结构的 Visual Basic .NET 代码,您必须将它们改写为 Java 类。 继承和接口 两种语言都仅允许单继承,但是都允许实现多个接口。这在两种语言中的实现方式有所不同。例如,清单 3 显示了如何从一个名为 Parent 的类派生子类并实现两个分别名为 IOne 和 ITwo 的接口。 清单 3 派生子类并实现接口
在 Java 语言中, extends 关键字表示继承, implements 关键字表示实现一个或多个接口,接口之间用逗号分隔。 包 如果熟悉 Visual Basic .NET 中的名称空间,那么对 Java 语言中的包就不应该有任何概念问题。像名称空间一样,包允许您组织类以避免当您在不同上下文中使用相同名称的类时存在的名称冲突。名称空间的逻辑类分组促进了类复用,使得导航大量的类成员更加容易。在 Java 语言中,您需要通过两种方式处理这种分组:如何将类声明为特定包的成员,以及如何引用特定包中的类。清单 4 中的例子说明了名称空间和包的处理。 清单 4. 名称空间和包
在 Java 语言中,约定的做法是包名称全部使用小写,并使用反向的 Internet 域名作为每个包的前缀。上述清单使用 Java import 语句(类似 Visual Basic .NET Imports 语句)来引用 Foo2 中的 Foo 类,而没有使用完全限定的名称(Visual Basic .NET 中的 MyApp.Utilities.Foo 或 Java 代码中的 com.mycompany.myapp.utilities.Foo )。 打包以供复用 在 Visual Basic .NET 中,您可以编写一组类,把它们生成程序集(assembly),程序集在文件系统中表示为动态链接库。其他类可以引用这些程序集,以便使用它们所包含的类。Java 语言也允许把一组类打包到一个称为“Java 归档(Java Archive,JAR)文件”的文件中以供复用。您可以将一组类组合到一个带有 .jar 扩展名的文件中,然后在其他类中引用该 JAR 文件。具有 .jar 扩展名的文件是标准的 zip 文件,可以使用 WinZip 或其他压缩实用程序来操作它们。不过为方便起见,Java SDK 包含了一个名为 jar.exe 的实用程序(在 Windows 平台上),可以使用它来把一组类组合到一个具有 .jar 扩展名的文件中。 在考察使用 jar.exe 实用程序的例子之前,理解包名称和 Java 平台用于生成类以及在运行时加载它们的目录结构之间的关系是很重要的。请考虑一个名为 Test 的类,它的源代码在一个名为 Test.java 的文件中。如果将 Test.java 定义为包 com.mycompany.test 的一部分,那么编译器会为结果类模块创建一个目录树。该目录树就建立在包名称的基础上。此例中该目录树为 commycompanytest,并且包名称中的点号被转换为目录边界(分隔符 )。 现在打开一个命令提示符窗口,然后创建一个目录(例如 c:javapack)。切换到该目录( cd javapack )。使用您最喜欢的文本编辑器,将清单 5 中的代码添加到一个名为 Test.java 的新文件中。 清单 5. 使用包的例子
现在使用以下命令编译 Test.java。( -d 选项应该指向您为这个例子创建的目录):
现在在 c:javapack 目录下应该有一个名为 com 的子目录。事实上,您可以看到编译所产生的 comTest.class 文件的完全限定名称是 Test.class。注意包名称( com.mycompany.test )是如何转换为对应目录结构(commycompanytest)的,该目录结构以您使用 -d 选项指定的目录作为根目录。 下面我们将展示如何打包 Test.class 以方便其他类复用。从 c:javapack 目录运行以下命令:
这个命令将创建一个名为 Test.jar 的文件,它包含 com 子目录下的所有类。 运行以下命令来使用 Test.jar 文件中的类:
注意您必须使用完全限定的类名称来从命令行运行该命令,而且还要注意使用 -classpath 选项来指向 Test.jar 文件的方式。或者,您可以把 Test.jar 文件添加到 CLASSPATH 环境变量中,该变量是分号分隔的 JAR 文件列表,以及 Java 编译器和 Java 虚拟机(Java virtual machine,JVM)用来寻找需要加载的类的目录列表。 访问修饰符 访问修饰符 public 、 private 和 protected 在两种语言中的工作方式大部分都是相同的。 在 Visual Basic .NET 中,默认的访问修饰符是 Friend 。在 Java 语言中,默认的访问权限是允许任何子类或相同包中的任何类访问当前类、字段或方法。这大致等价于 Visual Basic .NET 中的 Protected Friend 修饰符,该修饰符仅允许从相同程序集或从子类访问。 方法重载 Visual Basic .NET 中的子类如果要重载父内中的某个方法: 该方法一定 不 能在父类中使用 private 访问修饰符来声明。 该方法必须在父类中声明为 Overridable 。 子类中的方法必须与父类中的对应方法具有相同的名称、返回类型和参数签名。 子类中的方法必须使用 Overrides 关键字来声明。(您也可以使用 new 关键字,但是一般不推荐这样做。) 父类中的方法一定 不 能声明为 NotOverridable 。 Java 语言中方法重载的前提条件不太严格: 该方法一定 不 能在父类中使用 private 访问修饰符来声明。 子类中的方法必须与父类中的对应方法具有相同的名称、返回类型和参数签名。 父类中的方法一定 不 能声明为 final 。 这些区别的含义是,在 Java 代码中,子类中不可能包含与父类中的非私有方法具有相同名称和签名的方法而不会隐含地重载它。在 Visual Basic .NET 中,您必须显式地指明何时想要重载父类中的方法。还要注意,Java 语言中的 final 关键字或多或少地等价于 Visual Basic .NET 中的 NotOverridable 关键字。 异常处理 结构化的异常处理在两种语言中几乎完全相同。(两者都可以往后追溯到一份创始性的论文,即 Andrew Koenig 和 Bjarne Stroustrup 于 1990 年撰写的 Exception Handling for C++。) 两种语言都使用两种异常概念:应用程序生成的异常,以及系统运行库(Visual Basic .NET 的公共语言运行库,Java 语言的 JVM)生成的异常。 两种语言都具有 Exception 基类,应用程序异常和系统异常都是由它派生而来的。图 2 显示了每种语言中的 Exception 类层次。 图2. Java 语言和 Visual Basic .NET 中的 Exception 类 然而,两种语言的编译器对您的代码如何处理异常具有不同的预期。在 Visual Basic .NET 中,您可以选择捕获异常,或让它们沿调用堆栈向上传播到类的方法的调用者(和/或构造函数)。Java 语言允许同样的处理,但是对于未捕获的应用程序异常(也就是 java.lang.Exception 的子类),您必须显式地将它们作为方法声明的一部分来列出。因此 Java 编译器预期您或者自己捕获所有应用程序异常,或者告诉编译器关于未捕获的异常的信息。例如,假设 Foo 类的构造函数可能抛出一个应用程序异常,那么清单 6 中的 Visual Basic .NET 或 Java 代码对各自的编译器来说都不会有问题。 清单 6. 处理应用程序异常
然而,如果改变代码以使其不捕获异常,那么您就必须改变 Java 方法声明(如清单 7 所示)以避免编译器错误。 清单 7. 未捕获的应用程序异常
两种语言在这方面存在的另一个区别在于,在 Visual Basic .NET 中,每个捕获块的参数是可选的。如果省略它,那么所有异常都会被捕获。Java 语言不允许这样,而是允许一个等价功能( java.lang.Throwable ),它捕获所有异常类的父类,如清单 8 所示。 清单 8. 捕获所有异常
数组声明 Java 语言提供两种声明数组的方法;Visual Basic .NET 仅提供了一种方法。Java 数组工作起来很像 Visual Basic .NET 中的动态数组:必须通过一个显式的步骤来给它分配内存。清单 9 中的代码说明了这个区别。与在 Visual Basic .NET 中一样,Java 数组的下标从 0 开始。 清单 9. 声明数组
委托 Java 语言没有直接等价于 Visual Basic .NET 委托的结构。可以通过声明并实现一个具有单个方法定义的接口来模拟委托功能。 变量声明 Java 语言是强类型的,控制变量声明的规则等价于打开 Option Explicit 选项后的 Visual Basic .NET 规则。也就是说,您必须在使用变量之前声明它们。Java 语言不允许改变这个性质。 OnError GoTo Java 语言没有直接等价于 Visual Basic .NET 的 GoTo 语句的结构。不过,您可以使用异常处理机制相当容易地执行异常处理。 构造函数 像 Visual Basic .NET 一样,Java 类可以包含具有不同参数列表的不同构造函数。在 Visual Basic .NET 中,构造函数通过名为 New() 的 Sub 来声明。Java 语言构造函数的名称与类名称相同。清单 10 中的代码说明了这个区别。 清单 10:构造函数
属性(property) 等价于 Visual Basic .NET 属性的 Java 结构称为 字段(field)。在 Java 语言中,您不能将 getter 和 setter 定义为字段定义的一部分,但是可以向类中声明那些字段的地方添加 getter 和 setter。清单 11 中的代码说明了这个区别。 清单 11:属性
小结 Visual Basic .NET 的语法类似于以前的 Visual Basic 版本,但是最新的版本包括了许多可从 Java 语言中找到的面向对象特性。继承、接口和异常处理就是两种语言的实现存在相似性的一些方面。与以前版本的 Visual Basic 相比,这种相似性应该使您转向 Java 平台更加容易。我们提倡您首先把 Visual Basic .NET 小程序转换到 Java 语言。不要忘了使用 Java 平台文档,要查找功能上等价于 System... 名称空间中的 Visual Basic .NET 类的 Java 类,您会发现这些文档非常有用。 Java 语言与 C# 之比较 下面让我们考察 Java 语言和 C# 之间的区别。这两种语言具有许多相似之处,因此我们将重点关注其区别所在。 类型 Java 语言和 C# 都是单继承的面向对象语言,它们都具有一个作为其他所有类的基类的类:C# 中的 System.object 和 Java 语言中的 java.lang.Object 。这意味着对于您开发的类层次,两种语言是相似的。如果沿着层次树往上,您最终会到达对应的根类。 Java 语言使用了原始类型(primitive type)的概念,它们非常类似 C 和 C++ 中的对应概念。它们不是任何类层次的一部分,也不具有任何方法。此外,当使用它们作为参数时,它们总是按值传递。表 3 列出了 Java 语言中的原始类型和它们在 C# 中的等价类型: 表 3 Java 语言中的原始类型和它们在 C# 中的等价类型
在 Java 语言中,每种原始类型具有一个对应的包装类,可以使用它将该类型作为对象而不是作为原始类型来处理。每个包装类具有一个构造函数,允许您根据原始类型中的数据创建该包装类型的一个实例。 在 C# 中,您可以隐式地将对应的类型转换为 object 的一个实例,因此不需要在这种情形下使用包装类。清单 12 中的代码突出了它们之间的区别。 清单 12. 原始类型和它们的包装类的例子
清单 12 中的 Java 原始类型被显式地包装在 Object 的一个派生类中,而在 C# 中,这种转换是隐式的。(C# 中隐式的转换称为 装箱(boxing)。) 注意在 Java 语言中,原始类型是按值传递的,对象类型在内部使用指针来表示,它们也是按值传递的。C# 中的默认行为是相同的,不过该语言还包括 ref 关键字来强制将值类型作为引用来传递。 表 4 显示了一些等价于 C# 类型但是没有映射到 Java 原始类型的 Java 语言类型。 表 4 C# 类型和它们的 Java 等价类型
继承和接口 两种语言都仅允许单继承,但是都允许实现多个接口。这在两种语言中的实现方式有所不同。例如,清单 13 显示了如何从一个名为 Parent 的类派生子类,并实现两个分别名为 IOne 和 ITwo 的接口。 清单 13 派生子类并实现接口
注意在 Java 语言中, extends 关键字表示继承, implements 关键字表示一个或多个接口的实现。 包 如果熟悉 C# 中的名称空间,那么对 Java 语言中的包就不应该有任何概念问题。像名称空间一样,包允许您组织类以避免当您在不同上下文中使用相同名称的类时存在的名称冲突。名称空间的逻辑类分组促进了类复用,使得导航大量的类成员更加容易。在 Java 语言中,您需要通过两种方式处理这种分组:如何将类声明为特定包的成员,以及如何引用特定包中的类。清单 14 中的例子说明了名称空间和包的处理。 清单 14. 名称空间和包
注意在 Java 语言中,约定的做法是包名称全部使用小写,并使用反向的 Internet 域名作为每个包的前缀。上述清单使用 Java import 语句(类似 C# using 语句)来引用 Foo2 中的 Foo 类,而没有使用完全限定的名称(C# 中的 MyApp.Utilities.Foo 或 Java 语言中的 com.mycompany.myapp.utilities.Foo )。 打包以供复用 在 C# 中,您可以编写一组类,把它们生成程序集(assembly),程序集在文件系统中表示为动态链接库。其他类可以引用这些程序集,以便使用它们所包含的类。Java 语言也允许把一组类打包到一个称为 Java 归档(Java Archive,JAR)文件的文件中以供复用。您可以将一组类组合到一个带有 .jar 扩展名的文件中,然后在其他类中引用该 JAR 文件。具有 .jar 扩展名的文件是标准的 zip 文件,可以使用 WinZip 或其他压缩实用程序来操作它们。不过为方便起见,Java SDK 包含了一个名为 jar.exe 的实用程序(在 Windows 平台上),可以使用它来把一组类组合到一个具有 .jar 扩展名的文件中。 在考察使用 jar.exe 实用程序的例子之前,理解包名称和 Java 平台用于生成类以及在运行时加载它们的目录结构之间的关系是很重要的。请考虑一个名为 Test 的类,它的源代码在一个名为 Test.java 的文件中。如果将 Test.java 定义为包 com.mycompany.test 的一部分,那么编译器会为结果类模块创建一个目录树。该目录树就建立在包名称的基础上。此例中该目录树为 commycompanytest,并且包名称中的点号被转换为目录边界(分隔符 )。 现在打开一个命令提示符窗口,然后创建一个目录(例如 c:javapack)。切换到该目录( cd javapack )。使用您最喜欢的文本编辑器,将清单 15 中的代码添加到一个名为 Test.java 的新文件中。 清单 15. 使用包的例子
使用以下命令编译 Test.java。(注意 -d 选项应该指向您为这个例子创建的目录):
现在 c:javapack 目录下应该有一个名为 com 的子目录。事实上,您可以看到编译所产生的 comTest.class 文件的完全限定名称是 Test.class。 注意包名称( com.mycompany.test )是如何转换为对应目录结构(commycompanytest)的,该目录结构以您使用 -d 选项指定的目录作为根目录。 下面我们将展示如何打包 Test.class 以方便其他类复用。从 c:javapack 目录运行以下命令:
jar 命令将创建一个名为 Test.jar 的文件,它包含 com 子目录下的所有类。 执行以下命令来使用 Test.jar 文件中的类:
注意您必须使用完全限定的类名称来从命令行运行该命令,而且还要注意使用 -classpath 选项来指向 Test.jar 文件的方式。或者,您可以把 Test.jar 文件添加到 CLASSPATH 环境变量中,该变量是分号分隔的 JAR 文件列表,以及 Java 编译器和 Java 虚拟机(Java virtual machine,JVM)用来寻找需要加载的类的目录列表。 访问修饰符 访问修饰符 public 、 private 和 protected 在两种语言中的工作方式大部分都是相同的。在 C# 中,默认的访问权限是 private ;Java 语言中的默认行为是允许任何子类或相同包中的任何类访问当前类、字段或方法。这大致等价于 C# 中的 internal 关键字,该修饰符仅允许从相同程序集访问。 方法重载 C# 中的子类如果要重载父类中的某个方法: 该方法一定 不 能在父类中使用 private 访问修饰符(或没有访问修饰符)来声明。 该方法必须在父类中声明为 virtual 。 子类中的重载方法必须与父类中的方法具有相同的名称、返回类型和参数签名。 子类中的方法必须使用 Overrides 关键字来声明。(您也可以使用 new 关键字,但是一般不推荐这样做。) 父类中的方法一定 不 能声明为 sealed 。 Java 语言中方法重载的前提条件不太严格: 该方法一定 不 能在父类中使用 private 访问修饰符来声明。 子类中的方法必须与父类中的对应方法具有相同的名称、返回类型和参数签名。 父类中的方法一定 不 能声明为 final 。 这些区别的含义是,在 Java 代码中,子类中不可能包含与父类中的非私有方法具有相同名称和签名的方法而不会隐式地重载它。在 C# 中,您必须显式地指明何时想要重载父类中的方法。还要注意 Java 语言中的 final 关键字如何或多或少地等价于 C# 中的 sealed 关键字。 异常处理 结构化的异常处理在两种语言中几乎完全相同。(两者都可以往后追溯到一份创始性的论文,即 Andrew Koenig 和 Bjarne Stroustrup 于 1990 年撰写的 Exception Handling for C++。) 两种语言都使用了两种异常概念:应用程序生成的异常,以及系统运行库(C# 的公共语言运行库,Java 语言的 JVM)生成的异常。两种语言都具有 Exception 基类,应用程序异常和系统异常都是由它派生而来的。 图 3 显示了两种语言中的 Exception 类层次。 图3. Java 语言和 C# 中的 Exception 类层次 然而,两种语言的编译器对您的代码如何处理异常具有不同的预期。在 C# 中,您可以选择捕获异常,或让它们沿调用堆栈向上传播到类的方法的调用者(和/或构造函数)。Java 语言允许同样的处理,但是对于未捕获的应用程序异常(也就是 java.lang.Exception 的子类),您必须显式地将它们作为方法声明的一部分来列出。因此 Java 编译器预期您或者自己捕获所有应用程序异常,或者告诉编译器关于未捕捉的异常的信息。例如,假设 Foo 类的构造函数可能抛出一个应用程序异常,那么清单 16 中的 C# 或 Java 代码对各自的编译器来说都不会有问题。 清单 16. 处理应用程序异常
然而,如果改变代码以使其不捕获异常,那么您就必须改变 Java 方法声明(如清单 17 所示)以避免编译器错误。 清单 17. 未捕获的应用程序异常
两种语言在这方面存在的另一个区别在于,在 C# 中,每个捕获块的参数是可选的。如果省略它,那么所有异常都会被捕获。Java 语言不允许这样,而是允许一个等价功能( java.lang.Throwable ),它捕获所有异常类的父类,如清单 18 所示。 清单 18. 捕获所有异常
数组声明 Java 语言提供两种声明数组的方法;C# 仅提供了一种方法。清单 19 中的代码说明了这个区别。 清单 19. 声明数组
委托和索引器(indexer) Java 语言没有直接等价于 C# 委托的结构。您可以通过声明并实现一个具有单个方法定义的接口来模拟委托功能。 Java 语言也没有索引器;您需要将它们编写为常规的类方法。 操作符重载 Java 语言不允许操作符重载(这是一个从 C++ 借用来的 C# 特性)。您可以容易地编写方法来模拟操作符重载行为。 非安全模式 C# 的非安全模式允许您使用指针和内存插接块(pin bolck)来绕过垃圾收集。Java 运行库本身广泛使用了指针,但是 Java 语言没有指针,也没有等价的非安全模式。这样是为了遵循 Java 平台的“编写一次,随处运行”的哲学,它允许你安全地避免平台依赖性、内存泄露以及“失控(runaway)”代码。 小结 您的 C# 背景应该使得转向 Java 平台相当容易。本文中手把手的代码示例或许会让您认识到:这两种语言使用了相当类似的语法。它们在概念上也相当相似。继承、接口和异常处理就是这两种语言的实现几乎完全相同的一些方面。我们提倡您首先把一些 C# 小程序转换到 Java 语言。不要忘了使用 Java 平台文档,要查找功能上等价于 System... 名称空间中的 C# 类的 Java 类,您会发现这些文档非常有用。 (责任编辑:admin) |