Javadoc:Documentation Comments

Code_Train

Java 中的注释

java 中主要有三种注释:

  1. 行内注释(用于注释一行)
  2. 多行注释(用于注释多行)
  3. 文档注释(可以用于生成javadoc)
1
2
3
4
5
6
7
8
9
10
11
12
// 这是行内注释

/*
* 这是多行注释。<br>
* 当然,多行注释也可以转换成单行注释,<br>
* 你应该不会这么干吧。o(^▽^)o
*/


/**
* 这是文档注释,可以用 {@code javadoc} 生成文档。
* Copyright (c) 2009-2014 Xinglin-Tech Corporation. All rights reserved.
*/

对于第一种和第二种注释,比较容易使用,而文档注释(Documentation Comments)使用起来则不是那么容易,我们先来看看javadoc命令的使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
C:\Users\Leo>cls
C:\Users\Leo>javadoc -help
用法: javadoc [options] [packagenames] [sourcefiles] [@files]
-overview <file> 从 HTML 文件读取概览文档
-public 仅显示 public 类和成员
-protected 显示 protected/public 类和成员 (默认值)
-package 显示 package/protected/public 类和成员
-private 显示所有类和成员
-help 显示命令行选项并退出
-doclet <class> 通过替代 doclet 生成输出
-docletpath <path> 指定查找 doclet 类文件的位置
-sourcepath <pathlist> 指定查找源文件的位置
-classpath <pathlist> 指定查找用户类文件的位置
-exclude <pkglist> 指定要排除的程序包列表
-subpackages <subpkglist> 指定要递归加载的子程序包
-breakiterator 计算带有 BreakIterator 的第一个语句
-bootclasspath <pathlist> 覆盖由引导类加载器所加载的
类文件的位置
-source <release> 提供与指定发行版的源兼容性
-extdirs <dirlist> 覆盖所安装扩展的位置
-verbose 输出有关 Javadoc 正在执行的操作的信息
-locale <name> 要使用的区域设置, 例如 en_USen_US_WIN
-encoding <name> 源文件编码名称
-quiet 不显示状态消息
-J<flag> 直接将 <flag> 传递到运行时系统
-X 输出非标准选项的提要

通过标准 doclet 提供:
-d <directory> 输出文件的目标目录
-use 创建类和程序包用法页面
-version 包含 @version
-author 包含 @author
-docfilessubdirs 递归复制文档文件子目录
-splitindex 将索引分为每个字母对应一个文件
-windowtitle <text> 文档的浏览器窗口标题
-doctitle <html-code> 包含概览页面的标题
-header <html-code> 包含每个页面的页眉文本
-footer <html-code> 包含每个页面的页脚文本
-top <html-code> 包含每个页面的顶部文本
-bottom <html-code> 包含每个页面的底部文本
-link <url> 创建指向位于 <url> 的 javadoc 输出的链接
-linkoffline <url> <url2> 利用位于 <url2> 的程序包列表链接至位于 <url> 的文档
-excludedocfilessubdir <name1>:.. 排除具有给定名称的所有文档文件子目录。
-group <name> <p1>:<p2>.. 在概览页面中, 将指定的程序包分组
-nocomment 不生成说明和标记, 只生成声明。
-nodeprecated 不包含 @deprecated 信息
-noqualifier <name1>:<name2>:... 输出中不包括限定符的列表。
-nosince 不包含 @since 信息
-notimestamp 不包含隐藏时间戳
-nodeprecatedlist 不生成已过时的列表
-notree 不生成类分层结构
-noindex 不生成索引
-nohelp 不生成帮助链接
-nonavbar 不生成导航栏
-serialwarn 生成有关 @serial 标记的警告
-tag <name>:<locations>:<header> 指定单个参数定制标记
-taglet 要注册的 Taglet 的全限定名称
-tagletpath Taglet 的路径
-Xdocrootparent <url> 将文档注释中出现的所有后跟 /.. 的 @docRoot 替换为 <url>
-charset <charset> 用于跨平台查看生成的文档的字符集。
-helpfile <file> 包含帮助链接所链接到的文件
-linksourceHTML 格式生成源文件
-sourcetab <tab length> 指定源中每个制表符占据的空格数
-keywords 使程序包, 类和成员信息附带 HTML 元标记
-stylesheetfile <path> 用于更改生成文档的样式的文件
-docencoding <name> 输出编码名称

C:\Users\Leo>

常用命令举例:

1.处理单个 java 文件:

