包、注释及JAR文件
包
包名
类的导入
静态导入
在包中增加类
包访问
类路径
设置类路径
JAR文件
创建JAR文件
清单文件
可执行JAR文件
多版本JAR文件
关于命令行选项的说明
文档注释
注释的插入
类注释
方法注释
字段注释
通用注释
包注释
类设计技巧
往期文章
包、JAR文件及文档注释
包
Java
允许使用「包」将类组织在一个集合中,可以类比于文件夹,便于管理。
包名
之所以会使用包, 这主要是因为要确保类名的唯一性,如果不建立包,那么所有的类文件就会堆积在一起,倘若有两个类文件具有相同的名字,那么便会发生冲突,因为编译器不知道你究竟想要执行的是哪个文件。
然而为了「保证包名的绝对唯一性」,要用一个因特网域名以逆序的形式作为包名。
❝因特网域名:Internet域名是Internet网络上的一个服务器或一个网络系统的名字,在全世界,域名都是唯一的。域名的形式是以若干个英文字母和数字组成,由“.”分隔成几部分组成。无论是国际或国内域名,全世界接入Internet网的人都能够准确无误的访问到。
❞
一般对于不同的工程使用不同的子包,如域名horstmann.com
,经过逆序书写就成了com.horstmann
注意:对于编译器来说,嵌套的包之间没有任何的关系,每一个包之间都是一个独立的类集合。
例:
类的导入
一个类可以使用「所属包中的所有类」及其他包中的「公共类」。
通常我们有两种方式来访问另一个包中的所有公共类
使用「完全限定名」,具体的意思是包名后面跟着类名,例如: java.time.LocalDate today=java.time.LocalDate.now()
,显然这种方式比较繁琐,并且看起来有点复杂。使用「import语句」,这是引用其他包中公共类的一种快捷方式,使用时就不需要写完全限定名。
注意:
使用import语句可以导入一个特定的类,也可以导入整个包。 同时,应该将import语句写在源文件的顶部。但位于package包的后面 如果能够明确要导入的类的具体类型,我们应该明确指出 只能使用星号(*)导入一个包,并且是所在包的下一个子包。 如果在导入包时,发生了类命名的冲突,可以在import语句中指明想要使用的是哪个类。 如果命名冲突的两个类都需要使用,那么就应该在使用时将每个类的前面加上各自所属的包名。 在包中定位类是编译器的事情,「类文件中的字节码文件总是使用完整的包名引用其他类。」
来看个例子
静态导入
有一种import语句允许导入「静态方法」和「静态字段」,而不只是类。
如:import static java.lang.System.*
,就可以使用System类的静态方法和静态字段,而不必加类名前缀。
out.println(“We will never say HelloWorld!”)
此外还可以导入特定的方法或字段
import static java.lang.System.out
在包中增加类
要想将类放入包中,「就必须将包的名字放在源文件的开头,放在定义这个包中各个类的代码之前。」
例如Package com.horstmann.corejava
其中,com.horstmann.corejava
是包名,必须放在代码的最前面。
如果在源文件中没有放置Package
语句,那么这个类就属于无名包,无名包顾名思义就是没有包名。
应该将源文件放到与完整包名匹配的子目录中
例如com.horstmann.corejava
包中的所有源文件都应该放置在子目录com/horstmann/corejava
中,编译后的类文件也应该放在相同结构的目录中
来看个例子
其中StaticImport.java
和Employee.java
都属于包Example中的。而TestNoPackage
则属于无名包的
那么他们在项目中的目录结构应该是这样的
要想编译这个无名包的类,只需要切换到基目录,运行javac TestNoPackage.java
和java TestNoPackage
需要注意的是:
编译器处理文件(带有文件分隔符和扩展名 .java
的文件)Java
解释器加载类(带有点(.)分隔符)编译器在编译源文件时「不会」检查目录的结构,如某个源文件开头指明了子目录,但子目录并没有该文件,仍然可以进行编译,如果「它不依赖于其他包,那么就可以通过编译并且不会出现编译错误」,但最终的程序可能无法运行。如果包与目录不匹配,那么虚拟机就找不到类。
包访问
访问权限可以说是一个有效的安全机制,标记为public
的部分可以由任意类使用,标记为private
的部分只能由定义他们的类使用。
那么如果没有使用public
和private
这个部分(类、方法、变量)可以被同一个包中的所有方法访问。
在这种情况下,可以将我们之前定义的Employee类的public
修饰符去掉,那么它就只能被同一个包中的其他类访问。需要注意的是对于类来说,似乎很合情合理。
但我们不建议将变量去掉private
修饰符,因为这会破坏封装,但人们常常会忘记权限修饰符的书写。
例:java.awt
包中的Window
类就是一个典型的示例
public class window extends Container{
String warningString;//这里的变量就是没采用private修饰符
}
注意这里的String warningString
不是私有访问权限,而是默认访问权限,这意味着java.awt
包中的其他类都可以随意访问该变量并且随意地改变它。
一般来说「包不是封闭的实体」,任何人都可以向包中添加更多的类。
看看java.awt
包中的window
类
类路径
类可以存储在文件系统的子目录中,「类的路径必须与包名匹配。」
与此同时,类文件也可以存储在JAR(JAVA归档)文件中。在一个JAR文件中,可以包含多个压缩形式的类文件和子目录,这样既可以节省空间,也可以改善性能。在程序中使用到的第三方的库文件时,你通常得到一个或多个需要包含的JAR文件。
而且JAR文件使用ZIP格式组织文件和子目录。可以使用ZIP工具查看JAR文档。
为了使类能够被多个程序共享。需要做到以下几点:
把类文件放到一个目录中,例如: /home/user/classdir
此目录是包树状结构目录,增加com.horstmann.corejava.Employee
类,那么Employee的类文件就必须在子目录/home/user/classdir/com/horstmann/corejava
中将JAR文件放置在一个目录中,如: /home/user/archives
设置类路径,类路径是所有包含类文件的路径的集合
在UNIX环境中,类路径中的各项之间用冒号分隔
/home/user/classdir:.:/home/user/archives/archive.jar
而在WINDOWS环境中,则以分号分隔
C:\classdir;.;C:\archives\archive.jar
需要注意的是无论是UNIX还是WINDOWS,都用句点(.)表示当前目录
总结一下:类路径包括
基目录: /home/user/classdir
或C:\classes
当前目录(.) JAR文件: /home/user/archives/archive.jar
或C:\archives\archive.jar
从Java6
开始,可以在JAR文件目录中指定通配符。
/home/user/classdir:.:/home/user/archives/'*'
C:\classdir;.;C:\archives\*
需要注意的是在UNIX中,需要对*进行转义,防止shell扩展。
注意
javac编译器总是在当前目录中查找文件,但虚拟机仅在类路径中包含“.”目录的时候才会看当前目录。
这里就有一个很有意思的事情
如果你忘记设置类路径了,那么没关系,因为有个默认类路径会包含“.”目录。 如果你设置了类路径,但忘记包含“.”目录,那么尽管程序可以编译,但是却不能运行,因为虚拟机只有在包含“.”目录时才会查看当前目录。
「类路径所列出的目录和归档文件是搜寻类的起始点」
例如:/home/user/classdir:.:/home/user/archives/archive.jar
假设虚拟机要搜寻com.horstmann.corejava.Employee
类的类文件。
它首先查看Java API
类,显然没有,接着转向类路径查找以下文件:
/home/user/classdir/com/horstmann/corejava/Employee.class
com/horstmann/corjava/Employee.class
(从当前目录开始)com/horstmann/corejava/Employee.class
(在/home/user/archives/archive.jar
中)
以上便是虚拟机查找文件的步骤。
相比之下,编译器查找文件可能更复杂,如果引用了一个类,没有指定这个类的包,那么编译器会首先查找包含这个类的包,查看所有的import指令,此外,它还需要查看源文件是否比类文件新,如果是这样的话,那么源文件就会自动地被重新编译。
一个源文件只能包含一个公共类,并且文件名必须与公共类名相匹配
设置类路径
使用-classpath
或者-cp
,或者Java9
中的--class-path
选项指定路径。
如:java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg
「整个指令必须写在一行中。」
利用-classpath
选项设置路径是首选方法,也可以通过设置CLASSPATH
环境变量来指定。具体实现依赖于所使用的shell
如在bash中,命令如下:
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
在WINDOWS的shell中
set CLASSPATH=C:\classdir;.;C:\archives\archive.jar
直到退出shell,类路径设置均有效。
JAR文件
Java
归档文件可以将应用程序打包,向用户提供一个单独的文件,一个JAR文件既可以包含类文件,也可以包含诸如声音、图像等其他类型的文件。它使用我们熟悉的ZIP文件格式。
创建JAR文件
可以使用jar工具制作JAR文件(在JDK
中的工具,位于jdk/bin
目录下)
语法格式:jar cvf jarFileName file1 file2
例如:jar cvf CalculatorClasses.jar *.class icon.gif
通常jar命令的格式如下
jar options file1 file2.....
可以将应用程序和代码库打包在JAR文件中。
以下是所有的JAR程序选项
选项 | 说明 |
---|---|
c | 创建一个新的或者空的存档文件并加入文件,如果指定文件名是目录,那么jar程序将会对他们进行「递归」处理 |
C | 临时改变目录,如:jar cvf jarFileName.jar -C classes *.class 切换到classes子目录以便增加类文件 |
e | 在清单文件中创建一个入口点 |
f | 指定JAR文件名作为第二个命令行参数,如果没有这个参数,那么jar命令会将结果写至标准输出(创建JAR文件时)或者从标准输入读取(在解压或者列出JAR文件内容时。) |
i | 创建索引文件(用于加快大型文档中的查找) |
m | 将一个清单文件添加到JAR文件中,清单是对归档内容和来源的一个说明,每个归档有一个默认的清单文件。但若想验证归档文件的内容,可以提供自己的清单文件 |
M | 不为条目创建清单文件 |
t | 显示内容表 |
u | 更新一个已有的JAR文件 |
v | 生成详细的输出结果 |
x | 解压文件,如果提供一个或多个文件名,则解压这些文件,否则,解压所有文件 |
o | 存储。但不进行ZIP压缩。 |
清单文件
每个JAR文件都有一个清单文件,「用来描述归档文件的特殊特性。」
清单文件被命名为MANIFEST.MF
它位于JAR文件的一个特殊的META-INF
子目录结构中。
复杂的清单文件可能会被分成更多条目,这些条目被分成多个节。第一个节被称为主节,作用于整个JAR文件。随后的条目用来指定命名实体的属性。
如单个文件、包或者URL,他们必须要以一个Name条目开始,「节与节之间用空行分开。」
如:
Mainfest-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文件,运行:
jar cfm MyArchive.jar mainfest.mf com/mycompany/mypkg/*.class
更新一个已有的JAR文件清单
jar ufm MyArchive.jar mainfest-additions.mf
可执行JAR文件
可使用jar命令中的e选项指定程序的「入口点」。(指定类)
jar cvfe MyProgram.jar com.mycompany.mypkg.MainAppClass files to add
或者在清单文件中「指定程序的主类」(不需要为主类增加扩展名)
Main-Class:com.mycompany.mypkg.MainAppClass
清单文件的最后一行必须以换行符结束,否则清单文件将无法被正确地读取。
用户都可以简单地通过命令来启动程序:java -jar MyProgram.jar
多版本JAR文件
Java9
引入了「多版本JAR」,包含面向不同Java
版本的类文件。为保证向后的兼容性,额外的类文件放在META-INF/versions
目录中。
要增加不同版本的类文件,可以使用–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....
多版本的JAR文件并不适用于不同版本的库或程序,其唯一目的就是支持你的某个特定版本的程序或者库能够在多个不同版本的JDK上运行。如果你增加了功能或者改变了一个API
,就应当提供一个新版本的JAR
关于命令行选项的说明
JDK
的命令行选项一直以来都是「单个横线加多字母选项名」的形式。如:java -jar
,java -XLint:unchecked -classpath...
但jar命令例外,它使用经典的tar命令格式,没有短横线
jar cvf .....
从Java9
开始,转向了一种更加常用的选项格式:「多字母选项名的前面加两个短横线,对于常用的选项可以使用单字母快捷方式。」
文档注释
JDK
包含一个很有用的工具,叫做javadoc
,它可以由源文件生成一个HTML
文档
注释的插入
javadoc
工具从下面几项中抽取信息:
模块 包 公共类与接口 公共的和受保护的字段 公共的和受保护的构造器及方法
应该为以上各个特性编写注释,注释放置在所描述特性的前面。注释以/**开头,以 */结尾,每个文档包含标记及之后紧跟着的自由格式文本。在自由格式文本中可以使用HTML修饰符
如果文档中有其他文件的链接,就应该将这些文件放到包含源文件的目录下的一个子目录doc-files中。javadoc
工具将从源目录将doc-files目录及其内容拷贝到文档目录中。
类注释
类注释必须放在import语句之后,类定义之前。
下面是一个类注释的例子:
/**
* A {@code Window} object is a top-level window with no borders and no
* menubar.
* The default layout for a window is {@code BorderLayout}.
* <p>
* A window must have either a frame, dialog, or another window defined as its
* owner when it's constructed.
*
* In such an environment, when calling {@code setLocation},
* you must pass a virtual coordinate to this method. Similarly,
* calling {@code getLocationOnScreen} on a {@code Window} returns
* virtual device coordinates. Call the {@code getBounds} method
* of a {@code GraphicsConfiguration} to find its origin in the virtual
* coordinate system.
* <p>
* The following code sets the location of a {@code Window}
* at (10, 10) relative to the origin of the physical screen
* of the corresponding {@code GraphicsConfiguration}. If the
* bounds of the {@code GraphicsConfiguration} is not taken
* into account, the {@code Window} location would be set
* at (10, 10) relative to the virtual-coordinate system and would appear
* on the primary physical screen, which might be different from the
* physical screen of the specified {@code GraphicsConfiguration}.
*
* <pre>
* Window w = new Window(Window owner, GraphicsConfiguration gc);
* Rectangle bounds = gc.getBounds();
* w.setLocation(10 + bounds.x, 10 + bounds.y);
* </pre>
*
*
* Due to the asynchronous nature of native event handling, the results
* returned by {@code getBounds}, {@code getLocation},
* {@code getLocationOnScreen}, and {@code getSize} might not
* reflect the actual geometry of the Window on screen until the last
* request has been processed. During the processing of subsequent
* requests these values might change accordingly while the window
* management system fulfills the requests.
* <p>
* An application may set the size and location of an invisible
* {@code Window} arbitrarily, but the window management system may
* subsequently change its size and/or location when the
* {@code Window} is made visible. One or more {@code ComponentEvent}s
* will be generated to indicate the new geometry.
* <p>
* Windows are capable of generating the following WindowEvents:
* WindowOpened, WindowClosed, WindowGainedFocus, WindowLostFocus.
*
* @author Sami Shaio
* @author Arthur van Hoff
* @see WindowEvent
* @see #addWindowListener
* @see java.awt.BorderLayout
* @since 1.0
*/
public class Window extends Container implements Accessible {
其实没有必要在每一行开始都添加星号*,大部分IDE
都是会自动提供星号的,换行改变时,会重新放置星号。
方法注释
每一个方法注释必须放在所描述的方法之前。除了通用标记之外,还可以使用以下标记。
@param variable description
标记给当前方法的参数部分添加一个条目,描述可以占据多行,并且使用HTML标记。「一个方法的@param
标记必须放在一起」
@return description
这个标记给当前方法添加返回部分,描述可以跨多行,并且可以使用HTML标记。
@throws class description
这个标记将添加一个注释,表示这个方法可能抛出异常。
例子:以下是一个方法注释。
/**
* Constructs a new, initially invisible window with the specified
* {@code Frame} as its owner. The window will not be focusable
* unless its owner is showing on the screen.
* <p>
* If there is a security manager set, it is invoked to check
* {@code AWTPermission("showWindowWithoutWarningBanner")}.
* If that check fails with a {@code SecurityException} then a warning
* banner is created.
*
* @param owner the {@code Frame} to act as owner or {@code null}
* if this window has no owner
* @exception IllegalArgumentException if the {@code owner}'s
* {@code GraphicsConfiguration} is not from a screen device
* @exception HeadlessException when
* {@code GraphicsEnvironment.isHeadless} returns {@code true}
*
* @see java.awt.GraphicsEnvironment#isHeadless
* @see #isShowing
*/
public Window(Frame owner) {
this(owner == null ? (GraphicsConfiguration)null :
owner.getGraphicsConfiguration());
ownedInit(owner);
}
字段注释
只需要对公共字段(通常指静态常量)建立文档。
/**
The “Hearts” card suit
*/
public static final int HEARTS=1;
以上便是对一个公共字段进行注释。
通用注释
@since text
会建立一个“since”(始于)条目,text可以是引入这个特性的版本任何描述。
@author name
这个标记将产生一个“author”条目,可以使用多个标记,每个标记代表一个作者,但并不是非得使用这个标记,你的版本控制系统能够更好地跟踪作者。
@ version text
这个标记将产生一个“version”条目,这里的文本可以是对当前版本的任何描述。
@see和@link
可以使用超链接,链接到javadoc
文档的相关部分或外部文档。
@see reference
将在“see also(参见)”部分增加一个超链接。它可以用于类中,也可以用于方法中。这里引用可以有以下选择:
package.class#feature label
<a href="...."> label</a>
"text"
第一种情况最有用,只要提供类、方法或变量的名字,javadoc
就在文档中插入一个超连接。
例如;@see com.horstmann.corejava.Employee#raiseSalary(double)
这会建立一个链接到com.horstmann.corejava.Employee
类的raiseSalary(double)
方法的超链接。
可以省略包名及类名。这样会位于当前包或者当前类中。
一定要使用#来分隔类名和方法名
如果在@see标记后面看到<字符,就需要指定一个**超链接。**可以链接到任何的URL。
@see <a href="www.horstmann.com/corejava.html">The Core java home page</a>
可以指定一个可选的标签作为「链接锚」,如果省略标签,用户只能看到目标代码名或URL
在@see后面标记一个双引号字符,文本显示“see also”部分,例如:
@see “Core Java 2 volume 2”
同时可以为一个特性添加多个@see标记,但必须将他们放在一起。
如果愿意可以在文档注释中的任何位置放置指向其他类或方法的超链接。如:{@ link package.class#feature label}
包注释
要想产生包注释,需要在每一个包目录中添加一个「单独」的文件,可以有如下两个选择:
提供一个名为 package-info .java
的文件,必须包含一个初始的/** */界定的Javadoc
注释,后面是一个package语句,不能包含更多的代码和注释。提供一个名为 package.html
的HTML文件。会抽取标记….之间的所有文本
类设计技巧
总结一下,设计类的一些常用技巧:
一定要保证数据私有。 一定要对数据进行初始化。 不要在类中过多的使用基本类型。 不是所有的字段都需要单独的字段访问器和字段更改器。 分解有过多职责的类。 类名和方法名要能够体现他们的职责。 优先使用不可变类
往期文章
<<< 左右滑动见更多 >>>