欢迎光临散文网 会员登陆 & 注册

Java学习记录:对象与类(二)

2023-03-19 19:26 作者:冰灵___Ling  | 我要投稿

类路径:

类存储在文件系统的子目录中。类的路径必须与包名匹配。

另外,类文件也可以存储在JAR(java归档)文件中。在一个JAR文件中,可以包含多个压缩格式的类文件和子目录,这样既可以节省空间又可以改善性能。在程序中用到第三方的库时,你通常会得到一个或多个需要包含的JAR文件。(第11章会讲JAR文件)

【提示:JAR文件使用ZIP格式组织文件和子目录。可以使用任何ZIP工具查看JAR文件。】

为了使类能够被多个程序共享,需要做到下面几点:

1. 把类文件放到一个目录中,例如/home/user/classdir。需要注意,这个目录是包树状结构的基目录。如果希望增加com.horstmann.corejava.Employee类,那么Employee.class类文件就必须位于子目录/home/user/classdir/com/horstmann/corejava中。

2. 将JAR文件放在一个目录中,例如/home/user/archives。

3. 设置类路径(class path)。类路径是所有包含类文件的路径的集合。

在UNIX环境中,类路径中的各项之间用冒号(:)分割:

/home/user/classdir:.:/home/user/archives/archive.jar

而在Windows环境中,则以分号(;)分割:

C:\classdir;.;C:\archives\archive.jar

不论是UNIX还是Windows,都用句点(.)表示当前目录。

类路径包括:

·基目录/home/user/classdir或C:\classdir;

·当前目录(.)

·JAR文件/home/user/archives/archive.jar或者C:\archives\archive.jar.

从java6开始,可以为JAR文件目录指定一个通配符,如下:

/home/user/classdir:.:/home/user/archives/’*’

或者

C:\classdir;.;C:\archives\*

在UNIX中,*必须转义以防止shell扩展。

archives目录中的所有JAR文件(但不包括.class文件)都包含在这个类路径中。

由于总是会搜索JavaAPI的类,所以不必显式地包含在类路径中。

【警告:javac编译器总是在当前目录中查找文件,但只有当类路径中包含“.”目录时,java虚拟机才会查看当前目录。如果你没有设置类路径,那么没有什么问题,因为默认的类路径会包含”.“目录。但是如果你设置了类路径却忘记包含”.“目录,那么尽管你的程序可以没有错误地通过编译,但不能运行。】

类路径所列出地目录和归档文件是搜寻类地起始点。下面看一个类路径示例:

/home/user/classdir:.:/home/user/archives/archive.jar

假定虚拟机要搜寻com.horstmann.corejava.Employee类的类文件。它首先要查看JavaAPI类,显然,在那里找不到相应的类文件,所以转而查看类路径。它会查找以下文件:

·/home/user/classdir/com/horstmann/corejava/Employee.class

·com/horstmann/corejava/Employee.class(从当前目录开始)

·com/horstmann/corejava/Employee.class(/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语句的次序并不重要)。

编译器的任务不止这些,它还要查看源文件是否比类文件新。如果是这样的话,那么源文件就会自动地重新编译。在前面已经知道,只可以导入其他包中的公共类。一个源文件只能包含一个公共类,并且文件名与公共类名必须匹配。因此,编译器很容易找到公共类的源文件。不过,还可以从当前包中导入非公共类。这些类有可能在与类名不同的源文件中定义。如果从当前包中导入一个类,那么编译器就要搜索当前包中的所有源文件,查看哪个源文件定义了这个类。

 

设置类路径

最好使用-classpath(或-cp,或者Java中的--class-path)选项指定类路径:

java -classpath /home/user/classdir:.:/home/user/archive.jar MyProg

或者

java -classpath c:\classdir;.;c:\archives\archive.jar MyProg

整个指令必须写在一行中。将这样一个很长的命令行放在一个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为止,类路径设置均有效。

【警告:有人建议永久地设置CLASSPATH环境变量。一般来说,这是一个糟糕地想法。人们有可能会忘记全局设置,因此,当他们的类没有正确地加载时,就会感到很奇怪。一个颇受诟病的示例是Windows中Apple QuickTime安装程序。很多年来,它都将CLASSPATH全局设置为指向它需要的一个JAR文件,而没有在类路径中包含当前目录。因此,当程序编译后却不能运行时,无数JAVA程序员不得不花费很多精力去解决这个问题。】

