管理Java类路径(Windows)

类路径是Java运行时与文件系统之间的连接。 它定义了编译器和解释器在何处查找要加载的.class文件。 基本思想是文件系统层次结构反映Java包层次结构,而类路径指定文件系统中的哪些目录充当Java包层次结构的根。

不幸的是,文件系统非常复杂,并且非常依赖平台,并且它们与Java软件包并不完全匹配。 在Windows上尤其如此。 Java是由UNIX黑客设计的,在许多方面,这意味着与Windows约定的同步还不够完美。 因此,多年来,类路径一直困扰着新用户和经验丰富的Java程序员。 它不是Java平台的漂亮部分。 正是这种烦人的故障使您无法在下午5点之前正常工作,试图调试一个顽固地拒绝解决方案的小问题。

像Eclipse这样的优秀IDE可以使您摆脱管理类路径的某些困难,但这仅在一定程度上且仅在没有问题的情况下(并且总有问题)。 因此,至关重要的是,每个Java程序员都必须完全理解类路径。 只有深入了解,您才能希望调试由类路径引起的问题。

在本文中,我列出了您需要了解的有关Windows上的Java类路径(以及关联的源路径)的所有信息。 在随附的文章中 ,我演示了针对UNIX和Mac OS X的类似技术。按照此处概述的步骤进行操作,应避免不必要的类路径问题,并可以解决确实出现的大多数问题。

包装结构

掌握类路径始于源代码。 每个类都属于一个包,并且该包必须遵循标准的命名约定。 简要回顾一下:程序包名称以两级反向域名开头,例如com.exampleedu.poly 。 紧随其后的是至少一个用于描述软件包内容的单词。 例如,因为我拥有域名elharo.com,所以如果要编写Fraction类,可以将其放在以下软件包之一中:

  • com.elharo.math
  • com.elharo.numbers
  • com.elharo.math.algebra.fields

在反向域名之后,仅使用单字子包名称。 不要缩写,并正确拼写所有单词。 如果需要,请使用拼写检查器。 与类路径相关的大部分问题是由于在源代码中使用一个单词,而在文件系统中对该单词的拼写或缩写稍有不同。 唯一明智的选择是始终使用正确拼写的未缩写名称。

整个软件包的名称都应小写,即使对于通常使用大写字母的专有名称和首字母缩写词也是如此。 Windows并没有真正区分文件名中的大写和小写字母,但是Java和某些UNIX文件系统可以识别。 视情况而定是在系统之间移动文件时引起麻烦的可靠方法。

软件包名称应仅由ASCII字符组成。 尽管编译器接受用希伯来语,西里尔字母,希腊语和其他脚本编写的程序包名称,但许多文件系统却不接受。 不久您将看到,这些软件包名称将必须担当目录名称的双重职责。 虽然Java包和类名是Unicode,但是许多文件系统(包括FAT)尚未支持Unicode。 可悲的是,仍然有很多FAT文件系统。 只需将文件复制到具有不同默认编码的系统,就可以防止编译器和解释器找到正确的类。

不要忽略您的包裹名称! 从长远来看,这只会导致灾难。 如果您需要域名,请购买一个。 如果名称太长,请购买较短的名称。 (我曾经购买过xom.nu,所以我的包前缀只有六个字符。)不要将类放在默认包中(如果您在类中不包含package语句,则得到的包)。 如果程序包访问阻止对象进行通信,请向类添加更多公共方法。 您多次使用的每个类都必须放在一个包中。

配置Windows

文件扩展名和路径对Java和Windows非常重要。 因此,在开始下一步之前,请确保可以看到它们。 隐藏文件名的一部分对于最终用户来说可能是可以接受的(尽管我对此表示怀疑),但是对于开发人员来说当然不行。 要解决此问题,您需要更改某些Windows资源管理器的默认设置。

首先,在Windows桌面上打开一个文件夹; 哪一个都没关系。 转到工具菜单,然后选择文件夹选项。 在打开的对话框中,确保设置了以下三个选项,如图1所示:

  • 应该选中“在地址栏中显示完整路径”。
  • 应该选中“在标题栏中显示完整路径”。
  • 不应选中 “隐藏已知文件类型的文件扩展名”。
