Java编码规范


Java编码规范

命名

  • 类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO / PO / UID 等
  • 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
  • 抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾
  • 类型与中括号紧挨相连来表示数组:int[] arrayDemo
  • POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。
  • 包名统一使用小写,点分隔符之间有且仅有一个自然语义的英语单词。包名统一使用单数形式,但是类名如果有复数含义,类名可以使用复数形式。
  • 避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名, 使可理解性降低。
  • 杜绝完全不规范的缩写,避免望文不知义
  • 枚举类名带上 Enum 后缀,枚举成员名称需要全大写,单词间用下划线隔开。(枚举其实就是特殊的常量类,且构造方法被默认强制是私有。)
  • 命名时,表示类型的名词放在词尾(startTime / workQueue / nameList / TERMINATED_THREAD_COUNT)
  • 使用了设计模式,在命名时需体现出具体模式。(public class OrderFactory; public class LoginProxy; public class ResourceObserver;)
  • 接口类方法和属性不加任何修饰符号public也不加(尽量不在接口定义变量,如果一定要,确定与接口方法相关,并且是整个应用的基础常量)
  • 如果是形容能力的接口名称,取对应的形容词为接口名(通常是–able的形容词)
  • Service/DAO 层方法命名规约:
    - 获取单个对象的方法用 get 做前缀
    - 获取多个对象的方法用 list 做前缀,复数结尾
    - 获取统计值的方法用 count 做前缀
    - 插入的方法用 save/insert 做前缀
    - 删除的方法用 remove/delete 做前缀
    - 修改的方法用 update 做前缀
  • 领域模型命名规约:
    - 数据对象:xxxDO,xxx 即为数据表名
    - 数据传输对象:xxxDTO,xxx 为业务领域相关的名称
    - 展示对象:xxxVO,xxx 一般为网页名称
    - POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO

常量命名

  • 不允许任何魔法值
  • long 或者 Long 赋值时,数值后使用大写字母 L,不能是小写字母 l
  • 不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护
  • 缓存相关常量放在类 CacheConsts 下;系统配置相关常量放在类 SystemConfigConsts 下
  • 变量值仅在一个固定范围内变化用 enum 类型来定义。 如果存在名称之外的延伸属性应使用 enum 类型

代码风格

  • if/for/while/switch/do 等保留字与括号之间都必须加空格
  • 赋值运算符=、逻辑运算符&&、加减乘除符号等都需要加空格
  • 采用4个空格缩进,禁止使用Tab字符
  • IDEA设置Tab为4个空格时,请勿勾选 Use tab character;
  • 强制转换值之间不需要任何空格:int second = (int)first + 2
  • 单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
    - 第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进。 
    - 运算符与下文一起换行。 
    - 方法调用的点符号与下文一起换行。 
    - 方法调用中的多个参数需要换行时,在逗号后进行。 
    - 在括号前不要换行。
  • IDE的text file encoding 设置为UTF-8; IDE中文件的换行符使用Unix格式,不要使用Windows格式
  • 单个方法的总行数不超过80行
  • 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可
  • 所有的覆写方法,必须加@Override注解
  • 相同参数类型,相同业务含义,才可以使用Java的可变参数,避免使用Object
  • Object的equals方法容易抛空指针异常,应使用常量或确定有值的对象来调用 equals
  • 推荐使用java.util.Objects#equals(Object a, Object b)
  • 所有整型包装类对象之间值的比较,全部使用equals方法比较
  • Integer var = ?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断
  • 浮点数之间的等值判断采用“尾数+阶码”的编码方式:
    BigDecimal a;
    a.compareTo(b)
  • 禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象,应带string入参
    BigDecimal recommend1 = new BigDecimal("0.1");
    BigDecimal recommend2 = BigDecimal.valueOf(0.1);
  • 所有的POJO类属性必须使用包装数据类型
  • RPC方法的返回值和参数必须使用包装数据类型
  • 所有的局部变量使用基本数据类型
  • 定义DO/DTO/VO等POJO类时,不要设定任何属性默认值
  • 序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值
  • 构造方法里面禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中
  • POJO类必须写toString 方法
  • 禁止在POJO类中,同时存在对应属性xxx的isXxx()和getXxx()方法
  • 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险(String[] any = “a,b,c,,”.split(“,”))
  • 类内方法定义的顺序依次是:公有方法或保护方法 > 私有方法 > getter / setter 方法。
  • getter/setter 方法中,不要增加业务逻辑,增加排查问题的难度
  • 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
  • clone方法默认是浅拷贝,若想实现深拷贝,需覆写clone方法实现域对象的深度遍历式拷贝
  • 如果不允许外部直接通过new来创建对象,那么构造方法必须是private。
  • 工具类不允许有public或default构造方法
  • 日期和时间的格式如下所示: new SimpleDateFormat(“yyyy-MM-dd HH:mm:ss”)
  • 注意Date,Calendar等日期相关类的月份 month取值在0-11之间