1
F:\eclipse\workspaces\20140719\comee\src\main\java>javadoc -d D:\javadoc -encoding utf-8 -charset utf-8 org\comee\util\RegExpUtils.java

2.处理单个包:

1
F:\eclipse\workspaces\20140719\comee\src\main\java>javadoc -d D:\javadoc -encoding utf-8 -charset utf-8 org.comee.util

3.处理多个包

1
F:\eclipse\workspaces\20140719\comee\src\main\java>javadoc -d D:\javadoc -encoding utf-8 -charset utf-8 org.comee.util org.comee.ws

4.处理整个项目
先在项目的src目录下创建一个 packages.txt 文件,里面罗列项目中所有的包,大致如下:

1
2
3
4
5
6
F:\eclipse\workspaces\20140719\comee\src\main\java>type packages.txt
org.comee.util
org.comee.ws
org.comee.ws.util
org.comee.intellij
F:\eclipse\workspaces\20140719\comee\src\main\java>

生成项目 api :

1
F:\eclipse\workspaces\20140719\comee\src\main\java>javadoc -d D:\javadoc -encoding utf-8 -charset utf-8 @packages.txt

5.利用 eclipse 生成项目的 api :

选择项目,然后右键,选择Export,然后选择javadoc

Eclipse Javadoc

设置javadoc命令的参数options

Eclipse Javadoc Options

写好 Javadoc

写好 Java Documentation Comments 实属不易,为了能更好的来讲述这块,接下来将结合 Eclipse 以及 Java JDK 中源码的文档注释来进行讲解。

Files

下面是 eclipse 中 Files 的 java 文档注释,一般在开发过程中,我们都会在 IDE 中提前配置好文档注释模版,这样在开发的过程中就可以快速生成注释。
Javadoc Files

如上图所示,我们在 Pattern 中使用了 ${year} 来表示当前的年份,除了 ${year} , eclipse 中还提支持如下所示的引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 日期:${date}
* 美元:$$
* 文件:${file_name}
* 包名:${package_name}
* 项目:${project_name}
* 时间:${time}
* 待做:${todo}
* 类名:${type_name}
* 用户:${user}
* 年份:${year}
*/


/**
* 日期:2015年4月16日
* 美元:$
* 文件:TestComments.java
* 包名:com.comee.ws
* 项目:comee
* 时间:上午9:42:38
* 待做:TODO
* 类名:TestComments
* 用户:Leo
* 年份:2015
*/

其他常用的变量还有 ${tags}${see_to_overridden}${see_to_target},分别用于一般的方法、重写的方法和代理方法。

我们先来看一下 JDK 中 System.class 中的 Files 注释:

jdk1.8.0_11:

1
2
3
4
/*
* Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/

jdk1.6.0_45:

1
2
3
4
/* 
* Copyright (c) 2006, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*/

可以看到,这里使用的是多行注释,而非文档注释,但是 eclipse 中默认推荐的是文档注释。通过使用 javadoc 命令生成文档后发现,Files 使用文档注释和多行注释的效果是一致的,都不会出现在生成的 API 文档里。结合注释的含义,我门可以做出恰当的选择:Files 注释应该使用多行注释而非文档注释

很多公司的 Files 注释都是使用的文档注释,总是感觉不妥。

我们再来详细看一下 Files 注释中的内容,可以看出,主要就是版权的说明,一般相对比较固定:

  • Copyright (c) 1994, 2013 中的第一个时间为版权开始年份,第二个时间为此最新作品的发布年份。
  • Copyright (c) 2006 中的时间指此最新作品的发布年份。
  • Oracle 和 ORACLE 都是指公司名。

Type

我们还是先来看一下 System.class 中 Type 的注释:
jdk1.8.0_11:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* The <code>System</code> class contains several useful class fields
* and methods. It cannot be instantiated.
*
* <p>Among the facilities provided by the <code>System</code> class
* are standard input, standard output, and error output streams;
* access to externally defined properties and environment
* variables; a means of loading files and libraries; and a utility
* method for quickly copying a portion of an array.
*
* @author unascribed
* @since JDK1.0
*/

可以看出,Type 的文档注释主要包括类的描述、作者信息以及在哪个版本中被引入的。

jdk1.5及其以后可以使用 <@code System> 来表示 <code>System</code> 。
@author unascribed 表示我接手的时候就有了,我也不知道作者是谁。

@author 的格式一般如下(纯文本、链接、邮件地址):