图1. Windows资源管理器选项
文件夹选项/查看

您可能还需要选中“显示隐藏的文件和文件夹”。 它不会对您的Java工作产生太大影响,但是就我个人而言,我希望能够看到正在使用的工具。 设置这些选项将揭示有关您在哪里做什么的更多详细信息,并使调试出现的任何问题变得更加容易。

目录结构

下一步是组织源文件以匹配包结构。 在某处创建一个干净的空目录。 出于本文的目的,我将其命名为project 。 如果将其放在根级别,例如C:\ project,通常是最简单的。 您可以将其放在桌面上或“文档和设置”文件夹中。 仅在必要时才执行此操作,因为这会使您键入的命令更长,更复杂。 (如果您在Windows XP或更高版本上运行并且没有管理员特权,则可能别无选择。在单用户系统上,如果具有管理特权,运行起来会容易得多。)

无论您做什么,都不要将此目录(或其他任何目录)放在JDK文件夹中(例如C:\ jdk1.6.0或C:\ Program Files \ Java \ j2sdk1.5.0_09)。 初始安装后,JDK和JRE目录应保持不变。

在项目目录中,再创建两个目录:bin和src。 (有些人喜欢分别命名这些内部版本和源。)

接下来,在src目录中,建立一个与包层次结构相对应的层次结构。 例如,给定一个名为com.elharo.math.Fraction的类,我将在src目录中放置一个com目录。 然后,我将在com目录中创建一个elharo目录。 然后,我将数学目录放在elharo目录中。 最后,我将Fraction.java放入此数学目录中,如图2所示:

图2.目录结构遵循包结构
项目/src/com/elharo/math/Fraction.java

您可以使用一个md命令执行此操作:

C:\project> md com\elharo\math

非常重要:切勿在源代码目录中放置源代码以外的任何内容。 通常,您放入的唯一文件是.java文件。 有时,您可以在该目录中放置.html文件(对于JavaDoc)或其他类型的源代码。 但是,您永远不想将.class文件或其他编译的,生成的工件放入此层次结构中。 这样做是灾难的根源。 可悲的是,除非您小心,否则javac编译器将完全做到这一点。 在下一节中,我将向您展示如何解决该问题。

编译中

编译Java代码非常棘手,因为您需要跟踪一些相关但不同的事情:

  • 您正在编译的目标文件。
  • 编译器在其中查找目标文件导入的.java文件的目录。
  • 编译器在其中查找目标文件导入的.class文件的目录。
  • 编译器放置已编译输出的目录。

默认情况下,javac编译器认为所有这些都是当前的工作目录,几乎从来都不是您想要的目录。 因此,在编译时,您需要显式指定每个元素。

编译文件

您指定的第一件事是要编译的.java文件。 这是从当前工作目录到该文件的路径。 例如,假设您位于图2所示的项目目录中。 该目录包含一个src目录。 src目录包含com目录,其中包含示例目录,其中包含Fraction.java文件。 以下命令行对其进行编译:

C:\project> javac src\com\elharo\math\Fraction.java

如果路径不正确,您将收到如下错误消息:

error: cannot read: src\com\example\mtah\Fraction.java

如果您看到此错误消息,请检查路径的每一部分以确保其拼写正确。 在这种情况下,错误表明“ math”的第二个和第三个字母已转置。

如果找不到拼写错误,请通过发出dir命令(如此处所示)来检查文件是否确实在预期的位置:

C:\project\src> dir src\com\example\math
ls: src/com/example/math: No such file or directory

此问题通常表明路径输入错误,但也可能意味着您不在自己认为的目录中。在此示例中,您将检查当前工作目录是否为项目目录。 在命令行上检查C:>之间的文本,以确保您在正确的位置。 在此示例中,您会注意到当我应该在C:\ project中时,我实际上在C:\ project \ src中。

输出到哪里

假定没有语法错误,javac会将编译的.class文件放在其对应.java文件所在的目录中。 您不想要这个。 混合使用.class和.java文件,很难清除已编译的文件,而又不小心删除要保留的.java文件。 这使干净的版本成为问题,并倾向于导致版本问题。 分发二进制文件时,也很难仅打包已编译的.class文件。 因此,您需要告诉编译器将编译后的输出放在一个完全不同的目录中。 -d开关指定输出目录(通常称为bin,build或classes):

