Java代码规范

33 篇文章 3 订阅

前言

由于近年来对于代码质量的要求越来越高,特制定部门级Java代码规范规则集X-JAVA-RULE,整体要求规则可用可查、循序渐进。

可用是指考虑目前已有代码的体量,不满足这些规则的代码能否能被修复,如果工作量巨大不能被修复或者实际适用场景不会导致漏洞,暂时没有必要纳入其中。

比如规则【包名统一使用小写】是个很好的规则,但是考虑到老项目中有大量的代码在大小的包名下面,这里没有纳入规则集X-JAVA-RULE中。

可查是指有工具可以扫描出,而不是人工筛查。在规则后面附上了SonaQube中的相应规则名称。要求在SonaQube可查询对应规则。

比如规则1
在这里插入图片描述

在这里插入图片描述

循序渐进是指以下规则集不是最终版本,根据实际情况后续会不断扩展。规则集会增删,会被继承,但每次不建议加入过多规则,导致无法推进。
比如【包名统一使用小写】可以考虑在后续新项目中加入,此时新建一个继承【X-JAVA-RULE】的规则集【X-JAVA-RULE-EXT】,将这些规则集纳入其中。

以下规则内容部分参考 阿里巴巴Java开发手册《嵩山版》阿里巴巴 Java 开发手册 终极版(1.3.0),二者有出入则参考后者。比如关于集合转数组的规则,在两个版本中不一致。

在《嵩山版》中
在这里插入图片描述
在终极版中
在这里插入图片描述
此处以终极版为准。

规则集X-JAVA-RULE

1. 字符串和包装类型比较要使用equals方法

通常来说使用==或者!=来比较两个字符串或者包装对象都是一个错误,因为这两个符号比较的是内存地址而不是对象的值。

反例:

String firstName = getFirstName(); // String overrides equals
String lastName = getLastName();

if (firstName == lastName) { ... }; // Non-compliant; false even if the strings have the same value

正例:

String firstName = getFirstName();
String lastName = getLastName();

if (firstName != null && firstName.equals(lastName)) { ... };

Strings and Boxed types should be compared using “equals()”

2. 如果equals方法已经被覆盖过 “==” 和 “!=” 不允许用于对象比较

如果一个对象的equals方法没有被覆盖,则默认继承Object的equals方法。此时== 和 != 还有equals实际比较的都是对象在内存中的引用地址,所以都是一样的。
但是当equals方法被覆盖之后,两个内存地址不同的对象有可能值是相同的。因此,覆盖了equals方法的对象通过 ==和 != 来比较值大概率都是错误的(例外的场景是Class对象比较、枚举类型比较)。

反例:

String firstName = getFirstName(); // String overrides equals
String lastName = getLastName();

if (firstName == lastName) { ... }; // Non-compliant; false even if the strings have the same value

正例:

String firstName = getFirstName();
String lastName = getLastName();

if (firstName != null && firstName.equals(lastName)) { ... };

例外
判断一个对象是否属于某个类不会有问题,因为在JVM当中Class实例是单例的:

Class c;
if(c == Integer.class) { // No issue raised
}

枚举类型不需要使用equals方法:

public enum Fruit {
   APPLE, BANANA, GRAPE
}
public boolean isFruitGrape(Fruit candidateFruit) {
  return candidateFruit == Fruit.GRAPE; // it's recommended to activate S4551 to enforce comparison of Enums using ==
}

equals方法中使用==判断对象是否同一个也没有问题:

  public boolean equals(Object other) {
    if (this == other) {  // Compliant
      return false;
    }
 }

Comparing with java.lang.String and boxed types java.lang.Integer, … will not raise an issue.

“==” and “!=” should not be used when “equals” is overridden

3. 【强制】 POJO 类中布尔类型的变量,都不要加 is,否则部分框架解析会引起序列化错误。

反例: 定义为基本数据类型 Boolean isDeleted; 的属性,它的方法也是 isDeleted(), RPC 框架在反向解析的时候, “以为”对应的属性名称是 deleted,导致属性获取不到,进而抛出异常。

[p3c]Do not add ‘is’ as prefix while defining Boolean variable.

4. 【强制】 long 或者 Long 初始赋值时, 使用大写的 L,不能是小写的 l,小写容易跟数字 1 混淆,造成误解。

