JAVA核心技术——类与对象6

类与对象

Java 允许使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
标准的 Java 类库分布在多个包中,包括java.lang、java.util 和java.net 等。标准的 Java包具有一个层次结构。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。所有标准的 Java 包都处于java 和 javax 包层次中。
使用包的主要原因是确保类名的唯一性。假如两个程序员不约而同地建立了 Employee 类。只要将这些类放置在不同的包中, 就不会产生冲突。
从编译器的角度来看, 嵌套的包之间没有任何关系。例如,java.util 包与java.util.jar 包毫无关系。每一个都拥有独立的类集合。

  • 类的导入
    1. 一个类可以使用所属包中的所有类, 以及其他包中的公有类( public class)。我们可以采用两种方式访问另一个包中的公有类。第一种方式是在每个类名之前添加完整的包名。
      例如:java.time.LocalDate today = java.time.LocalDate.now();
      这显然很繁琐。更简单且更常用的方式是使用 import 语句。import 语句是一种引用包含在包中的类的简明描述。一旦使用了 import 语句,在使用类时,就不必写出包的全名了。
      可以使用 import 语句导人一个特定的类或者整个包。import 语句应该位于源文件的顶部(但位于 package 语句的后面)。例如, 可以使用下面这条语句导人 java.util 包中所有的类。import java.util.*;然后使用LocalDate today = LocalDate.now();而无须在前面加上包前缀。还可以导入一个包中的特定类:import java.time.LocalDate;
      java.time.* 的语法比较简单,对代码的大小也没有任何负面影响。当然, 如果能够明确地指出所导人的类, 将会使代码的读者更加准确地知道加载了哪些类。
    2. 在 Eclipse 中, 可以使用菜单选项 Source—>Organize Imports。Package 语句, 如
      import java.util.*; 将会自动地扩展指定的导入列表,如:
      import java.util .ArrayList;
      import java.util .Date;
      这是一个十分便捷的特性。
    3. 需要注意的是, 只能使用星号(*) 导入一个包, 而不能使用 import java.*
      import java.*.*导入以 java 为前缀的所有包。
      在大多数情况下, 只导入所需的包, 并不必过多地理睬它们。
      但在发生命名冲突的时候,就不能不注意包的名字了。例如,java.util 和 java.sql 包都有日期( Date) 类。如果在程序中导入了这两个包:
      import java.util .*;
      import java.sql .*;
      在程序使用 Date 类的时候, 就会出现一个编译错误:
      Date today; // Error--java.util.Date or java.sql.Date?
      此时编译器无法确定程序使用的是哪一个 Date类。可以采用增加一个特定的 import 语句来解决这个问题:
      import java.util .*;
      import java.sql .*;
      import java.util .Date;
      如果这两个 Date 类都需要使用,又该怎么办呢?答案是,在每个类名的前面加上完整的包名。
      java.util .Date deadline = new java.util .Date();
      java.sql .Date today = new java.sql .Date(...);
      在包中定位类是编译器(compiler)的工作。类文件中的字节码肯定使用完整的包名来引用其他类。
    4. C++ 程序员经常将 import 与 #include 弄混。 实际上, 这两者之间并没有共同之处。在 C++ 中,必须使用 include 将外部特性的声明加栽进来,这是因为 C++ 编译器无法查看任何文件的内部,除了正在编译的文件以及在头文件中明确包含的文件。Java编译器可以查看其他文件的部,只要告诉它到哪里去查看就可以了。
      在 Java 中, 通过显式地给出包名, 如java.util.Date, 就可以不使用 import ; 而在C++ 中, 无法避免使用 #include 指令。
      Import 语句的唯一的好处是简捷。 可以使用简短的名字而不是完整的包名来引用一个类。例如, 在 import java.util.*(或import java.util.Date) 语句之后, 可以仅仅用 Date引用java.util.Date类。 在 C++中,与 包 机 制 类 似 的 是 命 名 空 间(namespace)。 在 Java 中, package 与
      import 语句类似于 C++中的 namespace 和 using 指令。
  • 静态导入
    1. import 语句不仅可以导人类,还增加了导人静态方法和静态域的功能。例如,如果在源文件的顶部, 添加一条指令:
      import static java.lang.System.*;
      就可以使用 System 类的静态方法和静态域,而不必加类名前缀:
      out.println("Goodbye, World!"); // i.e., System.out
      exit(0); //i.e., System.exit
      另外,还可以导入特定的方法或域:
      import static java.lang.System.out;
      实际上,是否有更多的程序员采用 System.outSystem.exit 的简写形式,似乎是一件值
      得怀疑的事情。这种编写形式不利于代码的清晰度。不过,
      sqrt(pow(x, 2) + pow(y, 2))
      看起来比
      Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
      清晰得多。
  • 将类放入包中
    1. 要想将一个类放人包中, 就必须将包的名字放在源文件的开头,包中定义类的代码之前。
      package com.horstmann.corejava;
      
      public class Employee
      {
      	...
      }
      
      如果没有在源文件中放置 package 语句, 这个源文件中的类就被放置在一个默认包(defaulf package)中。默认包是一个没有名字的包。在此之前,我们定义的所有类都在默认包中。
    2. 将包中的文件放到与完整的包名匹配的子目录中。例如,com.horstmann.corejava 包
      中的所有源文件应该被放置在子目录 com/horstmann/corejava ( Windows 中com\horstmann\corejava) 中。编译器将类文件也放在相同的目录结构中。
      例如:PackageTest 类放置在默认包中;
      Employee 类放置在 com.horstmann.corejava 包中。因此, Employee.java 文件必须包含在子目
      录 com/horstmann/corejava 中。
      目录结构1
      要想编译这个程序, 只需改变基目录,并运行命令
      javac PackageTest.java
      编译器就会自动地查找文件 com/horstmann/corejava/Employee.java 并进行编译。
      在这里不使用默认包, 而是将类分别放在不同的包中(com. horstmann.corejava 和com.mycompany)。
      目录结构2
      在这种情况下,仍然要从基目录编译和运行类,即包含 com 目录:
      javac com/myconipany/PayrollApp.java
      java com.mycompany.PayrollApp
      需要注意,编译器对文件(带有文件分隔符和扩展名 .java 的文件)进行操作。而 Java 解释器加载类(带有 . 分隔符)。
    3. 编译器在编译源文件的时候不检查目录结构。例如,假定有一个源文件开头有下
      列语句:
      package con.myconpany;
      即使这个源文件没有在子目录 com/mycompany 下, 也可以进行编译。如果它不依赖于其他包, 就不会出现编译错误。但是, 最终的程序将无法运行, 除非先将所有类文件移到正确的位置上。 如果包与目录不匹配, 虚拟机就找不到类。
  • 包的作用域
    1. 前面已经接触过访问修饰符 public 和 private。标记为 public 的部分可以被任意的类使用;标记为 private 的部分只能被定义它们的类使用。如果没有指定 public 或 private , 这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
    2. 变量必须显式地标记为 private, 不然的话将默认为包可见。显然, 这样做会破坏封装性。
    3. 在默认情况下,包不是一个封闭的实体。也就是说, 任何人都可以向包中添加更多的类。当然,有敌意或低水平的程序员很可能利用包的可见性添加一些具有修改变量功能的代码。
    4. 从 1.2 版开始, JDK 的实现者修改了类加载器, 明确地禁止加载用户自定义的、 包名以“ java.” 开始的类!当然,用户自定义的类无法从这种保护中受益。然 而,可以通过包密封(package sealing)机制来解决将各种包混杂在一起的问题。如果将一个包密封起来,就不能再向这个包添加类了。