1
2
3
4
5
6
7
/**
*
* @author Comee
* @author <a href="http://comee.github.io/">Comee</a>
* @author <a href="mailto:shaozl@xinglin-tech.com">Comee</a>
*
*/

@since 从字面的意思上很好理解,所以使用的比较多(如同 @author、@version 一样)。但问题是大家写的时候表达的意思五花八门,常见的有:

  1. 想表达日期/时间:@since 2014-01-01
  2. 想表达可运行的 JDK 版本:@since JDK1.5
  3. 想表达加入这个元素的版本:@since 1.0.0

Oracle 官网给出的解释如下:

http://docs.oracle.com/javase/7/docs/technotes/tools/windows/javadoc.html#since
@since since-text
Adds a “Since” heading with the specified since-text to the generated documentation. The text has no special internal structure. This tag is valid in any doc comment: overview, package, class, interface, constructor, method or field. This tag means that this change or feature has existed since the software release specified by the since-text. For example:
    @since 1.5
For source code in the Java platform, this tag indicates the version of the Java platform API specification (not necessarily when it was added to the reference implementation). Multiple @since tags are allowed and are treated like multiple @author tags. You could use multiple tags if the prgram element is used by more than one API.

以及:

http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html
(The convention once was “ @since JDK1.2” but because this is a specification of the Java Platform, not particular to the Oracle JDK or SDK, we have dropped “JDK”.)

总结如下:

一般我们只需要在 Class 上标注 @since ,当在新版本中添加了新的 Method 时,则需要在 Method 上标注 @since ,其值为新版的版本值。并且 @since JDK1.5 这种标注已经不推荐,现在推荐的是@since 1.5

Constructors

没什么好说的,如下:

1
2
3
4
5
/**
* Don't let anyone instantiate this class.
*/

private System() {
}

Methods

第一句话(通过 . 来识别 )一定要简短,用于显示方法的简单描述,否则,生成的文档中,关于方法的简介部分会比较难看。(变量或者常量的文档注释的第一句话也应当简短明了)

简介:
java_method_lineSeparator_summary