C:\project> javac -d bin src\com\elharo\math\Fraction.java

现在的输出如图3所示。请注意,javac已经创建了完整的目录com \ elharo \ math层次结构。 您不需要手动进行。

图3.并行源和已编译的层次结构
project / bin / com / elharo / math / Fraction.class project / src / com / elharo / math / Fraction.java

源路径

Java查找源文件的目录称为sourcepath 。 在此处概述的方案中,这是src目录。 该目录包含源文件的层次结构 ,并组织成各自的目录。 它不是 com目录,也不是src \ com \ elharo \ math目录。

大多数项目使用一个以上的类和一个以上的包。 这些通过导入语句和完全由程序包限定的类名称连接。 例如,假设您现在在com.elharo.gui包中创建了一个新的MainFrame类,如清单1所示:

清单1.一个包中的类可以将另一个包中的类导入
package com.elharo.gui;

import com.elharo.math.*;

public class MainFrame {

  public static void main(String[] args) {
    Fraction f = new Fraction();
    // ...
  }

}

此类在与MainFrame类不同的包中使用com.elharo.math.Fraction类。 现在,源设置如图4所示。(我已从上一步中删除了编译后的输出。我随时可以再次对其进行编译。)

图4.几个软件包的源结构
project / src / com / elharo / math / Fraction.java project / src / com / elharo / gui / MainFrame.java

现在让我们看看像以前一样尝试编译MainFrame.java时会发生什么。

清单2.编译MainFrame.java
C:\project> javac -d bin src\com\elharo\gui\MainFrame.java
src\com\elharo\gui\MainFrame.java:3: package com.elharo.math does not exist
import com.elharo.math.*;
^
src\com\elharo\gui\MainFrame.java:7: cannot find symbol
symbol  : class Fraction
location: class com.elharo.gui.MainFrame
  private Fraction f = new Fraction();
          ^
src\com\elharo\gui\MainFrame.java:7: cannot find symbol
symbol  : class Fraction
location: class com.elharo.gui.MainFrame
  private Fraction f = new Fraction();
                           ^
3 errors

出现清单2中的错误是因为,尽管javac知道在哪里可以找到MainFrame.java,但是它不知道在哪里可以找到Fraction.java。 (您可能会注意到匹配的程序包层次结构足够聪明,但是事实并非如此。)为了说明这一点,我必须指定sourcepath 。 这指定了编译器在其中查找源文件层次结构的目录。 在清单2中,它是src。 因此,我使用-sourcepath选项,如下所示:

C:\project> javac -d bin -sourcepath src src\com\elharo\gui\MainFrame.java

现在,程序进行编译,没有错误,并产生图5所示的输出。请注意,javac还编译了文件Fraction.java,该文件由我正在编译的文件引用。

图5.多类输出
project / bin / com / elharo / math / Fraction.class project / bin / com / elharo / gui / MainFrame.class

在源路径中编译多个目录

实际上,您的源路径中可以有多个目录,用分号分隔,尽管通常不需要这样做。 例如,如果我要同时包含本地src目录和目录C:\ Projects \ XOM \ src,而我在其中保留了另一个项目的源代码,则可以这样编译:

C:\project> javac -d bin -sourcepath src;C:\Projects\XOM\src
  src/com/elharo/gui/MainFrame.java

此命令不会编译在这两个层次结构中找到的每个文件。 它仅编译由我明确要求进行编译的单个.java文件直接或间接引用的文件。

更常见的是,您将拥有一个用于.java文件的源目录,但是对于放置了预编译第三方库的类或JAR归档文件具有多个目录。 这是类路径的角色。

设置类路径

在大中型项目中,每次重新编译每个文件都会很耗时。 您可以通过分别编译同一项目的独立部分并将其存储在不同的bin目录中来减轻此负担。 这些目录被添加到类路径中。

有几种将类添加到类路径的方法。 但是, -classpath命令行开关是唯一应使用的开关。 例如,假设我想从以前编译到目录C:\ lib \ classes的另一个项目中导入文件。 然后,我将-classpath C:\lib\classes到命令行,如下所示:

C:\project> javac -d bin -sourcepath src -classpath C:\lib\classes
  src\com\elharo\gui\MainFrame.java