【警告:过去,有人建议完全绕过类路径,将所有的JAR文件都放在jre/lie/ext目录中。这种机制在Java9中已经过时,不过不管怎样这都是一个不好的建议。从扩展目录加载一些已经遗忘很久的类时,这会让人非常困惑。】

【注释:在Java9中,还可以从模块路径加载类。本书卷II的第9章将讨论模块和模块路径。】

 

JAR文件:

在将应用程序打包时,你希望只向用户提供一个单独的文件,而不是一个包含大量类文件的目录结构,Java归档(JAR)文件就是为此目的而设计的。JAR文件既可以包含类文件,也可以包含诸如图像和声音等其他类型的文件。此处,JAR文件是压缩的,它使用了我们熟悉的ZIP压缩格式。

创建JAR文件:

可以使用jar工具制作JAR文件(在默认的JDK安装中,这个工具位于jdk/bin目录下)。创建一个新JAR文件最常用的命令使用以下语法:

jar cvf jarFileName file1file2...

例如:

jar cvf CalculatorClasses.jar*.class icon.gif

通常,jar命令的格式如下:

jar options file1file2...

可以将应用程序和代码库打包在JAR文件中。例如,如果想在一个Java程序中发送邮件,可以使用打包在文件javax.mail.jar中的一个库。

 

以下是jar程序选项:

用法: jar {ctxui}[vfmn0PMe] [jar-file] [manifest-file] [entry-point] [-C dir] files ...

选项:

    -c  创建新档案

    -t  列出档案目录

    -x  从档案中提取指定的 (或所有) 文件

    -u  更新现有档案

    -v  在标准输出中生成详细输出,详细输出告诉您每个文件添加到 JAR 文件时的名称。

    -f  指定档案文件名

    -m  包含指定清单文件中的清单信息。

    -n  创建新档案后执行 Pack200 规范化

    -e  为捆绑到可执行 jar 文件的独立应用程序指定应用程序入口点

    -0  (数字零)选项表示仅存储而不使用 ZIP 压缩 JAR 文件

    -P  保留文件名中的前导 '/' (绝对路径) 和 ".." (父目录) 组件

    -M  指示不应生成默认清单文件。Jar 工具默认会自动将一个清单文件添加到 Jar 归档文件中,其路径名为 META-INF/ manifest

    -i  为指定的 jar 文件生成索引信息

    -C  更改为指定的目录并包含以下文件

如果任何文件为目录, 则对其进行递归处理。

清单文件名, 档案文件名和入口点名称的指定顺序

与 'm', 'f' 和 'e' 标记的指定顺序相同。

示例 1: 将两个类文件归档到一个名为 classes.jar 的档案中:

       jar cvf classes.jar Foo.class Bar.class

示例 2: 使用现有的清单文件 'mymanifest' 并

           将 foo/ 目录中的所有文件归档到 'classes.jar' 中:

       jar cvfm classes.jar mymanifest -C foo/ .

关于jar的常见用法请访问:jar工具详解 - 简书 (jianshu.com)

 

清单文件:

除了类文件、图像和其他资源外,每个JAR文件还包含一个清单文件(meanifest),用于描述归档文件的特殊特性。

清单文件被命名为MANIFEST.MF,它位于JAR文件的一个特殊的META.INF子目录中。合法的最小清单文件极其简单:

Manifest-Version: 1.0

简单的清单文件可能包含更多条目。这些清单条目被分组为多个节。第一节被称为主节(main section)。它作用于整个JAR文件。随后的条目可以指定命名实体的属性,如单个文件、包或者URL。它们都必须以一个Name条目开始。节与节之间用空行分开。例如:

Manifest-Version: 1.0
lines describing this archive

Name: Woozle.class
lines describing this file
Name: com/mycompany/mypkg/
lines describing this package

要想编辑清单文件,需要将希望添加到清单文件中的行放到文本文件中,然后运行。

jar cfm jarFileName manifestFileName...

例如,要创建一个包含清单文件的JAR文件,应该运行:

java cfm MyArchive.jar manifest.mf com/mycompany/mypkg/*.class

要想更新一个已有的JAR文件的清单,则需要将增加的部分放置到一个文本文件中,然后执行以下命令:

java ufm MyArchive.jar manifest-additions.mf

【注释:请参见http://docs.oracle.com/javase/10/docs/specs/jar/jar.html获得有关JAR文件和清单文件格式的更多信息。】

 

可执行JAR文件:

可以使用jar命令中的e选项指定程序的入口点,即通常调用java执行程序时指定的类:

java cvfe Myprogram.jar com.mycompany.mypkg.MainAppClass files to add

或者,可以在清单文件中指定程序的主类,包括以下形式的语句:

Main-Class: com.mycompany.mypkg.MainAppClass

不要为主类名加扩展名.class。

【警告:清单文件的最后一行必须以换行符结束。否则,将无法正确地读取清单文件。常见的一个错误时创建了一个只包含Main-Class行而没有行结束符的文本文件。】

不论使用哪一种方法,用户都可以简单地通过下面的命令来启动程序:

jar -jar Myprogram.jar

取决于操作系统的配置,用户甚至可以通过双击JAR文件图标来启动应用程序。下面是各种操作系统的操作方式:

·在Windows平台中,Java运行时安装程序将为“.jar”扩展名创建一个文件关联,会用javaw -jar命令启动文件(与java命令不同,javaw命令不打开shell窗口)。

·在Mac OS X平台中,操作系统能够识别“.jar”扩展名文件。双击JAR文件时就会执行Java程序。

不过,人们对JAR文件中的Java程序与原生应用还是感觉不同。在Windows平台中,可以使用第三方的包装器工具将JAR文件转换成Windows可执行文件。包装器是一个Windows程序,有大家熟悉的扩展名.exe,它可以查找和加载Java虚拟机(JVM),或者在没有找到JVM时会告诉用户应该做些什么。有许多商业的和开源的产品,例如,Launch4J(http://launch4j.sourceforge.net)和IzPack(http://izpack.org)。

 

多版本JAR文件:

随着模块和包强封装的引入,之前可以访问的一些内部API不再可用。这可能要求库提供商为不同Java版本发布不同的代码。为此,java9引入了多版本JAR(multi-release JAR)。

为了保证向后兼容,特定于版本的类文件放在META-INF/versions目录中:

Application.class

BuildingBlocks.class

Util.class

META-INF

->MANIFEST.MF(with line Multi-Release: true)、

  Versions

->9->Application.class

   ->BuildingBlocks.class

->10->BuildingBlocks.class

假设Application类使用了CssParser类,那么遗留版本的Application.class文件可以使用com.sun.javafx.css.CssParer,而Java9版本可以使用javafx.css.Cssparser。

Java8完全不知道META-INF/versions目录,它只会加载遗留的类。Java9读取这个JAR文件时,则会使用新版本。

要增加不同的版本的类文件,可以使用--release标志:

Jar uf MyProgram.jar --release 9 Application.class

要从头构建一个多版本JAR文件,可以使用-C选项,对应每个版本要切换到一个不同的类文件目录:

Jar cf Myprogram.jar -C bin/8 . --release 9 -C bin/9 Application.class

面向不同版本编译时,要使用--release标志和-d标志来指定输出目录:

Javac -d bin/8 --release 8...

在java9中,-d选项会创建这个目录(如果原先该目录不存在)。

--release标志也是Java9新增的。在较早的版本中,需要使用-source、-target和-bootclasspath标志。JDK现在为之前的两个API版本提供了符号文件。在Java9中,编译时可以将--release设置为9、8或7.

多版本JAR并不适用于不同版本的程序或库。对于不同的版本,所有类的公共API都应当是一样的。多版本JAR的唯一作用是使你的某个特定版本的程序或库能够使用多个不同的JDK版本。如果你新增了功能或者改变了一个API,就应当提供一个新版本的JAR。

【注释:javap之类的工具并没有改造为可以处理多版本JAR文件。如果调用

javap -classpath Myprogram.jar Application.class

你会得到类的基本版本(毕竟它与更新的版本应该有相同的公共API)。如果必须查看更新的版本,则可以调用:

javap -classpath Myprogram.jar\!/META-INF/versions/9/Application.class

 

关于命令行选项的说明:

Java开发包(JDK)的命令行选项一直以来都使用单个短横线加多字母选项名的形式,如:

java -jar...
javac -Xlint:unchecked -classpath...

但jar命令是个例外,这个命令遵循经典的tar命令选项格式,而没有短横线:

jar cvf...

从Java9开始,Java工具开始转向一种更常用的选项格式,多字母选项名前面加两个短横线,另外对于常用的选项可以使用单字母快捷方式。例如,调用Linux ls命令时可以提供一个“human-readable”选项:

ls --human-readble

或者

ls -h

在Java9中,可以使用--version而不是version,另外可以使用--class-path而不是-classpath。在本书卷II的第9章中可以看到,--module-path选项有一个快捷方式-p。

详细内容可以参见JEP293增强请求(http://openjdk.java.net/jeps/293)。在所有清洁工作中,作者还提出要标准化选项参数。带--和多字母选项的参数用空格或者一个等号(=)分隔:

Java --class-path /home/user/classdir...

或者

Javac --class-path=/home/user/classdir...

单字母选项的参数可以用空格分隔,或者直接跟在选项后面:

Javac -p moduledir...

或者

Javac -pmoduledir...

【警告:后一种方式现在不能使用,而且一般来讲这也不是一个好主意。如果模块目录恰好是arameters或rocessor,这就很容易与遗留的选项(parameters或processor)发生冲突,这又何必呢?】

无参数的单字母选项可以组合在一起:

Jar -cvf Myprogram.jar -e mypackage.MyProgram */*.class

