Java代码规范
一、前言
本文参考《阿里巴巴Java开发手册》,这本书主要定义了一些代码的规范以及一些注意事项。我只根据我自己的不足,摘录了一些内容,方便以后查阅。
二、读书笔记
- 命名
1、代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束。
-
常量定义
-
OOP规约
-
集合处理
1、ArrayList的subList结果不可强转成ArrayList,否则会抛出 ClassCastException异常: java.util.RandomAccessSubList cannot be cast to java.util.ArrayList。subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
2、在 subList 场景中, 高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生 ConcurrentModificationException 异常。
3、使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一样的数组,大小就是 list.size()。
4、使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
5、泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法, 而<? super T>不能使用 get 方法,做为接口调用赋值时易出错。
6、不要在 foreach 循环里进行元素的 remove/add 操作。 remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
正例: Iterator<String> it = a.iterator(); while (it.hasNext()) { String temp = it.next(); if (删除元素的条件) { it.remove(); } } 反例: List<String> a = new ArrayList<String>(); a.add("1"); a.add("2"); for (String temp : a) { if ("1".equals(temp)) { a.remove(temp); } }
7、集合初始化时, 指定集合初始值大小。
9、使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历。
10、在 java 集合类库中,List 的 contains 方法普遍时间复杂度是 O(n) ,如果在代码中需要频繁调用 contains 方法查找数据,可以先将 list 转换成 HashSet 实现,将 O(n) 的时间复杂度降为 O(1) 。
11、高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 分段锁技术 |
TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |
-
控制语句
1、在 if/else/for/while/do 语句中必须使用大括号。 即使只有一行代码,避免使用单行的形式: if (condition) statements;
2、表达异常的分支时, 少用 if-else 方式。如果非得使用 if()...else if()...else...方式表达逻辑,避免后续代码维护困难, 请勿超过 3 层。
3、除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
正例: final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } 反例: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... }
-
异常处理
1、Java 类库中定义的一类 RuntimeException 可以通过预先检查进行规避,而不应该通过 catch 来处理,比如: IndexOutOfBoundsException, NullPointerException 等等。
- 日志规约
1、应用中不可直接使用日志系统(Log4j、 Logback) 中的 API,而应依赖使用日志框架SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。
private static final Logger logger = LoggerFactory.getLogger(Abc.class);
2、日志文件推荐至少保存 15 天,因为有些异常具备以“周”为频次发生的特点。
3、对 trace/debug/info 级别的日志输出,必须使用条件输出形式或者使用占位符的方式。
说明: logger.debug("Processing trade with id: " + id + " symbol: " + symbol); 如果日志级别是 warn,上述日志不会打印,但是会执行字符串拼接操作,如果 symbol 是对象,会执行 toString()方法,浪费了系统资源,执行了上述操作,最终日志却没有打印。 正例: (条件) if (logger.isDebugEnabled()) { logger.debug("Processing trade with id: " + id + " symbol: " + symbol); } 正例: (占位符) logger.debug("Processing trade with id: {} symbol : {} ", id, symbol);
4、避免重复打印日志,浪费磁盘空间,务必在 log4j.xml 中设置 additivity=false。
<logger name="com.taobao.dubbo.config" additivity="false">
5、谨慎地记录日志。生产环境禁止输出 debug 日志; 有选择地输出 info 日志; 如果使用 warn 来记录刚上线时的业务行为信息,一定要注意日志输出量的问题,避免把服务器磁盘撑爆,并记得及时删除这些观察日志 。
- 其他
1、在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
2、后台输送给页面的变量必须加$!{var}——中间的感叹号。
3、不要在视图模板中加入任何复杂的逻辑。
4、任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
5、对于“明确停止使用的代码和配置”,如方法、变量、类、配置文件、动态配置属性等要坚决从程序中清理出去,避免造成过多垃圾。
6、new BigDecimal(double) 存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。请使用 BigDecimal.valueOf(double) 。
7、SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为static,必须加锁,或者使用 DateUtils 工具类。
正例: 注意线程安全,使用DateTools 。亦推荐如下处理:
public class DateTools
{
private static ThreadLocal<SimpleDateFormat> t1=new ThreadLocal<>();
public static SimpleDateFormat getSdf(String pattern){
SimpleDateFormat simpleDateFormat = t1.get();
if (simpleDateFormat==null){
simpleDateFormat=new SimpleDateFormat(pattern);
}
t1.set(simpleDateFormat);
return simpleDateFormat;
}
}
说明: 如果是 JDK8 的应用,可以使用 Instant 代替 Date, LocalDateTime 代替 Calendar,DateTimeFormatter代替Simpledateformatter,官方给出的解释:simple beautiful strong immutable thread-safe。
// Instant 相当于 java.util.Date 类 Instant instant = Instant.now(); instant.getLong(INSTANT_SECONDS); //时间戳 // LocalDate 不包含时间的日期 LocalDate localDate = LocalDate.now(); // 自定义日期时间 LocalDate of = LocalDate.of(2019, 10, 1); LocalDate parse = LocalDate.parse("2019-09-02"); // 日期加减 LocalDate days = parse.plusMonths(1L).minusDays(1L); // 日期比较 System.out.println(of.isBefore(days)); System.out.println(of.isAfter(days)); System.out.println(of.equals(days)); // LocalTime 不包含日期的时间 LocalTime localTime = LocalTime.now(); System.out.println(LocalTime.MAX); System.out.println(LocalTime.MIN); // LocalDateTime 包含了日期及时间,没有偏移信息(时区),相当于 java.util.Calendar 类 LocalDateTime localDateTime = LocalDateTime.now(); LocalDateTime atTime = localDate.atTime(LocalTime.MAX); LocalDateTime atDate = localTime.atDate(LocalDate.of(2019, 10, 14)); // 时间日期格式化,相当于 java.text.SimpleDateFormat LocalDateTime dateTime = LocalDateTime.now(); dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME); dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
书籍链接: 阿里巴巴Java开发手册