集合

  • 只要覆写equals,就必须覆写hashCode。
  • Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须覆写这两种方法
  • 如果自定义对象作为Map的键,那么必须覆写hashCode和equals
  • 判断所有集合内部的元素是否为空,使用isEmpty()方法
  • 在使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要使用含有参数类型为BinaryOperator,参数名为mergeFunction的方法,否则当出现相同key值时会抛出IllegalStateException异常
  • 使用java.util.stream.Collectors类的toMap()方法转为Map集合时,一定要注意当value为null时会抛NPE异常
  • ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异 常
  • 使用Map的方法keySet()/values()/entrySet()返回集合对象时,不可以对其进行添加元素操作,否则会抛出UnsupportedOperationException异常。
  • Collections类返回的对象,如:emptyList()/singletonList()等都是immutable list,不可对其进行添加或者删除元素的操作
  • 在subList场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、 增加、删除产生ConcurrentModificationException异常。
  • 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一 致、长度为0的空数组(直接使用toArray无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现 ClassCastException 错误。)
  • 使用toArray带参方法,数组空间大小的length:
    - 等于0,动态创建与size相同的数组,性能最好
    - 大于0但小于size,重新创建大小等于size的数组,增加GC负担
    - 等于size,在高并发情况下,数组创建完成之后,size正在变大的情况下,负面影响与 2 相同
    - 大于size,空间浪费,且在size处插入null值,存在NPE隐患
  • 在使用Collection接口任何实现类的addAll()方法时,都要对输入的集合参数进行NPE判断
  • 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法, 它的add/remove/clear方法会抛出UnsupportedOperationException异常
  • 在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行instanceof判断,避免抛出ClassCastException异常
  • 不要在foreach循环里进行元素的remove/add 操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁
  • 在JDK7版本及以上,Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会抛IllegalArgumentException异常。
    - x,y的比较结果和y,x的比较结果相反。
    - x>y,y>z,则x>z。
    - x=y,则x,z比较结果和y,z比较结果相同
  • 集合初始化时,指定集合初始值大小。HashMap(int initialCapacity)初始化暂时无法确定集合大小,那么指定默认值(16)即可
  • 使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。keySet遍历了2次,一次是转Iterator一次是从hashMap中取出key对应的value。而entrySet只是遍历了一次
  • 高度注意Map类集合K/V能不能存储null值的情况,如下表格
  • 合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳 定性(unorder)带来的负面影响。
  • 说明:有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是一定的。
    ArrayList是order/unsort;
    HashMap是unorder/unsort;
    TreeSet是 order/sort。
  • 利用Set元素唯一的特性,可以快速对一个集合进行去重操作

控制语句

  • switch 块内,每个 case 要么通过 continue/break/return 等来终止,要么注释说明程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default语句并且放在最后,即使它什么代码也没有。(注意 break 是退出 switch 语句块,而 return 是退出方法体。)
  • switch 括号内的变量类型为 String,注意null判断
  • 三目运算符 condition? 表达式1: 表达式2中,高度注意表达式1和2在类型对齐 时,可能抛出因自动拆箱导致的 NPE 异常
    以下两种场景会触发类型对齐的拆箱操作:
    1)	表达式1或表达式2的值只要有一个是原始类型。 
    2)	表达式1或表达式2的值的类型不一致,会强制拆箱升级成表示范围更大的那个类型。
    Integer a = 1;
    Integer b = 1;
    Integer c = null;
    Boolean flag = false;
    Integer re = (flag?a*b:c)
    a*b为int,c会拆箱成int,抛出异常
  • 在高并发场景中,避免使用”等于”判断作为中断或退出的条件,如果并发控制没有处理好,容易产生等值判断被“击穿”,如抢购中库存击穿成负数,==0程序不会停止
  • 可以将复杂的判断逻辑用一个好理解的boolean变量替代
  • 不要在表达式中赋值,赋值语句需要清晰地单独成为一行。
  • 循环体中的语句要考量性能,定义对象、变量、 获取数据库连接等语句最后放到循环体外面
  • 公开接口需要进行入参保护,尤其是批量操作的接口
  • 下列情形,需要进行参数校验:
    1)	调用频次低的方法。 
    2)	执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致 中间执行回退,或者错误,那得不偿失。
    3)	需要极高稳定性和可用性的方法。 
    4)	对外提供的开放接口,不管是 RPC/API/HTTP 接口。 
    5)	敏感权限入口
  • 下列情形,不需要进行参数校验
    1)	极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查。 
    2)	底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。 
    3)	被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。

