更多 Java 高级知识方面的文章,请参见文集《Java 高级知识》
摘要: 阿里巴巴集团推出的《阿里巴巴Java开发手册(正式版)》是阿里巴巴近万名开发同学集体智慧的结晶,以开发视角为中心,详细列举如何开发更加高效、更加容错、更加有协作性,力求知其然,更知其不然,结合正反例,让Java开发者能够提升协作效率、提高代码质量。
作为一个 Java 开发人员,我花了一天时间阅读了这个开发手册,摘要了一些对于我有用的知识点。记录如下,仅供参考。
编程规约
1. 命名规约
- 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
- 类名使用 UpperCamelCase 风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO 等。
- 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。
- 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
- 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。
-
POJO 类中布尔类型的变量,都不要加
is
,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型boolean isSuccess;
的属性,它的方法也是isSuccess()
,RPC 框架在反向解析的时候,“以为”对应的属性名称是success
,导致属性获取不到,进而抛出异常。 - 【推荐】如果使用到了设计模式,建议在类名中体现出具体模式。例如:
public class OrderFactory;
public class LoginProxy;
public class ResourceObserver;
- 【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。
尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
反例:public abstract void f();
- 接口和实现类的命名有两套规则:
1)【强制】对于 Service 和 DAO 类,基于 SOA 的理念,暴露出来的服务一定是接口,内部的实现类用 Impl 的后缀与接口区别。
正例:CacheServiceImpl
实现CacheService
接口。
2)【推荐】如果是形容能力的接口名称,取对应的形容词做接口名(通常是–able 的形式)。
正例:AbstractTranslator
实现Translatable
。
2. 常量定义
-
long
或者Long
初始赋值时,必须使用大写的L
,不能是小写的l
,小写容易跟数字1
混淆,造成误解。
- 不要使用一个常量类维护所有常量,应该按常量功能进行归类,分开维护。如:缓存相关的常量放在类:
CacheConsts
下;系统配置相关的常量放在类:ConfigConsts
下。
3. 格式规约
- 大括号的使用约定。如果是大括号内为空,则简洁地写成
{}
即可,不需要换行;如果是非空代码块则:
1) 左大括号前不换行。
2) 左大括号后换行。
3) 右大括号前换行。
4) 右大括号后还有else
等代码则不换行;表示终止右大括号后必须换行。
if(...) {
...
} else {
...
}
- 任何运算符左右必须加一个空格。
-
缩进采用 4 个空格,禁止使用 tab 字符。
说明:如果使用 tab 缩进,必须设置 1 个 tab 为 4 个空格。IDEA 设置 tab 为 4 个空格时,请勿勾选 Use tab character;而在 eclipse 中,必须勾选 insert spaces for tabs。 - 单行字符数限制不超过 120 个,超出需要换行
StringBuffer sb = new StringBuffer();
//超过 120 个字符的情况下,换行缩进 4 个空格,并且方法前的点符号一起换行
sb.append("zi").append("xin")...
.append("huang")...
.append("huang")...
.append("huang");
- IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,不要使用 windows 格式。
- 没有必要增加若干空格来使某一行的字符与上一行的相应字符对齐。反例:
int a = 1; // 在 a 之前有两个空格
long b = 2;
4. OOP 规约
- 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
- 所有的覆写方法,必须加
@Override
注解。 - 对外暴露的接口签名,原则上不允许修改方法签名,避免对接口调用方产生影响。接口过时必须加
@Deprecated
注解,并清晰地说明采用的新接口或者新服务是什么。 - 不能使用过时的类或方法。
-
Object
的equals
方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals
。
正例:"test".equals(object);
反例:object.equals("test");
说明:推荐使用java.util.Objects#equals
(JDK7 引入的工具类) - 所有的相同类型的包装类对象之间值的比较,全部使用
equals
方法比较。
说明:对于Integer var=?
在-128 至 127 之间的赋值,Integer
对象是在IntegerCache.cache
产生,会复用已有对象,这个区间内的Integer
值可以直接使用==
进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals
方法进行判断。 - 关于基本数据类型与包装数据类型的使用标准如下:
1) 所有的 POJO 类属性必须使用包装数据类型。
2) RPC 方法的返回值和参数必须使用包装数据类型。
3) 所有的局部变量【推荐】使用基本数据类型。
说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何 NPE 问题,或者入库检查,都由使用者来保证。
正例:数据库的查询结果可能是null
,因为自动拆箱,用基本数据类型接收有 NPE 风险。
反例:比如显示成交总额涨跌情况,即正负x%
,x
为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示:0%
,这是不合理的,应该显示成中划线-
。所以包装数据类型的null
值,能够表示额外的信息,如:远程调用失败,异常退出。 - 序列化类新增属性时,请不要修改
serialVersionUID
字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID
值。
说明:注意serialVersionUID
不一致会抛出序列化运行时异常。 - 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在
init
方法中。 - 使用索引访问用
String
的split
方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException
的风险。
String str = "a,b,c,,";
String[] ary = str.split(",");
//预期大于 3,结果是 3
System.out.println(ary.length);
- 循环体内,字符串的联接方式,使用
StringBuilder
的append
方法进行扩展。反例:
String str = "start";
for(int i=0; i<100; i++){
str = str + "hello";
}
说明:反编译出的字节码文件显示每次循环都会 new
出一个 StringBuilder
对象,然后进行append 操作,最后通过 toString
方法返回 String
对象,造成内存资源浪费。
5. 集合处理
关于 Java 集合类的学习,参见 Java 集合类
《阿里巴巴Java开发手册(正式版)》 学习笔记 - 集合处理
6. 并发处理
关于 Java 并发编程的学习,参见 Java 并发编程
《阿里巴巴Java开发手册(正式版)》 学习笔记 - 并发处理
7. 控制语句
- 很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
正例:
//伪代码如下
boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
反例:
if ((file.open(fileName, "w") != null) && (...) || (...)) {
...
}
8. 注释规约
- 类、类属性、类方法的注释必须使用 Javadoc 规范,使用
/**内容*/
格式,不得使用// xxx
方式。
- 方法内部单行注释,在被注释语句上方另起一行,使用
//
注释。方法内部多行注释 使用/* */
注释,注意与代码对齐。
9. 其它
- 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则);
- 获取当前毫秒数
System.currentTimeMillis();
而不是new Date().getTime();
说明:如果想获取更加精确的纳秒级时间值,用System.nanoTime()
。在 JDK8 中,针对统计时间等场景,推荐使用Instant
类。
异常日志
关于 Java Exception 异常的学习,参见 Java Exception 异常
关于 Java Log 日志的学习,参见 Java Log 日志
《阿里巴巴Java开发手册(正式版)》 学习笔记 - 异常日志