详细介绍:
java_method_lineSeparator_introduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns the system-dependent line separator string. It always
* returns the same value - the initial value of the {@linkplain
* #getProperty(String) system property} {@code line.separator}.
*
* <p>On UNIX systems, it returns {@code "\n"}; on Microsoft
* Windows systems it returns {@code "\r\n"}.
*
* @return the system-dependent line separator string
* @since 1.7
*/

public static String lineSeparator() {
return lineSeparator;
}

在 methods 中经常会用到 @see@link@linkplain 进行标注:

@linkplain 相当于用带链接的文本替换了 @link

@linkplain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* Returns the system-dependent line separator string. It always
* returns the same value - the initial value of the {@linkplain
* #getProperty(String) system property} {@code line.separator}.
*
* <p>On UNIX systems, it returns {@code "\n"}; on Microsoft
* Windows systems it returns {@code "\r\n"}.
*
* @return the system-dependent line separator string
* @since 1.7
*/

public static String lineSeparator() {
return lineSeparator;
}

java_method_lineSeparator_introduce

@link 、@see

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Sets the system properties to the <code>Properties</code>
* argument.
* <p>
* First, if there is a security manager, its
* <code>checkPropertiesAccess</code> method is called with no
* arguments. This may result in a security exception.
* <p>
* The argument becomes the current set of system properties for use
* by the {@link #getProperty(String)} method. If the argument is
* <code>null</code>, then the current set of system properties is
* forgotten.
*
* @param props the new system properties.
* @exception SecurityException if a security manager exists and its
* <code>checkPropertiesAccess</code> method doesn't allow access
* to the system properties.
* @see #getProperties
* @see java.util.Properties
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkPropertiesAccess()
*/

java_method_setProperties_link

如果文档注释中要用到代码,最好的注释方式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* The "standard" output stream. This stream is already
* open and ready to accept output data. Typically this stream
* corresponds to display output or another output destination
* specified by the host environment or user.
* <p>
* For simple stand-alone Java applications, a typical way to write
* a line of output data is:
* <blockquote><pre>
* System.out.println(data)
* </pre></blockquote>
* <p>
* See the <code>println</code> methods in class <code>PrintStream</code>.
*
* @see java.io.PrintStream#println()
* @see java.io.PrintStream#println(boolean)
* @see java.io.PrintStream#println(char)
* @see java.io.PrintStream#println(char[])
* @see java.io.PrintStream#println(double)
* @see java.io.PrintStream#println(float)
* @see java.io.PrintStream#println(int)
* @see java.io.PrintStream#println(long)
* @see java.io.PrintStream#println(java.lang.Object)
* @see java.io.PrintStream#println(java.lang.String)
*/

public final static PrintStream out = null;
1
2
3
4
5
6
7
/**
* <p> For example, to measure how long some code takes to execute:
* <pre> {@code
* long startTime = System.nanoTime();
* // ... the code being measured ...
* long estimatedTime = System.nanoTime() - startTime;}</pre>
*/

其他

@version

提到了 @since 就自然会联想到 @version,因为它们的实参都是版本相关的。@version 要表达的是被标记元素自己的版本(注意对比 @since),也就是说这个版本只要代码改过就应该发生变化,而 @since 是不会变的。

官方文档也解释了怎么用好这个文档标记:通过 SCCS 字符 “%I%, %G%” ,例如 1.39, 02/28/97 (文件版本号, 日期)生成。但实际上很少有项目这么做(至少目前 Oracle JDK 没这么做,甚至都没有使用 @version,或者是使用了但最后由于特殊原因总体移除了),大家一般都是 @version 1.0.0 然后就再也不修改了,不管被标记的元素改了多少次(这样的做法还不如不写)。

当然,通过版本控制系统 hook 来做是比较经典的做法,不过这样总感觉没有把这个标记的能力完全发挥出来。在我们的项目里是这样使用的( @version 1.2.3.4, ${date} ):@version 1.2.3.4, Jun 9, 2014

重点是版本号部分,在这个例子中从左到右(1.2.3.4)分别表示:

  • 兼容性位 1,表示兼容性,如果 +1 了说明这个修改是不兼容的
  • 特性位 2,表示已引入了两个特性,每次 +1 说明引入一个新特性
  • 缺陷修复位 3,表示已经修复了 3 个缺陷,每次 +1 说明修复了一个缺陷
  • 重构位 4,表示已经进行了 4 次重构,每次 +1 说明重构了一次

前面 3 位表达的意义和 Semantic Versioning 建议的一致,重构我觉得非常重要,所以也加了进来。

@exception @throws

这两兄弟的情况比 @link @linkplain 更纠结(人家 link 兄弟最起码可以区分出来使用场景)。按照官方文档解释:它们完全是同义词,没有任何区分。那当年 Sun 在 JDK1.2 的时候为什么要加入 @throws 呢——答案是起名失误了,词性没弄匹配:@throws Exception@exception Exception 更符合语法,代入感更好!(细节:In javadoc, what is the difference between the tags @throws and @exception?

包注释

和前面几点打码风格相关的细节比起来,包注释是具有一定的实用性的。虽然大家可能用得很少,但看得应该比较多,就是这部分:
Java_Package_Comments

这里我们使用了两种方式来生成包文档:

  • package.html:这是 JDK1.5 以前的方式,现在已经不推荐使用
  • package-info.java:目前推荐方式,因为这样可以使用注解

在包上面使用注解?这个用法和在其他地方使用注解一样,只是被标注的元素变成了包,在运行时可以获取到包的注解,然后做你想做的事情吧!

{@value}

这个文档标记非常实用(不光好用),可以用于生成被标记的常量字段的值。

直接用于常量字段时:

1
2
3
4
/**
* 默认命令空间,其值为{@value}。
*/

public static final String DEFAULT_NS = "_NS";

Java_Comments_Tag_value

也可以使用引用方式:

1
2
3
4
5
6
/**
* 返回命名空间。
*
* @return 命名空间,缺省时返回默认的命令空间 {@value #DEFAULT_NS}。
*/

public static final String DEFAULT_NS = "_NS";

Java_Comments_Tag_value_another

{@inheritDoc}

这个标签体现了 Java 面向对象的精辟所在:不但可以类可以集成,连文档都可以继承(足见 Java 在经典面向对象概念上的完备与圆润)。
比如有个计算面积的接口:

1
2
3
4
5
6
/**
* 返回面积。
*
* @return 面积
*/

public float getArea();

它的实现方法标注了 {@inheritDoc}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* {@inheritDoc}
*
* <p>
* 正方形面积计算公式:边长 X 边长
* </p>
*
*/

@Override
public float getArea()
{

//...
return area;
}

最后生成的文档:
Java_Comments_Tag_inheritDoc

  • 基类的文档注释被继承到了子类
  • 子类可以再加入自己的注释(特殊化扩展)
  • @return @param @throws 也会被继承

其实在不写 {@inheritDoc} 的情况下也存在文档注释的继承,具体规则请看inheriting comments

@Deprecated

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Enable or disable finalization on exit; doing so specifies that the
* finalizers of all objects that have finalizers that have not yet been
* automatically invoked are to be run before the Java runtime exits.
* By default, finalization on exit is disabled.
*
* <p>If there is a security manager,
* its <code>checkExit</code> method is first called
* with 0 as its argument to ensure the exit is allowed.
* This could result in a SecurityException.
*
* @deprecated This method is inherently unsafe. It may result in
* finalizers being called on live objects while other threads are
* concurrently manipulating those objects, resulting in erratic
* behavior or deadlock.
* @param value indicating enabling or disabling of finalization
* @throws SecurityException
* if a security manager exists and its <code>checkExit</code>
* method doesn't allow the exit.
*
* @see java.lang.Runtime#exit(int)
* @see java.lang.Runtime#gc()
* @see java.lang.SecurityManager#checkExit(int)
* @since JDK1.1
*/

@Deprecated
public static void runFinalizersOnExit(boolean value) {
Runtime.runFinalizersOnExit(value);
}

Demo:

1
2
3
4
5
6
/**
* @deprecated As of release 1.3, replaced by {@link #getPreferredSize()}
*/

@Deprecated public Dimension preferredSize() {
return getPreferredSize();
}

如果不是简单的对方法进行了重命名,那么 deprecation 可能要描述的更复杂一些,下面是一个撤回方法权限的 Example :

/**
 * Delete multiple items from the list.
 *
 * @deprecated  Not for public use.
 *    This method is expected to be retained only as a package
 *    private method.  Replaced by
 *    {@link #remove(int)} and {@link #removeAll()}
 */
@Deprecated public synchronized void delItems(int start, int end) {
    ...
}

Order of Tags

Include tags in the following order:

  • @author (classes and interfaces only, required)
  • @version (classes and interfaces only, required. See footnote 1)
  • @param (methods and constructors only)
  • @return (methods only)
  • @exception (@throws is a synonym added in Javadoc 1.2)
  • @see
  • @since
  • @serial (or @serialField or @serialData)
  • @deprecated (see How and When To Deprecate APIs)

Ordering Multiple Tags

We employ the following conventions when a tag appears more than once in a documentation comment. If desired, groups of tags, such as multiple @see tags, can be separated from the other tags by a blank line with a single asterisk.

Multiple @author tags should be listed in chronological order, with the creator of the class listed at the top.

Multiple @param tags should be listed in argument-declaration order. This makes it easier to visually match the list to the declaration.

Multiple @throws tags (also known as @exception) should be listed alphabetically by the exception names.

Multiple @see tags should be ordered as follows, which is roughly the same order as their arguments are searched for by javadoc, basically from nearest to farthest access, from least-qualified to fully-qualified, The following list shows this progression. Notice the methods and constructors are in “telescoping” order, which means the “no arg” form first, then the “1 arg” form, then the “2 arg” form, and so forth. Where a second sorting key is needed, they could be listed either alphabetically or grouped logically.

  • @see #field
  • @see #Constructor(Type, Type…)
  • @see #Constructor(Type id, Type id…)
  • @see #method(Type, Type,…)
  • @see #method(Type id, Type, id…)
  • @see Class
  • @see Class#field
  • @see Class#Constructor(Type, Type…)
  • @see Class#Constructor(Type id, Type id)
  • @see Class#method(Type, Type,…)
  • @see Class#method(Type id, Type id,…)
  • @see package.Class
  • @see package.Class#field
  • @see package.Class#Constructor(Type, Type…)
  • @see package.Class#Constructor(Type id, Type id)
  • @see package.Class#method(Type, Type,…)
  • @see package.Class#method(Type id, Type, id)
  • @see package

附录

Javadoc 文档标记总表(JDK 1.7):

TAG Introduced in JDK/SDK
@author 1.0
{@code} 1.5
{@docRoot} 1.3
@deprecated 1.0
@exception 1.0
{@inheritDoc} 1.4
{@link} 1.2
{@linkplain} 1.4
{@literal} 1.5
@param 1.0
@return 1.0
@see 1.0
@serial 1.2
@serialData 1.2
@serialField 1.2
@since 1.1
@throws 1.2
{@value} 1.4
@version 1.0

参考文献

[1] 语义化版本2.0.0
[2] 细节见真功之 Javadoc
[3] SCCS Version
[4] javadoc