【警告:目前不能使用这种方式,这肯定会带来混淆。假设javac有一个-c选项,那么javac -cp是指java -c -p还是-cp?】

这就会带来一些混乱,希望过段时间能够解决这个问题,尽管我们想要远离这些古老的jar选项,但最好还是等到尘埃落定为妙。不过,如果你想做到最现代化,那么可以安全地使用jar命令的长选项:

jar --create --verbose ==file jarFileName file1file2...

对于单字母选项,如果不组合,也是可以使用的:

jar -c -v -f jarFileName file1file2...

 

文档注释:

JDK包含一个很有用的工具,叫做javadoc,它可以由源文件生成一个HTML文档。

如果在源代码中添加以特殊定界符/**开始的注释,那么你也可以很容易生成一个看上去具有专业水准的文档。这是一种很好的方法,因为这样可以将代码与注释放在一个地方。应该知道,如果将文档存放在一个单独的文件中,随着时间的推移,代码和注释很可能出现不一致。不过,如果文档注释与源代码在同一个文件中,就可以很容易地同时修改源代码和注释,然后重新运行javadoc。

 

注释的插入:

Javadoc实用工具从下面几项中抽取信息:

·模块;

·包;

·公共类与接口;

·公共的和受保护的字段;

·公共的和受保护的构造器及方法。

第5章中将介绍受保护特性,第6章中将介绍接口,模块在卷II的第9章介绍。

可以(而且应该)为以上各个特性编写注释。各个注释放置在所描述特性的前面。注释以/**开始,并以*/结束。

每个/**。。。*/文档注释包含标记以及之后紧跟着的自由格式文本(free-form text)。标记以@开始,如@since或@param。

自由格式文本的第一个句子应该是一个概要陈述。Javadoc工具自动地将这些句子抽取出来生成概要页。

在自由格式文本中,可以使用HTML修饰符,例如,用于强调的<em>.....</em>、用于着重强调的<strong>.....</strong>、用于项目符号列表的<ul>/<li>以及用于包含图像的<img..../>等。要键入等宽代码,需要使用{@code.....}而不是<code>......</code>——这样一来,就不用操心对代码的<字符转义了。

【注释:如果文档中有到其他文件的链接,如图像文件(例如,图表或用户界面组件的图像),就应该将这些文件放到包含源文件的目录下的一个子目录doc-files中国。Javadoc工具将从源目录将doc-files目录及其内容复制到文档目录中。在链接中需要使用doc-files目录,例如<imgsrc=”doc-files/uml.png” alt= “UML dagram”/>

 

类注释:

类注释必须放在import语句之后,class定义之前。

下面是一个类注释的例子:

/**
 * A{@code Card}object represents a playing card, such
 * as "Queen of Hearts". A card has a suit (Diamond, Heard,
 * Spade or Club) and a value (1 = Ace, 2. . . 10, 11 = Jack,
 * 12 = Queen, 13 = King)
 */
public class Card {

      . . .

}

【注释:没有必要在每一行的开始都添加*,例如,以下注释同样是合法的:

**
  A{@code Card}object represents a playing card, such
  as "Queen of Hearts". A card has a suit (Diamond, Heard,
  Spade or Club) and a value (1 = Ace, 2. . . 10, 11 = Jack,
  12 = Queen, 13 = King)
 */

不过,大部分IDE会自动提供星号,而且换行改变时,还会重新放置星号。】

 

方法注释:

每个方法注释必须放在所描述的方法之前。除了通用标记之外,还可以使用下面的标记:

·@param variable descripition

这个标记将给当前方法的“parameters”(参数)部分添加一个条目。这个描述可以占据多行,并且可以使用HTML标记。一个方法的所有@param标记必须放在一起。

·@return description

