Java开发规范之一:编程规约

本文谨对阿里巴巴新版开发手册进行复述。

一、编程规约

(一)命名风格

1.代码命名不能以下划线或美元符号开始、结束。

2.不允许拼音加英文、或直接中文。

3.常量全部大写,单词间用下划线隔开。

4.抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类

命名以它要测试的类名开始,以 Test 结尾。

5.【强制】类型与中括号紧挨相连来定义数组。
正例:定义整形数组 int[] arrayDemo;

反例:在 main 参数中,使用 String args[]来定义

6. POJO 类中布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
反例:定义为基本数据类型 Boolean isDeleted;的属性,它的方法也是 isDeleted(),RPC

框架在反向解析的时候,“误以为”对应的属性名称是 deleted,导致

7. 包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。

正例:应用工具类包名为 com.alibaba.ai.util。该包下的类 MessageUtils

反例: com.alibaba.ai.utils

8.杜绝完全不规范的缩写,避免望文不知义.

9. 如果模块、接口、类、方法使用了设计模式,将设计模式体现在名字中。

正例:public class OrderFactory;
public class LoginProxy;

public class ResourceObserver;

10.枚举类名建议带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。
说明:枚举其实就是特殊的常量类,且构造方法被默认强制是私有。
正例:枚举名字为 ProcessStatusEnum 的成员名称:SUCCESS / UNKNOWN_REASON

11.各层的命名规范:

A)Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 作前缀。
2) 获取多个对象的方法用 list 作前缀。
3) 获取统计值的方法用 count 作前缀。
4) 插入的方法用 save/insert 作前缀。
5) 删除的方法用 remove/delete 作前缀。
6) 修改的方法用 update 作前缀
B)领域模型命名规约
1) 数据对象:xxxDO,xxx 即为数据表名。
2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3) 展示对象:xxxVO,xxx 一般为网页名称。
4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJ

(二)常量定义

1.long 或者 Long 初始赋值时,使用大写的 L。Long a = 2L; 

2.不要使用一个常量类维护所有常量,按常量功能进行归类,分开维护。(多个生产环境)

正例:缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 ConfigConsts 下

3.常量的复用层次有五层:跨应用共享常量、应用内共享常量、子工程内共享常量、包
内共享常量、类内共享常量。

1) 跨应用共享常量:放置在二方库中,通常是 client.jar 中的 constant 目录下。
2) 应用内共享常量:放置在一方库中,通常是子模块中的 constant 目录下。

4.如果变量值仅在一个固定范围内变化用 enum 类型来定义。

(三)代码格式 

1. if/for/while/switch/do 等保留字与括号之间都必须加空格。

2. 单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
1) 第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
2) 运算符与下文一起换行。
3) 方法调用的点符号与下文一起换行。
4) 方法调用时,多个参数,需要换行时,在逗号后进行。
5) 在括号前不要换行,见反例。

正例:StringBuffer sb = new StringBuffer(); 
// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
sb.append("zi").append("xin")... 
.append("huang")... 
.append("huang")... 

.append("huang");

3. 方法参数在定义和传入时,多个参数逗号后边必须加空格

4. IDE 的 text file encoding 设置为 UTF-8; IDE 中文件的换行符使用 Unix 格式,

不要使用 Windows 格式。

(四)OOP 规约 

1. 相同参数类型,相同业务含义,才可以使用 Java 的可变参数,避免使用 Object。
说明:可变参数必须放置在参数列表的最后。(提倡同学们尽量不用可变参数编程)

正例:public User getUsers(String type, Integer...  ids) {...}

2. 外部正在调用或者二方库依赖的接口,不允许修改方法签名,避免对接口调用方产生

影响。接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。不能使用过时的类或方法。

3. Object 的 equals 方法容易抛空指针异常,应使用常量或确定有值的对象来调用
equals。
正例:"test".equals(object);

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

5. 除了局部变量使用基本数据类型,其他变量声明皆采用包装类。POJO 类的任何属性不要设定默认值。

6. 序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败

7. 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在 init 方法中

8. POJO 类必须写 toString 方法。如果继承了另一个 POJO 类,注意在前面加一下 super.toString

9. 或者多个同名方法,这些方法应该按顺序放置在一起。类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter/setter方法。

(五)集合处理

1.  关于 hashCode 和 equals 的处理,遵循如下规则:
1) 只要重写 equals,就必须重写 hashCode。
2) 因为 Set 存储的是不重复的对象,依据 hashCode 和 equals 进行判断,所以 Set 存储的
对象必须重写这两个方法。
3) 如果自定义对象作为 Map 的键,那么必须重写 hashCode 和 equals。
说明:String 重写了 hashCode 和 equals 方法,所以我们可以非常愉快地使用 String 对象
作为 key 来使用