类路径

在前面已经看到,类存储在文件系统的子目录中。类的路径必须与包名匹配。
另外, 类文件也可以存储在 JAR(Java 归档)文件中。在一个 JAR 文件中, 可以包含多个压缩形式的类文件和子目录, 这样既可以节省又可以改善性能。在程序中用到第三方
(third-party)的库文件时,通常会给出一个或多个需要包含的 JAR 文件。JDK 也提供了许多的 JAR 文件, 例如,在 jre/lib/rt.jar 中包含数千个类库文件。
JAR 文件使用 ZIP 格式组织文件和子目录。可以使用所有 ZIP 实用程序查看内部
的 rt.jar 以及其他的 JAR 文件。

为了使类能够被多个程序共享,需要做到下面几点:
1. 把类放到一个目录中, 例如 /home/user/classdir。需要注意, 这个目录是包树状结构的基目录。如果希望将 com.horstmann.corejava.Employee 类添加到其中,这个 Employee.class 类文件就必须位于子目录 /home/user/classdir/com/horstmann/corejava 中。
2. 将 JAR 文件放在一个目录中,例如:/home/user/archives。
3. 设置类路径(classpath)。类路径是所有包含类文件的路径的集合。
在 UNIX 环境中, 类路径中的不同项目之间采用冒号(·)分隔: /home/user/classdir:.:/home/user/archives/archive.jar
而在 Windows 环境中,则以分号(;)分隔: c:\classdir;.;c:\archives\archive.jar
在上述两种情况中, 句点(.)表示当前目录。
在上述两种情况中, 句点(.)表示当前目录。
类路径包括:
•基目录 /home/user/classdir或 c:\classes;
•当前目录 (.);
•JAR 文件 /home/user/archives/archive.jar c:\archives\archive.jar。
从 Java SE 6 开始,可以在 JAR 文件目录中指定通配符,如下: /home/user/dassdir:.:/home/user/archi ves/'*'
或者
c:\classdir;.;c:\archives\*
但在 UNIX 中,禁止使用 * 以防止 shell 命令进一步扩展。
在 archives 目录中的所有 JAR 文件(但不包括 .class 文件)都包含在类路径中。
由于运行时库文件( rt.jar 和在 jre/lib 与 jre/lib/ext 目录下的一些其他的 JAR 文件)会被自动地搜索, 所以不必将它们显式地列在类路径中。

  • 警告:javac 编译器总是在当前的目录中查找文件, 但 Java 虚拟机仅在类路径中有目录的时候才查看当前目录如果没有设置类路径,那也并不会产生什么问题,默认的类路径包含目录“.”然而如果设置了类路径却忘记了包含目录,则程序仍然可以通过编译, 但不能运行。