说明: Long a = 2l; 写的是数字的 21,还是 Long 型的 2?

[p3c]‘L’ instead of ‘l’ should be used for long or Long variable.

5. 【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

正例

public class TimerTaskThread extends Thread {
  public TimerTaskThread() {
    super.setName("TimerTaskThread");
        ...
}

[p3c]A meaningful thread name is helpful to trace the error information,so assign a name when creating threads or thread pools.

6. 【强制】 必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。

正例:

objectThreadLocal.set(userInfo);
try {
    // ...
} finally {
    objectThreadLocal.remove();
}

[p3c]type ‘ThreadLocal’ must call remove() method at least one times.

7. 【强制】 Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用equals。

说明: 推荐使用 java.util.Objects#equals(JDK7 引入的工具类)

正例: “test”.equals(object);

反例: object.equals(“test”);

[p3c]Equals should be invoked by a constant or an object that is definitely not null.

8. 【强制】所有的相同类型的包装类对象之间值的比较,全部使用 equals 方法比较。

说明: 对于 Integer var = ? 在-128 至 127 范围内的赋值, Integer 对象是在 IntegerCache.cache 产生,会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

[p3c]The wrapper classes should be compared by equals method rather than by symbol of ‘==’ directly.

[p3c]To judge the equivalence of floating-point numbers, == cannot be used for primitive types, while equals cannot be used for wrapper classes.

9. 关于基本数据类型与包装数据类型的使用标准如下:

1) 【强制】 所有的 POJO 类属性必须使用包装数据类型。

2) 【强制】 RPC 方法的返回值和参数必须使用包装数据类型。

3) 【推荐】 所有的局部变量使用基本数据类型。

说明: POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何NPE 问题,或者入库检查,都由使用者来保证。

正例: 数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。

反例: 比如显示成交总额涨跌情况,即正负 x%, x 为基本数据类型,调用的 RPC 服务,调用不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出 。

[p3c]Rules for using primitive data types and wrapper classes.

10. 【强制】 关于 hashCode 和 equals 的处理,遵循如下规则:

1) 只要重写 equals,就必须重写 hashCode。 “equals(Object obj)” and “hashCode()” should be overridden in pairs

2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的对象必须重写这两个方法。

3) 如果自定义对象做为 Map 的键,那么必须重写 hashCode 和 equals。

说明: String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象作为 key 来使用。

Correctness - Use of class without a hashCode() method in a hashed data structure

11. 【强制】 ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常, 即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.

说明: subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。

[p3c]Do not cast subList in class ArrayList, otherwise ClassCastException will be thrown.

12. 【强制】在 subList 场景中, 高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生 ConcurrentModificationException 异常。

[p3c]When using subList, be careful to modify the size of original list.

13. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。

说明: 使用 toArray 带参方法,入参分配的数组空间不够大时, toArray 方法内部将重新分配内存空间,并返回新数组地址; 如果数组元素大于实际所需,下标为[ list.size() ]的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集合元素个数一致。

正例:

List<String> list = new ArrayList<String>(2);
list.add("guan");
list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array)

反例: 直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。

[p3c]Do not use toArray method without arguments.

14. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出UnsupportedOperationException 异常。

说明: asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。 Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组。

String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);

第一种情况: list.add(“yangguanbao”); 运行时异常。
第二种情况: str[0] = “gujin”; 那么 list.get(0)也会随之修改。

[p3c]Do not use methods which will modify the list after using Arrays.asList to convert array to list.

15. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

正例:

Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
 String item = iterator.next();
 if (删除元素的条件) {
     iterator.remove();
 }
}

反例:

List<String> list = new ArrayList<String>();
list.add("1");
list.add("2");
for (String item : list) {
 if ("1".equals(item)) {
     list.remove(item);
 }
}

说明: 以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的结果吗?

[p3c]Do not remove or add elements to a collection in a foreach loop.

16. 【强制】 SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。

正例: 注意线程安全,使用 DateUtils。亦推荐如下处理:

private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
 @Override
 protected DateFormat initialValue() {
     return new SimpleDateFormat("yyyy-MM-dd");
 }
};