注释规约

  • 类、类属性、类方法的注释必须使用 Javadoc 规范
  • 所有的枚举类型字段必须要有注释,说明每个数据项的用途
  • 注释代码需要说明,如果没用可以删除

其他

  • 使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
    private static final Pattern pattern = Pattern.compile(regexRule);
  • Apache BeanUtils 性能较差,可以使用其他方案比如 Spring BeanUtils
  • 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存
  • 对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余

异常处理

  • 对大段代码进行 try-catch,使程序无法根据不同的异常做出正确的应激反应,也不利于定位问题,这是一种不负责任的表现
  • 最外层的业务使用者,必须处理异常,将其转化为用户可以理解的内容。
  • 事务场景中,抛出异常被 catch 后,如果需要回滚,一定要注意手动回滚事务
  • finally 块必须对资源对象、流对象进行关闭,有异常也要做 try-catch。
  • 不要在 finally 块中使用 return
    try 块中的 return 语句执行成功后,并不马上返回,而是继续执行 finally 块中的语句,如果此处存在 return 语句,则在此直接返回,无情丢弃掉 try 块中的返回点
  • 捕获异常与抛异常,必须是完全匹配,或者捕获异常是抛异常的父类。
  • 在调用 RPC、二方包、或动态生成类的相关方法时,捕捉异常必须使用 Throwable 类来进行拦截
  • 方法的返回值可以为 null,不强制返回空集合,或者空对象等,必须添加注释充分说明什么情况下会返回 null 值
    明确防止 NPE 是调用者的责任。即使被调用方法返回空集合或者空对象,对调用者来说,也并非高枕无忧,必须考虑到远程调用失败、序列化失败、运行时异常等场景返回 null 的情况。
  • 注意 NPE 产生的场景:
    1)	返回类型为基本数据类型,return 包装数据类型的对象时,自动拆箱有可能产生 NPE。 
        反例:public int f(){ return Integer对象}, 如果为null自动解箱抛 NPE。 
    2)	数据库的查询结果可能为 null。 
    3)	集合里的元素即使 isNotEmpty,取出的数据元素也可能为 null。 
    4)	远程调用返回对象时,一律要求进行空指针判断,防止 NPE。 
    5)	对于 Session 中获取的数据,建议进行 NPE 检查,避免空指针。 
    6)	级联调用 obj.getA().getB().getC();一连串调用,易产生 NPE。
    使用 JDK8 的 Optional 类来防止 NPE 问题。
  • 定义时区分 unchecked / checked 异常,避免直接抛出 new RuntimeException(), 更不允许抛出 Exception 或者 Throwable,应使用有业务含义的自定义异常。推荐业界已定义过的自定义异常,如:DAOException / ServiceException 等。

ORM映射

  • 在表查询中,一律不要使用 * 作为查询的字段列表,需要哪些字段必须明确写明
    1)	增加查询分析器解析成本。
    2)	增减字段容易与 resultMap 配置不一致。
    3)	无用字段增加网络消耗,尤其是 text 类型的字段。 
  • POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
  • 不要用 resultClass 当返回参数,即使所有类属性名与数据库字段一一对应,也需要定义;反过来,每一个表也必然有一个与之对应。 (配置映射关系,使字段与 DO 类解耦,方便维护)
  • 不允许直接拿 HashMap 与 Hashtable 作为查询结果集的输出。
  • 不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字段,都进行 update table set ,这是不对的。执行 SQL 时, 不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
  • @Transactional 事务不要滥用。事务会影响数据库的 QPS,另外使用事务的地方需要考虑各方面的回滚方案。

  目录