现在,假设我需要添加两个目录,C:\ project1 \ classes和C:\ project2 \ classes。 然后,我将它们都用分号隔开,如下所示:

C:\project> javac -d bin -sourcepath src
  -classpath C:\project1\classes;C:\project2\classes
  src\com\elharo\gui\MainFrame.java

当然,如果愿意,可以使用各种形式的相对路径。 例如,如果project1和project2是当前工作目录的兄弟(即它们具有相同的父目录),那么我可以这样引用它们:

C:\project> javac -d bin -sourcepath src
  -classpath ..\project1\classes;..\project2\classes
  src\com\elharo\gui\MainFrame.java

到目前为止,我已经假定该程序是完整的,并且不使用任何单独编译的第三方库。 如果是这样,您还需要将它们添加到类路径中。 库通常以JAR文件的形式分发,例如junit.jar或icu4j.jar。 在这种情况下,添加到类路径的是JAR文件本身,而不是包含该文件的目录。 (实质上,JAR文件充当包含已编译的.class文件的目录。)例如,以下命令将三件事添加到类路径:目录C:\ classes,当前工作目录中的文件icu4j.jar,以及E:\ lib中的文件junit.jar:

C:\project> javac -d bin -sourcepath src
  -classpath C:\classes;icu4j.jar;E:\lib\junit.jar
  src\com\elharo\gui\MainFrame.java

JAR文件仅用于.class文件和类路径,而不用于.java文件和源路径。

运行程序

现在,您已经成功编译了程序,并准备运行它。 这类似于但比编译简单。 运行程序时,只需指定两件事:

  • 类路径。
  • 包含main()方法的类的完全包装合格名称。

您无需指定源路径。

通常,类路径与用于编译程序的类路径相同,并添加了放置编译输出的目录。 例如,如果compile命令是这样的:

C:\project> javac -d bin -sourcepath src
  -classpath C:\classes;E:\lib\junit.jar
  src\com\elharo\gui\MainFrame.java

并且main()方法在the class com.elharo.gui.MainFrame ,那么您将像这样运行程序:

C:\project> java -classpath bin;C:\classes;E:\lib\junit.jar
    com.elharo.gui.MainFrame

请注意,命令行上的最后一项是类名 。 它不是文件名。 它不以.java或.class结尾。 此类必须在类路径的某个位置找到。

其他地方的课程

我强烈建议您在编译和运行时始终明确指定类路径。 您可以在其他地方放置文件,以便将它们添加到类路径中,并由javac编译器和java解释器找到。 但是,这些选项只保存少量的键入内容。 并且这样做的代价是要花费大量的调试时间(如果不是,而您不小心将旧版本的类放在类路径中)。

在本节中,我将向您展示一些您可能希望找到隐藏类的地方,这些地方意外地弹出您的类路径并引起问题。 这尤其可能发生在您无法控制的机器(例如服务器)上。

当前工作目录

编译器使用当前工作目录(。)作为默认类路径。 但是,一旦以其他方式设置了类路径(例如,使用-classpathCLASSPATH环境变量),该路径就不再是自动的。 您必须像其他目录一样将当前工作目录添加到类路径中。 无论哪种情况,都很容易忘记与您所在目录相同或不同的目录。 因此,请尝试避免将任何类或层次结构放入您的项目或主目录中。 相反,请始终将内容整齐地分成.java文件的src目录和.class文件的bin目录。

类路径

一段时间后,您可能会厌倦了手动将bin目录和JAR存档添加到类路径。 然后,您可能会发现CLASSPATH环境变量。 您只能将目录和JAR归档一次添加到CLASSPATH环境变量中。 这样,您不必在每次运行javac或java时都键入它们的路径。

抵制这种诱惑。 当您加载错误的类或错误的类版本时,它将导致问题。 您现在保存的任何时间,都会因调试错误而意外地加载错误的类,从而使您退回一百次。 有更好的方法可以自动执行类路径并避免键入。

jre \ lib \ ext

放置在jre \ lib \ ext目录中的JAR归档文件将添加到使用该虚拟机运行的所有应用程序的类路径中。 尽管这看起来很方便,但是这也是一个长期的错误,类似于将目录添加到CLASSPATH环境变量中。 迟早(可能更快),您将从甚至根本没有考虑过的地方加载错误版本的类,并浪费大量时间进行调试。

