核心理念
在深入细节之前,请记住最重要的两条原则:

- 代码的编写主要是为了给人阅读的,只是顺便让机器执行。 (Code is written to be read by humans, and only incidentally for machines to execute.)
- 我们遵循这份规范,不是为了规范而规范,而是为了利用集体的智慧,让所有人的代码看起来都像是同一个人写的。 (We follow these rules not to stifle creativity, but to leverage the collective wisdom of the community to produce code that is readable, maintainable, and consistent.)
格式规范
这部分是最容易统一和自动化的。
大括号
-
使用 K&R 风格:左大括号与语句同行,右大括号单独成行。
// 正确 public long calculate() { long sum = 0; for (int i = 0; i < 10; i++) { sum += i; } return sum; } // 错误 (Allman 风格) public long calculate() { // ... } -
空块:一个空的块状结构可以简洁地写成 ,除非它是一个多语句的
if或for语句的一部分,在这种情况下,即使它是空的,也应包含注释。// 正确 void doNothing() {} // 正确 (带注释) void doNothing() { // This is intentionally left empty. }
缩进
- 使用 2 个空格进行缩进,不要使用制表符。
- 连续行对齐:当一行代码过长需要换行时,通常在逗号 后换行,并将后续代码缩进一级(2个空格)。
Method( param1, param2, param3, param4, param5);
空格
- 关键字与括号之间:无空格。
if (condition) { ... } - 方法名与左括号之间:无空格。
MyMethod(param);
- 二元/三元运算符:在运算符两侧使用空格。
int sum = a + b; String name = (firstName != null) ? firstName : "Unknown";
- 逗号、分号:后面使用空格。
int[] arr = {1, 2, 3}; for (int i = 0; i < 10; i++) { ... }
行长度
- 软限制为 100 个字符,超过此限制时,应考虑换行。
- 硬限制为 120 个字符,超过此限制的代码需要经过团队的特殊批准。
- 例外:
import语句、@Annotations和命令行等无法换行的行可以超过限制。
换行
-
换行的首要目标是提高可读性。
(图片来源网络,侵删) -
在逗号 后换行通常是最佳选择。
-
在运算符前换行通常比在运算符后换行更易读。
// 优选 String longName = thisIsAVeryLongVariableName + thisIsAnotherVeryLongVariableName; // 可接受 String longName = thisIsAVeryLongVariableName + thisIsAnotherVeryLongVariableName;
命名规范
清晰、一致的命名是代码可读性的基石。
| 元素 | 规范 | 示例 |
|---|---|---|
| 类名 | PascalCase (大驼峰) |
List, HttpRequest |
| 接口名 | PascalCase (大驼峰) |
Runnable, List (接口名通常用名词或形容词) |
| 方法名 | camelCase (小驼峰) |
calculateSum(), onClick() |
| 变量名 | camelCase (小驼峰) |
myVariable, userName |
| 常量名 | UPPER_SNAKE_CASE |
MAX_VALUE, DEFAULT_TIMEOUT |
| 类成员 (非静态) | camelCase (小驼峰) |
myInstance |
| 类成员 (静态) | UPPER_SNAKE_CASE |
static final int MAX_SIZE = 100; |
| 包名 | 全部小写,点分隔 | com.example.myproject |
| 测试类 | 以 Test
|
额外建议
-
避免缩写:除非是像
i,j,k这样的循环变量,或者max,min,avg等广为人知的缩写,否则,应使用完整的单词。
(图片来源网络,侵删)// 不好 int usrNmb; // 好 int userNumber;
-
避免使用下划线
_:除了常量和枚举值外,不要在标识符中使用下划线。
Java 语言特性
限制使用 @Override
- 必须使用:当重写父类或实现接口的方法时,必须使用
@Override注解。 - 推荐使用:对于实现接口的方法,也推荐使用
@Override注解,因为它能清晰地表明实现关系。
捕获空的异常
- 禁止:不要捕获像
Exception,RuntimeException,Error这样宽泛的异常。 - 禁止:不要捕获异常后不做任何处理(只打印日志或
e.printStackTrace()),如果确实不需要处理,应该重新抛出(throw)或包装后重新抛出。
使用标准的异常类型
- 优先使用 Java 标准库中定义的异常,而不是自己创建异常。
IllegalArgumentException: 参数非法。IllegalStateException: 对象状态不正确。NullPointerException: 对象为 null。IndexOutOfBoundsException: 数组或列表越界。
使用运行时异常
- 除非一个异常是预期调用者必须处理的情况(如文件不存在、网络中断),否则应使用继承自
RuntimeException的异常(非受检异常),这样可以减少try-catch块的嵌套,使代码更简洁。
静态成员访问
-
使用类名来访问静态成员,而不是通过实例。
// 好 boolean flag = MyUtils.isStaticMethodValid(); // 不好 MyUtils utils = new MyUtils(); boolean flag = utils.isStaticMethodValid();
Finalizers (终结器)
- 避免使用:
finalize()方法是不可预测的,并且在性能和安全性方面存在问题,通常有更好的替代方案(如try-with-resources)。
其他重要规范
@Nullable 和 @NonNull
-
强烈推荐:使用
@Nullable和@NonNull注解来明确地表示一个变量、参数或返回值是否可以为 null,这能极大地帮助静态分析工具(如 Error Prone)在编译时发现潜在的 NPE 错误。// 方法可能返回 null @Nullable public String getNullableValue() { ... } // 参数不能为 null public void process(@NonNull String data) { ... }
依赖注入
- 推荐使用:依赖注入(如通过构造函数注入)优于在类内部
new一个依赖对象,这降低了类之间的耦合度,使得单元测试更容易。
不可变性
- 推荐:尽可能创建不可变对象,不可变对象是线程安全的,更容易理解和推理。
- 一个不可变对象应该满足:
- 所有字段都是
final的。 - 对象在创建后,其状态不会发生改变。
- 所有字段都是
最佳实践和原则
避免过度设计
- YAGNI (You Ain't Gonna Need It):只实现当前需要的功能,不要为了“未来可能用到”而添加不必要的代码。
限制每个文件的顶级类数量
- 一个文件一个类:一个
.java文件应该只包含一个顶级类(或接口)。 - 例外:如果一个文件中包含多个紧密相关的枚举,可以放在同一个文件中。
限制使用 导入
- 不要使用:
import com.example.*;,这会降低代码的可读性,因为你无法一眼看出一个类来自哪个具体的包。 - 正确做法:明确导入每个需要的类。
限制使用原生类型
- 优先使用包装类:在需要表示“无值”概念的地方,优先使用包装类(如
Integer而不是int),因为包装类可以为null。 - 例外:在性能敏感的代码(如数组、集合)中,可以使用原生类型以提高效率。
如何获取和实施
- 官方源码:最新的规范始终在 GitHub 上。
- 自动化检查:
- Checkstyle: 可以根据 Google 规范配置,作为构建步骤的一部分自动检查代码格式。
- Error Prone: 一个编译器插件,可以发现很多潜在的错误,包括 NPE。
- Spotless / Lombok: 可以在构建时自动格式化代码,确保所有代码风格一致。
遵循这些规范,你的 Java 代码将更加专业、易于维护,并能与其他开发者的代码无缝协作。