这个标记将给当前方法添加“returns”(返回)部分。这个描述可以跨多行,并且可以使用HTML标记。

·@throws class description

这个标记将添加一个注释,表示这个方法有可能抛出异常。有关异常的详细内容将在第7章种讨论。

下面是一个方法注释的示例:

/**
 * Raises the salary of an employee.
 * @param byPercent the percentage by which to raise the salary (e.g., 10 means 10%)
 * @return the amount of the raise
 */
public double raiseSalary(double byPercent){
    double raise = this.salary * byPercent / 100;
    this.salary += raise;
    return raise;
}

 

字段注释:

只需要对公共字段(通常指的是静态常量)增加文档注释。例如,

/**
 * The "Hearts" card suit
 */
public static final int HEARTS = 1;

 

通用注释:

标记@since text会建立一个“since”(始于)条目。Text(文本)可以是对引入这个特性的版本描述。例如,@since 1.7.1

类文档注释中可以使用下面的标记:

·@author name

这个标记将建立一个“author”(作者)条目。可以有多个@author标记,每个@author标记对应一个作者。并不是非得使用这个标记,你的版本控制系统能够更好地跟踪作者。

·@version text

这个标记将建立一个“version”(版本)条目。这里的text可以是对当前版本的任何描述。

通过@see和@link标记,可以使用超链接,链接到javadoc文档的相关部分或外部文档。

标记@see reference将在“see also”(参见)部分增加一个超链接。它可以用于类中,也可以用于方法中。这里的reference(引用)可以有以下选择:

1.package.class#feature label
2.<a href=". . .">label</a>
3."test"

第一种情况是最有用的。只要提供类、方法或变量的名字,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)。如果省略了标签,则用户看到的锚就是目标代码名或URL。

如果@see标记后面有一个双引号(“)字符,文本就会显示在”see also”部分。例如,

@see "Core Java 2 volume 2"

可以为一个特性添加多个@see标记,但必须将它们放在一起。

如果愿意,可以在任何文档注释中放置指向其他类或方法的超链接。可以在注释中的任何位置插入一个形式如下的特殊标记:

{@link package.class#feature label}

这里的特性描述规则与@see标记的规则相同。

最后,在Java9中,还可以使用{@index entry}标记为搜索框增加一个条目。

 

包注释:

可以直接将类、方法和变量的注释放置在Java源文件中,只要用/**......*/文档注释界定就可以了。但是,要想产生包注释,就需要在每一个包目录中添加一个单独的文件。可以有如下两个选择:

1. 提供一个名为package-info.java的Java文件。这个文件必须包含一个初始的Javadoc注释,以/**和*/界定,后面是一个package语句。它不能包含更多的代码或注释。

2. 提供一个名为package.html的HTML文件,抽取标记<body>. . .</body>之间的所有文本。

 

注释提取:

在这里,假设你希望HTML文件将放在名为docDirectory的目录下。执行以下步骤:

1. 切换到源文件目录,其中包含想要生成文档的源文件。如果有嵌套的包要生成文档,例如com.horstmann.corejava,就必须切换到包含子目录com的目录(如果提供overview.html文件的话,这就是这个文件所在的目录)。

2. 如果是一个包,应该运行命令:

javadoc -d docDirectory nameOfPakage

或者,如果要为多个包生成文档,运行:

javadoc -d docDirectory nameOfPakage1 nameOfpackage2. . .

如果你的文件在无名包中,则应该运行:

javadoc -d docDirectory *.java

如果省略了-d docDirectory选项,HTML文件就会提取到到当前目录下。这样可能很混乱,因此我不提倡这种做法。

可以使用很多命令行选项对javadoc程序进行微调。例如,可以使用-auther和-version选项在文档包含@author和@version标记(默认情况下,这些标记会被省略)。另一个很有用的选项是-link,用来为标准类添加超链接。例如,如果使用命令:

javadoc -link http://docs.oracle.com/javase/9/docs/api *.java

那么,所有的标准类库的类都会自动地链接到Oracle网站的文档。

如果使用-linksource选项,那么每个源文件就会转换为HTML(不对代码着色,但包含行号),并且每个类和方法名将变为指向源代码的超链接。

还可以为所有源文件提供一个概要注释。把它放在一个类似overview.html的文件中,运行javadoc工具,并提供命令行选项-overview filename。将抽取标价<body>......</body>之间的所有文本。当用户从导航栏中选择“Overview”时,就会显示这些内容。

有关其他的选项,请查阅javadoc工具的联机文档http://docs.oracle.com/javase/9/javadoc/javadoc.html。

 

类设计技巧:

我们不会面面俱到,也不希望过于沉闷,所以在这一章结束之前再简单地介绍几点技巧。应用这些技巧可以使你设计的类更能得到专业OOP圈子的认可。

1.一定要保证数据私有。

这是最重要的;绝对不要破坏封装性。有时候,可能需要编写一个访问器方法或更改器方法,但最好还是保持实例字段的私有性。很多惨痛的教训告诉我们,数据的表示形式很可能会改变,但它们的使用方式却不会经常变化。当数据保持私有时,表示形式的变化不会对类的使用者产生影响,而且也更容易检测bug。

2.一定要初始化数据。

Java不会为你初始化局部变量,但是会对对象的实例字段进行初始化。最好不要依赖于系统的默认值,而是应该显式地初始化所有变量,可以提供默认值,也可以在所有构造器中设置默认值。

3.不要在类中使用过多的基础类型。

其想法是要用其他类,而不是使用多个相关的基本类型。这样会使类更易于理解,也更易于修改。例如,可以用一个名为Address的新类替换一个Customer类中的以下实例字段:

private String street;
private String city;
private String state;
private int zip;

这样一来,可以很容易地处理地址的变化,例如,可能需要处理国际地址。

4.不是所有的字段都需要单独的字段访问器和更改器。

你可能需要获得或设置员工的工资。而一旦构造了员工对象,肯定不需要更改雇佣日期。另外,在对象中,常常包含一些不希望别人获得或设置的实例字段,例如,Address类中的州缩写数组。

5.分解有过多职责的类。

这样说似乎有点含糊,究竟多少算是“过多”?每个人的看法都不同。但是,如果明显地可以将一个复杂的类分解成两个概念上更为简单的类,就应该进行分解。(但另一方面,也不要走极端。如果设计10个类,每个类只有一个方法,显然就有些矫枉过正了。)

下面是一个反面的设计示例。

public class CardDeck // bad design
{
    private int[] value;
    private int[] suit;
    
    public CardDeck(){....}
    public void shuffle{.....}
    public int getTopValue(){.....}
    public int getTopSuit(){.....}
    public void draw(){......}
}

实际上,这个类实现了两个独立的概念:一副牌(包含shuffle方法和draw方法)和一张牌(包含查看面值和花色的方法)。最好引入一个表示一张牌的Card类。现在有两个类,每个类分别完成自己的职责:

public class CardDeck // bad design
{
    private Card[] cards;

    public CardDeck(){....}
    public void shuffle{.....}
    public int getTopValue(){.....}
    public int getTopSuit(){.....}
    public void draw(){......}
}

public class Card
{
    private int value;
    private int suit;
    
    public Card(int aValue, int aSuit){.......}
    public int getValue(){.......}
    public int getSuit(){......}
}

6.类名和方法名要能够体现它们的职责。

变量应该有一个能够反映其含义的名字,类似地,类也应该如此(在标准类库中,确实存在着一些含义不明确的例子,如Date类实际上是一个描述时间的类)。

对此有一个很好的惯例:类名应当是一个名词(Order),或者是前面有形容词修饰的名词(RushOrder),或者是有动名词(有“-ing”后缀)修饰的名词(例如,BillingAddress)。对于方法来说,要遵循标准惯例:访问器方法用小写get开头(getSalary),更改器方法用小写的set开头(setSalary)。

7. 优先使用不可变的类。

LocalDate类以及java.time包中的其他类是不可变的——没有方法能够修改对象的状态。类似plusDays的方法并不会更改对象,而是会返回状态已修改的新对象。

更改对象的问题在于,如果多个线程试图同时更新一个对象,就会发生并发更改,其结果是不可预料的。如果类是不可变的,就可以安全地在多个线程间共享其对象。

因此,要尽可能让类是不可能变的,这是一个很好的想法。对于表示值的类,如一个字符串或一个时间点,这尤其容易。计算会生成新值,而不是更新原来的值。

当然,并不是所有类都应当是不可变的。如果员工加薪时让raiseSalary方法返回一个新的Employee对象,这会很奇怪。

本章介绍了有关对象和类的基础知识,这使得Java可以作为一种“基于对象”的语言。要真正做到面向对象,程序设计语言还必须支持继承和多态。Java提供了对这些特性的支持,具体内容将在下一章中介绍。

【学习参考书籍:《Java核心技术卷I》】 

Java学习记录:对象与类(二)的评论 (共 条)

分享到微博请遵守国家法律