以前实习的时候,组里的代码很少有注释,理解起来着实难受。mentor 可能会说:”不懂的问我,随便问”,但人家只是客气,你要是真随便问就输了。

因此,我写代码尽量带有完整的 JavaDoc 注释。

如果不做二次开发,个人觉得其实没必要理解一部分代码的实现逻辑。通过完整的 JavaDoc 了解每个方法在做什么,结合调用链路梳理逻辑,就差不多理解业务了。

有人可能会说:良好的代码本身就是一种注释。可是大部分人的代码命名水平实在不怎么高(包括我),变量名中混入拼音首字母缩写的大有人在,有一种 yyds 的美。

而且不是每个人的代码都优雅、简洁的能让大一新生看懂。如果做不到,还是好好写注释吧。

但是话又说回来,如果你的目的是防御性编程,增强自己不可替代的地位,那确实没必要写 JavaDoc。

类名 JavaDoc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 这是一个展示如何使用详细JavaDoc注释的示例类。
* 此类提供文件读取功能以及一些辅助方法。
*
* @author xyhao
* @since 3.2
* @version 1.0
* @see #readFileContent(String)
* @see #printMessage(String
* @deprecated 使用 {@link String}代替,因为它·······
*/
public class ExampleClass {

}
  • @author 作者是谁
  • @since 从项目的哪个版本引入的
  • @version 该类迭代了几个版本
  • @see 与该类相关联的类、方法、或者 URL
  • @deprecated 该类若准备删除,可以用这个标签表示。但是要给出删除之后的替代方案。

但其实这只对完美的、非常规范的开发流程生效,一般而言,用 @author 标签记录负责人就足够了。

方法名 JavaDoc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 读取指定文件的内容并返回其内容作为一个字符串。
* 如果在读取过程中发生I/O错误,则会抛出IOException。
*
* @param encoding 文件编码格式。
* @return {@link String} 返回文件的内容作为字符串。
* @since 3.2
* @see java.nio.file.Files#readAllBytes(java.nio.file.Path)
* @throws IOException 如果发生输入输出错误(如文件未找到或无法读取)。
*/
public String readFileContent(String encoding) throws IOException {
// 示例代码,实际实现被省略
return "";
}
  • @param 表示方法参数的含义
  • @return 返回值的含义
  • @since 从项目的哪个版本开始引入
  • @see 标记与该方法相关的类、方法、URL 等
  • @throws 如果该方法有抛出异常的可能,用该标签标记异常类型(引申:什么时候抛出异常,什么时候捕获异常?)

方法返回类型一眼就可以看到, 真的需要return 标签吗?

假设你对这个方法一点也不了解,当然也不了解这个方法返回值的具体含义,如果有个 return 标签解释了返回值的具体含义,这不是一件很好的事吗?

成员变量 JavaDoc

通常对于成员变量而言,只需使用无标签的文本描述即可

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 存储用于加密用户密码的盐值。
* <p>
* 盐值是一个随机生成的字符串,它被添加到原始密码中以增加哈希算法的安全性。
* 通过使用不同的盐值,即使两个用户拥有相同的密码,它们的哈希结果也会不同,
* 这样可以有效防止彩虹表攻击等常见的密码破解技术。
* </p>
* <p>
* 在创建新账户或更改密码时会生成一个新的盐值,并且该盐值将与加密后的密码一起存储。
* 当验证用户输入的密码时,需要使用相同的盐值重新计算哈希值并与存储的哈希值进行比较。
* </p>
*/
private String salt;

但为了便于后来者理解,用 @see 标签关联相关信息也可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 存储用于加密用户密码的盐值。
* <p>
* 盐值是一个随机生成的字符串,它被添加到原始密码中以增加哈希算法的安全性。
* 通过使用不同的盐值,即使两个用户拥有相同的密码,它们的哈希结果也会不同,
* 这样可以有效防止彩虹表攻击等常见的密码破解技术。
* </p>
* <p>
* 在创建新账户或更改密码时会生成一个新的盐值,并且该盐值将与加密后的密码一起存储。
* 当验证用户输入的密码时,需要使用相同的盐值重新计算哈希值并与存储的哈希值进行比较。
* </p>
*
* @see <a href="https://baike.baidu.com/item/salt%E5%80%BC/15931315">百度百科:盐值是什么?</a>
*/
private String salt;

如何在 JavaDoc 中引用方法、类、成员变量、URL?

看代码:

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
/**
* 用户账户的基本信息。
*
* 该类提供了用户账户的基本功能,包括密码加密和存储安全相关的属性。
* 加密操作依靠 {@link java.security.MessageDigest} 实现
*
*
* @author xyhao
* @version 1.0
* @since 2025-02-17
* @see #hashPasswordWithSalt(String,String) 引用本类的有参方法
* @see java.util.HashMap#size() 引用别的类的无参方法
* @see #salt 引用成员变量
* @see <a href="https://baike.baidu.com/item/salt%E5%80%BC/15931315">百度百科:盐值是什么?</a>
*/
public class Account {

private String salt;

private String password;

private String hashPasswordWithSalt(String password, String salt) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 将盐值和密码组合起来
String beforeHash = salt + password;
byte[] hashedBytes = md.digest(beforeHash.getBytes());
// 将字节数组转换为Base64编码的字符串
passwordHash = Base64.getEncoder().encodeToString(hashedBytes);
return passwordHash;
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("无法找到SHA-256算法", e);
}
}
}



上文中所有的 JavaDoc 都遵守着这样的格式:

1
2
3
4
5
6
7
8
9
10
11
/**
* xxxxxxxxxxxxxxxxxxxxx
* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
* xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
*
* @xxx xxxxxxxxxxxxx
* @xxx xxxxxxxxxxxxx
* @xxx xxxxxxxxxxxxx
* @xxx xxxxxxxxxxxxx
*/