部署服务器端应用程序时,此问题尤其严重。 请注意,要部署到的服务器的jre \ lib \ ext目录中没有任何额外的JAR。 如果您不了解症状并知道要查找的内容,则很难调试由类路径中的JAR存档版本错误引起的问题。 为了避免这些问题,有些框架甚至编写了自己的类加载器,以绕过Java代码的常规类加载机制。

jre \ lib \背书

jre \ lib \ endorsed目录中的JAR文件也将添加到使用该虚拟机运行的所有应用程序的类路径中。 区别在于,这些文件实际上是添加到bootclasspath中,而不是通常的classpath中,并且可以替换JDK附带的标准类。 该方法对于升级XML解析器和修复VM中的错误特别有用。

再一次,尽管这种方法看起来很方便,但出于相同的原因,这也是一个长期的错误。 如果需要替换JDK类,请在运行时使用-Xbootclasspath/p选项,以避免意外加载错误版本的类:

C:\project> java -classpath C:\classes
       -Xbootclasspath/p:xercesImpl.jar com.elharo.gui.MainFrame

自动化类路径管理

在拿起钉枪之前,您应该学会使用锤子。 同样,在尝试使用功能更强大的工具之前,您应该可以手动管理类。 但是,一旦掌握了命令行工具集,您可能希望使用一种工具来自动化一些处理sourcepath和classpath的繁琐工作。 通常,这些工具通过按照我在本文中介绍的方式组织文件来工作。

集成开发环境

诸如Eclipse和NetBeans之类的大多数集成开发环境都可以自动化并在类路径管理的某些方面提供帮助。 例如,当您更改程序包名称时,Eclipse提供了移动相应的.java文件以使其匹配的功能,如图6所示:

图6. Eclipse中的类路径的快速修复
将'Element.java'移动到包'nu.fox'

但是请记住,这些IDE仍位于必须正确设置的文件系统之上,尤其是在需要与其他工具和其他IDE集成的情况下。 这些工具的主要作用是GUI对话框,树形视图和选项卡替代了命令行开关,但是基本文件结构仍然相同。

蚂蚁

Ant是使构建过程自动化的事实上的标准工具。 与将目录放在jre \ lib \ ext或CLASSPATH环境变量中不同,Ant实际上确实允许您创建一步构建过程。 您仍然需要在Ant build.xml文件中设置类路径,然后手动将源文件放在正确的目录中。 但是,至少您不必在每次编译时都重新指定它。

Maven

在组织和自动化构建过程以及相关的类路径问题方面,Maven比Ant更进一步。 Maven提供了合理的默认设置,只要您将源文件放在Maven期望找到它们的位置,就可以使用几行代码来构建简单的项目。 您仍然必须协调文件系统层次结构和包层次结构。 尽管Maven不像Ant那样容易自定义,但它特别擅长管理对第三方库的依赖关系。

结论

尽管类路径很麻烦,但是您可以用一些简单的规则来驯服它。 特别是:

  • 将每个班级放在一个包中。
  • 严格遵循包和类的命名和大写约定。
  • 确保您的程序包层次结构与目录层次结构匹配。
  • 始终对javac使用-d选项。
  • 切勿将任何东西放在jre \ lib \ ext中。
  • 切勿在jre \ lib \ endorsed中放置任何内容。
  • 切勿将.java文件与.class文件放在同一目录中。
  • 切勿将任何.java或.class文件放在当前工作目录中。

最后一个提示:类路径的许多耗时的问题都围绕简单的错误,例如拼写错误的目录名或从错误的目录进行编译。 如果您只是无法找出问题所在,请请朋友或同事查看您的问题。 通常,我发现我太接近问题了,无法在我的设置中看到对其他任何人都显而易见的错误。 第二双眼睛是一种非常有效的调试技术。

类路径当然不容易,但是有一种疯狂的方法并且可以管理。 稍微注意一下命名约定,命令行参数和目录结构,应该可以使您以最小的麻烦来编译和运行程序。


翻译自: https://www.ibm.com/developerworks/java/library/j-classpath-windows/index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值