说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter 代替 SimpleDateFormat,官方给出的解释: simple beautiful strong immutable thread-safe。

[p3c]SimpleDataFormat is unsafe, do not define it as a static variable. If have to, lock or DateUtils class must be used.

17. 【强制】多线程并行处理定时任务时, Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题。

[p3c]Use ScheduledExecutorService instead.

18. 【强制】在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。

说明: 不要在方法体内定义: Pattern pattern = Pattern.compile(规则);

[p3c]When using regex, precompile needs to be done in order to increase the matching performance.

19. 【强制】不能在 finally 块中使用 return, finally 块中的 return 返回后方法结束执行,不会再执行 try 块中的 return 语句。

[p3c]Never use return within a finally block.

20. 【强制】 日期格式化时,传入 pattern 中表示年份统一使用小写的 y。

说明: 日期格式化时, yyyy 表示当天所在的年,而大写的 YYYY 代表是 week in which year( JDK7 之后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的 YYYY就是下一年。

正例: 表示日期和时间的格式如下所示:

new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 

[p3c]Date format string [%s] is error,When doing date formatting, ‘y’ should be written in lowercase for ‘year’.

21. 【强制】 禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。

说明: BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。如: BigDecimal g = new BigDecimal(0.1F); 实际的存储值为: 0.10000000149

正例: 优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。

BigDecimal recommend1 = new BigDecimal("0.1");
BigDecimal recommend2 = BigDecimal.valueOf(0.1); 

[p3c]Avoid using the constructor BigDecimal(double) to convert double value to a BigDecimal object.

22. 【强制】 如上所示 BigDecimal 的等值比较应使用 compareTo()方法,而不是 equals()方法。

说明: equals()方法会比较值和精度( 1.0 与 1.00 返回结果为 false) , 而 compareTo()则会忽略精度。

Correctness - Method calls BigDecimal.equals()

23. 【强制】 在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。

说明一: 如果在 lock 方法与 try 代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二: 如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中, unlock对未加锁的对象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出IllegalMonitorStateException 异常。
说明三: 在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。

正例:

Lock lock = new XxxLock();
// ...
lock.lock();
try {
	doSomething();
	doOthers();
} finally {
	lock.unlock();
}

反例:

Lock lock = new XxxLock();
// ...

try {
	// 如果此处抛出异常,则直接执行 finally 代码块
	doSomething();
	// 无论加锁是否成功, finally 代码块都会执行
	lock.lock();
	doOthers();
} finally {
	lock.unlock();
} 

[p3c]Lock operation [%s] must immediately follow by try block, and unlock operation must be placed in the first line of finally block.

24. 【强制】 在一个 switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有。

说明: 注意 break 是退出 switch 语句块,而 return 是退出方法体。

[p3c]In a switch block, each case should be finished by break/return.

规则集X-JAVA-RULE-EXT(新项目)

1. 【参考】 类名使用 UpperCamelCase 风格,但以下情形例外: DO / BO / DTO / VO / AO /PO / UID 等。 (备注:不适用 例外情况太多了)

正例: ForceCode / UserDO / HtmlDTO / XmlService / TcpUdpDeal / TaPromotion

反例: forcecode / UserDo / HTMLDto / XMLService / TCPUDPDeal / TAPromotion

[p3c]Class names should be nouns in UpperCamelCase except domain models: DO, BO, DTO, VO, etc.

2. 【参考】方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,必须遵从驼峰形式。 (备注:不适用 比如MQ、JDBC,尤其是一些业务上的简写用大写太多了,难以修改)

正例: localValue / getHttpMessage() / inputUserId

[p3c]Method names, parameter names, member variable names, and local variable names should be written in lowerCamelCase.

3. 【强制】 判断所有集合内部的元素是否为空,使用 isEmpty()方法,而不是 size()==0 的方式。

说明: 在某些集合中,前者的时间复杂度为 O(1),而且可读性更好。

Map<String, Object> map = new HashMap<>(16);
if(map.isEmpty()) {
	System.out.println("no element in this map.");
}

Collection.isEmpty() should be used to test for emptiness

4. 【强制】 代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。

反例: _name / _name / $Object / name / name$ / Object$

[p3c]All names should not start or end with an underline or a dollar sign.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lang20150928

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值