但其实除了第一行和最后一行的 *外,其他行开头的 *并不是必须的。

有人说带上 *格式更整洁?可读性更高?

那是不是觉得多行字符串的这种写法也很整洁,可读性更高呢?

1
2
3
4
5
String html = "<html>\n" +
" <body>\n" +
" <p>Hello, World!</p>\n" +
" </body>\n" +
"</html>";

有下面的整洁吗?

1
2
3
4
5
6
7
8
// JDK15开始引入的多行文本表达式
String html = """
<html>
<body>
<p>Hello, World!</p>
</body>
</html>
""";



完整示例如下:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;

/**
用户账户的基本信息。
该类提供了用户账户的基本功能,包括密码加密和存储安全相关的属性。
依靠 {@link java.security.MessageDigest} 实现


@author xyhao
@version 1.0
@since 3.2
@see #hashPasswordWithSalt(String,String)
@see java.util.HashMap#size()
@see #salt
@see <a href="https://baike.baidu.com/item/salt%E5%80%BC/15931315">百度百科:盐值是什么?</a>
*/
public class Account {


/**
存储用于加密用户密码的盐值。
<p>
盐值是一个随机生成的字符串,它被添加到原始密码中以增加哈希算法的安全性。
通过使用不同的盐值,即使两个用户拥有相同的密码,它们的哈希结果也会不同,
这样可以有效防止彩虹表攻击等常见的密码破解技术。
</p>
<p>
在创建新账户或更改密码时会生成一个新的盐值,并且该盐值将与加密后的密码一起存储。
当验证用户输入的密码时,需要使用相同的盐值重新计算哈希值并与存储的哈希值进行比较。
</p>

@see #generateSalt()
@see #encryptPassword(String)
@see <a href="https://baike.baidu.com/item/salt%E5%80%BC/15931315">百度百科:盐值是什么?</a>
*/
private String salt;

/**
存储加密后的密码哈希值。
<p>
密码哈希值是通过对用户提供的原始密码和盐值组合后,使用SHA-256算法计算得出的。
它用于在登录时验证用户输入的密码是否正确。
</p>
*/
private String passwordHash;

/**
这个方法的方法名和参数名配合起来非常简洁明了,没啥必要写文本注释

@param password 用户提供的原始密码。
@return 加密后的密码哈希值。
@see #generateSalt()
@see #hashPasswordWithSalt(String, String)
*/
public String encryptPassword(String password) {
// 如果没有设置盐值,则生成一个新的盐值
if (this.salt == null) {
this.salt = generateSalt();
}
return hashPasswordWithSalt(password, this.salt);
}

/**
这个方法的方法名非常简洁明了,没啥必要写文本注释

@return Base64编码的盐值字符串。
*/
private String generateSalt() {
SecureRandom random = new SecureRandom();
byte[] saltBytes = new byte[16];
random.nextBytes(saltBytes);
return Base64.getEncoder().encodeToString(saltBytes);
}

/**
这个方法的方法名和参数名配合起来非常简洁明了,没啥必要写文本注释

@param password 用户提供的原始密码。
@param salt 盐值字符串。
@return 基于SHA-256算法的哈希结果。
@throws IllegalStateException。
*/
private String hashPasswordWithSalt(String password, String salt) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
// 将盐值和密码组合起来
String beforeHash = salt + password;
byte[] hashedBytes = md.digest(beforeHash.getBytes());
// 将字节数组转换为Base64编码的字符串
passwordHash = Base64.getEncoder().encodeToString(hashedBytes);
return passwordHash;
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("无法找到SHA-256算法", e);
}
}

public static void main(String[] args) {
Account account = new Account();
String encryptedPassword = account.encryptPassword("myPassword123");
System.out.println("Encrypted Password: " + encryptedPassword);
System.out.println("Salt: " + account.salt);
}
}

附录:所有 JavaDoc 标签

  1. @author
    • 描述类或接口的作者。
  2. @version
    • 指定API版本信息。
  3. @since
    • 标识从哪个版本开始引入了该特性。
  4. @param
    • 描述方法参数的信息。
  5. @return
    • 描述方法返回值的信息。
  6. @throws / @exception
    • 描述可能抛出的异常。
  7. @deprecated
    • 表示某个类、方法或字段已被弃用,并提供替代方案。
  8. @see
    • 提供指向其他元素的链接,如类、方法或其他资源。
  9. {@link}{@linkplain}
    • 在文本中创建链接到另一个元素,区别在于字体样式({@link}使用代码字体,而{@linkplain}使用普通字体)。
  10. {@docRoot}
    • 代表生成的文档根目录的相对路径。
  11. {@value}
    • 显示静态字段的值。
  12. @serial
    • 用于序列化字段的说明。
  13. @serialField
    • 用于描述Serializable类中serialPersistentFields成员变量的具体字段。
  14. @serialData
    • 描述由writeObjectreadObject方法写入或读取的数据格式。
  15. @code
    • 将一段文本标记为代码样式。
  16. @literal
    • 类似于@code,但不使用等宽字体显示文本。
  17. @inheritDoc
    • 继承父类或接口的文档注释。
  18. @hidden
    • 隐藏特定的成员,使其不出现在生成的文档中。
  19. @review
    • 标记需要审查的部分。
  20. {@index}
    • 为特定术语创建索引条目。
  21. @apiNote
    • 提供关于API使用的额外信息。
  22. @implSpec
    • 描述实现细节,适用于接口默认方法或抽象类中的具体实现。
  23. @implNote
    • 提供关于实现的额外信息。
  24. @param <T>
    • 描述泛型类型参数。