2. ArrayList的subList结果不可强转成ArrayList。在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生 ConcurrentModificationException 异常。

3. 使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全
一样的数组,大小就是 list.size()。       
正例:    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 错误。

4. 使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方
法。asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList
体现的是适配器模式,只是转换接口,后台的数据仍是数组。
 String[] str = new String[] { "you", "wu" };
 List list = Arrays.asList(str);
第一种情情况:list.add("yangguanbao"); 运行时异常。

第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。

5. 泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方
法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
  说明:扩展说一下 PECS)原则:第一、频繁往外读取内容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。

6. 不要在 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”,会是同样的

结果吗?

7.  Comparator 比较器,进行比较时需满足:                                                                                                                   1) x,y 的比较结果和 y,x 的比较结果相反。
 2) x>y,y>z,则 x>z。
 3) x=y,则 x,z 比较结果和 y,z 比较结果相同

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:
new Comparator<Student>() { 
@Override 
public int compare(Student o1, Student o2) { 
    return o1.getId() > o2.getId() ? 1 : -1; 

};

8.  集合初始化时,指定集合初始值大小。HashMap 使用 HashMap(int initialCapacity) 初始化。                                                 正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader 
factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。
     反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容
量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能

9.  使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。keySet 其实是遍历了 2 次,一次是转为 Iterator 对象,另一次是从 hashMap 中取出key 所对应的 value。如果是 JDK8,使用 Map.foreach 方法。

10.  

集合类keyvalue Super 说明 
Hashtable 不允许为 null不允许为 null Dictionary 线程安全
ConcurrentHashMap 不允许为 null不允许为 nullAbstractMap 锁分段技术(JDK8:CAS)
TreeMap 不允许为 null允许为 null AbstractMap 线程不安全
HashMap 允许为 null 允许为 null AbstractMap 线程不安全

11.  利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
contains 方法进行遍历、对比、去重操作。

(六)并发处理 

1.  获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明:资源驱动类、工具类、单例工厂类都需要注

2.  1)线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。2)线程池通过 ThreadPoolExecutor 的方式创建

3.  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。

4.  高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能
锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用 RPC 方法

5.  对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造

成死锁。即多个线程的加锁顺序尽可能相同。

6.  并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加
锁,要么在数据库层使用乐观锁,使用 version 作为更新依据。

  说明:如果每次访问冲突概率小于 20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次
数不得小于 3 次。

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

8.  使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown
方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行
至 await 方法,直到超时才返回结果。

9.  使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown
方法,线程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行
至 await 方法,直到超时才返回结果。

10.  避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一
seed 导致的性能下降。在 JDK7 之后,可以直接使用 API ThreadLocalRandom,而在 JDK7 之前,需要编码保
证每个线程持有一个实例。

11.  在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优
化问题隐患。将目标属性声明为 volatile(不稳定) 型。(在单例、工厂、及工具类中适用)

12.  线程计数如果是 JDK8,推荐使用 LongAdder 对象,比 AtomicLong 性能更好(减少乐观锁的重试次数)

13.  HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在
开发过程中可以使用其它数据结构或加锁来规避此风险

(七)控制语句

1.  在高并发场景中,避免使用”等于”判断作为中断或退出的条件;表达异常的分支时,少用 if-else 方式,

这种方式可以改写成:
if (condition) { 
 ... 
 return obj; 

}      
// 接着写 else 的业务逻辑代码; 如果非得使用 if()...else if()...else...方式表达逻辑,避免后续代码维
护困难,请勿超过 3 层。

2.  表达异常的分支时,少用 if-else 方式。

(八)注释规约 

1.  所有的类都必须添加创建者和创建日期的注释信息。

2.  所有的枚举类型字段必须要有注释,说明每个数据项的用途

3.  特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,通过标记扫描,
经常清理此类
标记。线上故障有时候就是来源于这些标记处的代码。

1) 待办事宜(TODO):( 标记人,标记时间,[预计处理时间])
表示需要实现,但目前还未实现的功能。这实际上是一个 Javadoc 的标签,目前的 Javadoc
还没有实现,但已经被广泛使用。只能应用于类,接口和方法(因为它是一个 Javadoc 标签)。
2) 错误,不能工作(FIXME):(标记人,标记时间,[预计处理时间])
在注释中用 FIXME 标记某代码是错误的,而且不能工作,需要及时纠正的情况。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值