包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.outexit(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.javajava 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修饰符,如用于强调的 <em>…</em>、用于设置等宽“打字机”字体的 <code>…</code>、用于着重强调的 <strong>…</strong> 以及包含图像的 <img …> 等。不过,一定不要使用 <h1> 或 <hr>,因为它们将与文档的格式产生冲突。类注释类注释必须放置在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 <code>setVisible(true)</code> instead通过 @see 和 @link 标记,可以使用超级链接,链接到javadoc文档的相关部分或外部文档。@see reference这个标记将在“see also”部分增加一个超级链接。它可以用于类中,也可以用于方法中。这里的reference可以选择下列情形之一:• package.class#feature label• <a href="…">label</a>• "text"第一种情况是最常见的。只要提供类、方法或变量的名字,javadoc就在文档中插入一个超链接。例如,@see com.horstmann.corejava.Employee#raiseSalary(double)建立一个链接到com.horstmann.corejava.Employee类的raiseSalary(double)方法的超链接。可以省略包名,甚至把包名和类名都省去,此时,链接将定位于当前包或当前类。需要注意,一定要使用井号(#),而不要使用句号(.)来分隔类名与方法名,或类名与变量名。Java编译器本身可以熟练地断定句点在分隔包、子包、类、内部类与方法和变量时的不同含义。但是javadoc实用程序就没有这么聪明了,因此必须对它提供帮助。如果@see标记后面有一个 < 字符,就需要指定一个超链接。可以超链接到任何URL。例如,@see <a href="www.horstmann.com/corejava.html">The Core Java home page</a>在上述各种情况下,都可以指定一个可选的标签(label)作为链接锚(link anchor)。如果省略了label,那么用户看到的锚的名称就是目标代码名或URL。如果 @see 标记后面有一个双引号(")字符,文本就会显示在“see also”部分。例如,@see "Core Java 2 volume 2"可以为一个特性添加多个 @see 标记,但必须将它们放在一起。如果愿意的话,还可以在注释中的任何位置放置指向其他类或方法的超级链接,以及插入一个专用的标记,例如,{@link package.class#feature label}。这里的特性描述规则与@see标记规则一样。包与概述注释可以直接将类、方法和变量的注释放置在Java源文件中,只要用 /** . . . */ 文档注释界定就可以了。但是,要想产生包注释,就需要在每一个包目录中添加一个名为package.html的文件。在标记 <BODY>…</BODY> 之间的所有文本都会被抽取出来。还可以为所有的源文件提供一个概述性的注释。这个注释将被放置在一个名为overview.html的文件中,该文件位于包含所有源文件的父目录中。标记 <BODY>…</BODY> 之间的所有文本将被抽取出来。当用户从导航栏中选择“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)。觉得文章不错的话,可以转发此文关注小编,之后持续更新干货文章!!!加油(ง •̀_•́)ง!!!
本文出自快速备案,转载时请注明出处及相应链接。