类路径所列出的目录和归档文件是搜寻类的起始点。下面看一个类路径示例: /home/user/classdir:.:/home/user/archives/archive.jar
假定虚拟机要搜寻 com.horstmann.corejava.Employee 类文件。它首先要查看存储在 jre/lib 和 jre/lib/ext 目录下的归档文件中所存放的系统类文件。显然,在那里找不到相应的类文件,然后再查看类路径。然后查找以下文件: •/home/user/classdir/com/horstmann/corejava/Employee.class
•com/horstmann/corejava/Employee.class 从当前目录开始
•com/horstmann/corejava/Employee.class inside /home/user/archives/archive.jar
编译器定位文件要比虚拟机复杂得多。如果引用了一个类,而没有指出这个类所在的包, 那么编译器将首先查找包含这个类的包,并询查所有的 import 指令,确定其中是否包含了被引用的类。例如, 假定源文件包含指令:
import java.util.*;
import com.horstmann.corejava.*;
并且源代码引用了 Employee 类。 编译器将试图查找 java.lang.Employee (因为java.lang 包被
默认导入)、java.util.Employee、 com.horstmann.corejava.Employee 和当前包中的 Employee。对这个类路径的所有位置中所列出的每一个类进行逐一查看。如果找到了一个以上的类,就会产生编译错误(因为类必须是唯一的,而 import 语句的次序却无关紧要)。
编译器的任务不止这些,它还要查看源文件(Source files) 是否比类文件新。如果是这样的话,那么源文件就会被自动地重新编译。在前面已经知道,仅可以导人其他包中的公有类。一个源文件只能包含一个公有类,并且文件名必须与公有类匹配。因此, 编译器很容易定位公有类所在的源文件。当然, 也可以从当前包中导入非公有类。这些类有可能定义在与类名不同的源文件中。如果从当前包中导人一个类, 编译器就要搜索当前包中的所有源文件, 以便确定哪个源文件定义了这个类。

  1. 设置类路径
    最好采用 -classpath (或 -cp) 选项指定类路径:
    java -classpath /home/user/dassdir:.:/home/user/archives/archive.jar MyProg
    或者
    java -classpath c:\classdir;.;c:\archives\archive.jarMyProg
    整个指令应该书写在一行中。将这样一个长的命令行放在一个 shell 脚本或一个批处理文
    件中是一个不错的主意。
    利用 -classpath 选项设置类路径是首选的方法, 也可以通过设置 CLASSPATH 环境变量完成这个操作。其详细情况依赖于所使用的 shell。在Bourne Again shell ( bash) 中, 命令格式如下:
    export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
    在 Windows shell, 命令格式如下:
    set CLASSPATH=c:\classdir;.;c:\archives\archive.jar
    直到退出 shell 为止,类路径设置均有效。
  2. 有人建议将 CLASSPATH 环境变量设置为永久不变的值。 总的来说这是一个很糟糕的主意。人们有可能会忘记全局设置, 因此, 当使用的类没有正确地加载进来时,会感到很奇怪。一个应该受到it责的示例是 Windows 中 Apple 的 QuickTime 安装程序。它进行了全局设置, CLASSPATH 指向一个所需要的 JAR 文件, 但并没有在类路径上包含当前路径。 因此, 当程序编译后却不能运行时, 众多的 Java 程序员花费了很多精力去解决这个问题。
  3. 有人建议绕开类路径, 将所有的文件放在 jre/lib/ext 路径。这是一个极坏的主意,其原因主要有两个: 当手工地加载其他的类文件时, 如果将它们存放在扩展路径上, 则不能正常地工作。此外, 程序员经常会忘记 3 个月前所存放文件的位置。 当类加载器忽略了曾经仔细设计的类路径时, 程序员会毫无头绪地在头文件中查找。事实上,加栽的是扩展路径上已长时间遗忘的类。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值