包
Java允许使用包(package)将类组织起来。借助于包可以方便地组织自己的代码,并将自己的代码与别人提供的代码库分开管理。
标准的Java类库分布在多个包中,包括java.lang、java.util、java.net等等。标准的Java包具有一个层次结构。如同硬盘的目录嵌套一样,也可以使用嵌套层次组织包。所有标准的Java包都处于java和javax包层次中。
使用包的主要原因是确保类名的唯一性。假如两个程序员不约而同地建立了Employee类。只要将这些类放置在不同的包中,就不会产生冲突。事实上,为了保证包名的绝对唯一性,Sun公司建议将公司的因特网域名(这显然是独一无二的)以逆序的形式作为包名,并且对于不同的项目使用不同的子包。例如,horstmann.com是本书作者之一注册的域名。逆序形式为com.horstmann,将它作为包名。这个包还可以被进一步地划分成子包,如com.horstmann.corejava。
包嵌套的唯一目的是管理唯一的名字。从编译器的角度来看,嵌套的包之间没有任何关系。
例如,java.util包与java.util.jar包毫无关系。每一个都拥有独立的类集合。
类的导入
一个类可以使用所属包中的所有类,以及其他包中的公有类。我们可以采用两种方式访问另一个包中的公有类。第一种方式是在每个类名之前添加完整的包名。例如:
java.util.Date today = new java.util.Date( );
这显然很令人生厌。更简单且更常用的方式是使用import语句。import语句是一种引用包含在包中的类的简明描述。一旦使用了import语句,在使用类时,就不必写出包的全名了。
可以使用import语句导入一个特定的类或者整个包。import语句应该位于源文件的顶部(但位于package语句的后面)。例如,可以使用下面这条语句导入java.util包中所有的类。
import java.util.*;
然后,就可以使用
Date today = new Date( );
而无需在前面加上包前缀。还可以导入一个包中的特定类:
import java.util.Date;
java.util.*的语法比较简单,对代码的大小也没有任何负面影响。当然,如果能够明确地指出所导入的类,将会使读者更加准确地知道加载了哪些类。
但是,需要注意的是,只能使用星号(*)导入一个包,而不能使用import java.*或importjava.*.*来导入以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(...);
在包中定位类是编译器的工作。类文件中的字节码肯定使用完整的包名来引用其他类。
静态导入
从JDK 5.0开始,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.out或System.exit的简写形式,似乎是一件值得怀疑的事情。这种编写形式无益于代码的清晰度。不过,导入静态方法和导入静态域有两个实际的应用。
1)算术函数:如果对Math类使用静态导入,就可以采用更加自然的方式使用算术函数。例如,
sqrt(pow(x, 2) + pow(y, 2))
看起来比
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
清晰得多。
2)笨重的常量:如果需要使用大量带有冗长名字的常量,就应该使用静态导入。例如,
if (d.get(DAY_OF_WEEK) == MONDAY)
看起来比
if (d.get(Calendar.DAY_OF_WEEK) == Calendar.MONDAY)
容易得多。
将类放入包中
要想将一个类放入包中,就必须将包的名字放在源文件的开头,包中定义类的代码之前。
例如,例4-7中的文件Employee.java开头是这样的:
package com.horstmann.corejava;
public class Employee
{
. . .
}
如果没有在源文件中放置package语句,那么这个源文件中的类就被放置在一个默认包中。默认包是一个没有名字的包。在此之前,我们定义的所有类都在默认包中。
将包中的文件放到与完整的包名匹配的子目录中。例如,com.horstmann.corejava包中的所有类文件应该被放置在子目录com/horstmann/corejava(Windows中com\horstmann\corejava)中。
例4-6和例4-7中的程序分放在两个包中:PackageTest类放置在默认包中;Employee类放置在com.horstmann.corejava包中。因此,Employee.class文件必须包含在子目录com/horstmann/corejava中。
换句话说,目录结构如下所示:
基目录
要想编译这个程序,只需改变基目录,并运行命令
javac PackageTest.java
编译器就会自动地查找文件com/horstmann/corejava/Employee.java并进行编译。
下面看一个更加实际的例子。在这里不使用默认包,而将类分放在不同的包中(com.horstmann.corejava和com.mycompany)。
基目录
在这种情况下,仍然要从基目录编译和运行类,即包含com目录:
javac com/mycompany/PayrollApp.java
java com.mycompany.PayrollApp
需要注意,编译器对(带有文件分隔符和扩展名.java的)文件进行操作。而Java解释器加载类(带有.分隔符)。
例4-6 PackageTest.java
例4-7 Employee.java
虚拟机如何定位类
在前面已经看到,类存储在文件系统的子目录中。类的路径必须与包名匹配。另外,还可以利用JAR实用程序将类文件添加到归档文件中。在一个归档文件中,可以包含多个类文件和子目录,这样既可以节省又可以改善性能。(有关JAR文件的详细内容将在第10章中讨论。)
例如,在运行时库中的数千个类都包含在运行时库文件rt.jar中。在JDK的jre/lib子目录中可以找到这个文件。
在前面的例子程序中,包目录com/horstmann/corejava是程序目录的一个子目录。但是,这样的安排缺乏灵活性。通常,可能会有多个程序访问包文件,为了能够使程序共享包,需要做到下面几点:
1)把类放到一个或多个指定的目录中,例如/home/user/classdir。需要注意,这个目录是包树状结构的基目录。如果希望将com.horstmann.corejava.Employee类添加到其中,这个类文件就必须位于子目录 /home/user/classdir/com/horstmann/corejava中。
2)设置类路径。类路径是所有基目录的集合,基目录中的子目录可以用于包含类文件。
如何设置类路径将取决于编译环境。如果使用JDK,那么就有两种选择:为编译器和字节码解释器指定 -classpath选项,或者设置CLASSPATH环境变量。
具体的设置细节将取决于所用的操作系统。在 UNIX环境中,类路径中的不同项目之间是采用冒号(:)分隔的。
/home/user/classdir:.:/home/user/archives/archive.jar
在 Windows环境中,将以分号(;)分隔。
c:\classes;.;c:\archives\archive.jar
在上述两种情况中,句点(.)表示当前目录。
类路径包括:
• 基目录 /home/user/classdir或c:\classes;。
• 当前目录 (.);。
• JAR文件 /home/user/archives/archive.jar或c:\archives\archive.jar。
由于运行时库文件(在jre/lib与jre/lib/ext目录下的rt.jar和一些其他的JAR文件)会被自动地搜索,所以不必将它们显式地列在类路径中。
包作用域
前面已经接触过访问修饰符public和private。标记为public的部分可以被任意的类使用;标记为private 的部分只能被定义它们的类使用。如果没有指定public或private,那么这个部分(类、方法或变量)可以被同一个包中的所有方法访问。
下面再仔细地看一下例4-2中的程序。在这个程序中,没有将Employee类定义为公有类,因此只有在同一个包(在此是默认包)中的其他类可以访问,例如EmployeeTest。对于类来说,这种默认是合乎情理的。但是,对于变量来说就有些不适宜了,因此变量必须显式地标记为private,不然的话将默认为包可见。显然,这样做会破坏封装性。问题主要出于人们经常忘记键入关键字private。在java.awt包中的Window类就是一个典型的例子。java.awt包是JDK提供的源代码的一部分:
public class Window extends Container
{
String warningString;
. . .
}
请注意,这里的warningString变量不是private!这意味着java.awt包中的所有类的方法都可以访问该变量,并将它设置为任意值(例如,“Trust me!”)。实际上,只有Window类的方法才访问它,因此应该将它设置为私有变量。我们猜测可能是程序员匆忙之中忘记键入private修饰符了。(为防止程序员内疚,我们没有说出他的名字,感兴趣的话,可以查看一下源代码。)
文档注释
JDK包含一个很有用的工具,叫做javadoc,它可以由源文件生成一个HTML文档。事实上,在第3章讲述的联机API 文档就是通过对标准Java类库的源代码运行javadoc生成的。
如果在源代码中添加以专用的定界符 /** 开始的注释,就可以很容易地生成一个看上去具有专业水准的文档。这是一种很好的方式,因为这种方式可以将代码与注释保存在一个地方。如果将文档存入一个独立的文件中,就有可能会随着时间的推移,出现代码和注释不一致的问题。然而,由于文档注释与源代码在同一个文件中,在修改源代码的同时,重新运行javadoc就可以轻而易举地保持两者的一致性。
注释的插入
javadoc实用程序(utility)从下面几个特性抽取信息:
• 包
• 公有类与接口
• 公有的和受保护的(protected)方法
• 公有的和受保护的域
在第5章中将介绍受保护特性,接口将在第6章介绍。
应该为上述几部分编写注释。注释应该放置在所描述特性的前面。注释以 /** 开始,并以 */结束。
每个 /** . . . */ 文档注释在标记之后紧跟着自由格式文本(free-form text)。标记由 @ 开始,如@author或@param。
自由格式文本的第一句应该是一个概要性的句子。javadoc实用程序自动地将这些句子抽取出来形成概要页。
在自由格式文本中,可以使用HTML修饰符,如用于强调的 ...、用于设置等宽
“打字机”字体的 ...
、用于着重强调的 ... 以及包含图像的 等。
不过,一定不要使用
或 ,因为它们将与文档的格式产生冲突。
类注释
类注释必须放置在import语句之后,类定义之前。
下面是一个类注释的例子:
方法注释
每一个方法注释必须放置在所描述的方法之前。除了通用标记之外,还可以使用下面的标记:
@param variable description
这个标记将向当前方法的“param”(参数)部分添加一个条目。这个描述可以占据多行,并可以使用HTML标记。一个方法的所有 @param 标记必须放在一起。
@return description
这个标记将向当前方法添加“return”(返回)部分。这个描述可以跨越多行,并可以使用HTML标记。
@throws class description
这个标记将添加一个注释,用于表示这个方法有可能抛出异常。有关异常的详细内容将在第11章中讨论。
下面是一个方法注释的例子:
域注释
只需要对公有域(通常指的是静态常量)建立文档。例如,
通用注释
下面的标记可以用在类文档的注释中。
@author name
这个标记将产生一个“author”(作者)条目。可以使用多个 @author 标记,每个 @author 标记对应一名作者。
@version text
这个标记将产生一个“version”(版本)条目。这里的text可以是对当前版本的任何描述。
下面的标记可以用于所有的文档注释。
@since text
这个标记将产生一个“since”(始于)条目。这里的text可以是对引入特性的版本描述。例如,
@since version 1.7.1。
@deprecated text
这个标记将对类、方法或变量添加一个不再使用的注释。text中给出了取代的建议。例如,
@deprecated Use setVisible(true)
instead
通过 @see 和 @link 标记,可以使用超级链接,链接到javadoc文档的相关部分或外部文档。
@see reference
这个标记将在“see also”部分增加一个超级链接。它可以用于类中,也可以用于方法中。
这里的reference可以选择下列情形之一:
• package.class#feature label
• label
• "text"
第一种情况是最常见的。只要提供类、方法或变量的名字,javadoc就在文档中插入一个超链接。例如,
@see com.horstmann.corejava.Employee#raiseSalary(double)
建立一个链接到com.horstmann.corejava.Employee类的raiseSalary(double)方法的超链接。可以省略包名,甚至把包名和类名都省去,此时,链接将定位于当前包或当前类。
需要注意,一定要使用井号(#),而不要使用句号(.)来分隔类名与方法名,或类名与变量名。Java编译器本身可以熟练地断定句点在分隔包、子包、类、内部类与方法和变量时的不同含义。但是javadoc实用程序就没有这么聪明了,因此必须对它提供帮助。
如果@see标记后面有一个 < 字符,就需要指定一个超链接。可以超链接到任何URL。例如,
@see The Core Java home page
在上述各种情况下,都可以指定一个可选的标签(label)作为链接锚(link anchor)。如果省略了label,那么用户看到的锚的名称就是目标代码名或URL。
如果 @see 标记后面有一个双引号(")字符,文本就会显示在“see also”部分。例如,
@see "Core Java 2 volume 2"
可以为一个特性添加多个 @see 标记,但必须将它们放在一起。
如果愿意的话,还可以在注释中的任何位置放置指向其他类或方法的超级链接,以及插入一个专用的标记,例如,{@link package.class#feature label}。这里的特性描述规则与@see标记规则一样。
包与概述注释
可以直接将类、方法和变量的注释放置在Java源文件中,只要用 /** . . . */ 文档注释界定就可
以了。但是,要想产生包注释,就需要在每一个包目录中添加一个名为package.html的文件。在标
记
... 之间的所有文本都会被抽取出来。还可以为所有的源文件提供一个概述性的注释。这个注释将被放置在一个名为overview.html
的文件中,该文件位于包含所有源文件的父目录中。标记
... 之间的所有文本将被抽取出来。当用户从导航栏中选择“Overview”时,就会显示出这些注释内容。
注释的抽取
这里,假设HTML文件将被存放在目录docDirectory下。执行以下步骤:
1)切换到包含想要生成文档的源文件目录。如果有嵌套的包要生成文档,例如com.horstmann.corejava,就必须切换到包含子目录com的目录。(如果存在overview.html文件的话,这也是它的所在目录。)
2)如果是一个包,应该运行命令:
javadoc -d docDirectory nameOfPackage
或运行:
javadoc -d docDirectory nameOfPackage1 nameOfPackage2 . . .
对于多个包生成文档。如果文件在默认包中,就应该运行:
javadoc -d docDirectory *. java
如果省略了-d docDirectory选项,HTML文件就被提取到当前目录下。这样有可能会带来混
乱,因此不提倡这种做法。
可以使用多种形式的命令行选项对javadoc程序进行调整。例如,可以使用 -author 和 -version
选项在文档中包含 @author和 @version标记。(默认情况下,这些标记会被省略。)另一个很有用
的选项是 -link,用来为标准类添加超链接。例如,如果使用命令
javadoc -link http://java.sun.com/j2se/5.0/docs/api *.java
那么,所有的标准类库类都会自动地链接到Sun网站的文档。
有关其他的选项,请查阅javadoc实用程序的联机文档,http://java.sun.com/j2se/javadoc。
类设计技巧
在结束本章之前,简单地介绍几点技巧。应用这些技巧可以使得设计出来的类更具有OOP的专业水准。
1)一定将数据设计为私有。
最重要的是:绝对不要破坏封装性。在有的时候,需要编写一个访问器方法或更改器方法,但是最好还是保持实例域的私有性。很多惨痛的经验告诉我们,数据的表示形式很可能会改变,但它们的使用方式却不会经常发生变化。当数据保持私有时,它们的表示形式的变化不会对类的使用者产生影响,即使出现bug也易于检测。
2)一定要对数据初始化。
Java不对局部变量进行初始化,但是会对对象的实例域进行初始化。最好不要依赖于系统的默认值,而是应该显式地初始化所有的数据,具体的初始化方式可以是提供默认值,也可以是在所有构造器中设置默认值。
3)不要在类中使用过多的基本数据类型。
就是说,用其他的类代替多个相关的基本数据类型的使用。这样会使类更加易于理解且易于修改。例如,用一个称为Address的类替换下面的Customer类中的实例域:
private String street;
private String city;
private String state;
private int zip;
这样,可以很容易地顺应地址的变化,例如,需要增加对国际地址的处理。
4)不是所有的域都需要独立的域访问器和域更改器。
或许,需要获得或设置雇员的薪金。而一旦构造了雇员对象,就应该禁止更改雇用日期,并且在对象中,常常包含一些不希望别人获得或设置的实例域,例如,在Address类中,存放州缩写的数组。
5)使用标准格式进行类的定义。
一定采用下面的顺序书写类的内容:
公有访问特性部分
包作用域访问特性部分
私有访问特性部分
在每一部分中,应该按照下列顺序列出:
实例方法
静态方法
实例域
静态域
毕竟,类的使用者对公有接口要比对私有的实现细节更感兴趣,并且对方法要比对数据更感兴趣。
但是,哪一种风格更好并没有达成共识。Sun的程序设计风格建议 Java程序设计语言先书写域,后书写方法。无论采用哪种风格,重要的一点是要保持一致。
6)将职责过多的类进行分解。
这样说似乎有点含糊不清,究竟多少算是“过多”?每个人的看法不同。但是,如果明显地可以将一个复杂的类分解成两个更为简单的类,就应该将其分解。(但另一方面,也不要走极端。设计10个类,每个类只有一个方法,显然也太小了。)
下面是一个反面的设计的例子。
实际上,这个类实现了两个独立的概念:一副牌(含有shuffle方法和draw方法)、一张牌(含有查看面值和花色的方法)。另外,引入一个表示单张牌的Card类。现在有两个类,每个类完成自己的职责:
7)类名和方法名要能够体现它们的职责。
与变量应该有一个能够反映其含义的名字一样,类也应该如此。(在标准类库中,也存在着一些含义不明确的例子,如:Date类实际上是一个用于描述时间的类。)
命名类名的良好习惯是采用一个名词(Order)、前面有形容词修饰的名词(RushOrder)或动名词(有“-ing”后缀)修饰名词(例如,BillingAddress)。对于方法来说,习惯是访问器方法用小写get开头(getSalary),更改器方法用小写的set开头(setSalary)。
觉得文章不错的话,可以转发此文关注小编,之后持续更新干货文章!!!
加油(ง •̀_•́)ง!!!
10年架构师深解java核心技术:方法参数+对象构造,确定不学?
Java核心技术:GregorianCalendar类+更改器方法与访问器方法大牛带你深入Java核心技术,进行面向对象程序设计深剖,可以一学