阿里Java开发规约(2)
本文是对阿里插件中规约的详细解释二,关于插件使用,请参考这里
- 及时清理不再使用的代码段或配置信息。 说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余
Positive example: For codes which are temporarily removed and likely to be reused, use /// to add a reasonable note. public static void hello() { /// Business is stopped temporarily by the owner. // Business business = new Business(); // business.active(); System.out.println("it's finished"); }
- 后台输送给页面的变量必须加感叹号,${var}——中间加感叹号!。 说明:如果var=null或者不存在,那么${var}会直接显示在页面上
<input type="text" name="email" value="$!email"/> <input type="text" name="email" value="$!{email}"/>
- 在if/else/for/while/do语句中必须使用大括号,即使只有一行代码,避免使用下面的形式:if (condition) statements;
- 在subList场景中,高度注意对原列表的修改,会导致子列表的遍历、增加、删除均产生ConcurrentModificationException异常;
注:原因从源码可以看出,当子list添加删除元素时,也会相应添加删除本list。但反之就会出现异常。至于Java为何要这样设计,不得而知。
Negative example: List<String> originList = new ArrayList<String>(); originList.add("22"); List<String> subList = originList.subList(0, 1); //warn originList.add("22");
-
在一个switch块内,每个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使它什么代码也没有。
- 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。 说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则);
注:为代码效率考虑
public class XxxClass { // Use precompile private static Pattern NUMBER_PATTERN = Pattern.compile("[0-9]+"); public Pattern getNumberPattern() { // Avoid use Pattern.compile in method body. Pattern localPattern = Pattern.compile("[0-9]+"); return localPattern; } }
-
多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
注:当处理多个并行任务时,如果需要线程异常不互相影响,用ScheduledExecutorService代替Timer//org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { //do something } },initialDelay,period, TimeUnit.HOURS);
- 定义DO/DTO/VO等POJO类时,不要加任何属性默认值。(注:跟前面的用包装类一脉相承)
- 对于Service和DAO类,基于SOA的理念,暴露出来的服务一定是接口,内部的实现类用Impl的后缀与接口区别
注:SOA:面向服务架构(Service-Oriented Architecture) - 常量命名应该全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长
public class ConstantNameDemo { /** * max stock count */ public static final Long MAX_STOCK_COUNT = 50000L;
- 异常类命名使用Exception结尾
- 循环体内,字符串的联接方式,使用StringBuilder的append方法进行扩展。
-
必须回收自定义的ThreadLocal变量,尤其在线程池场景下,线程经常会被复用,如果不清理自定义的 ThreadLocal变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally块进行回收。
- 所有的包装类对象之间值的比较,全部使用equals方法比较。
说明:对于Integer var=?在-128至127之间的赋值,Integer对象是在IntegerCache.cache产生,会复用已有对象,这个区间内的Integer值可以直接使用==进行判断,
但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。 Integer a = 235; Integer b = 235; if (a.equals(b)) { //相等 } - 所有的抽象方法(包括接口中的方法)必须要用javadoc注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。 说明:如有实现和调用注意事项,请一并说明。
/** * fetch data by rule id * * @param ruleId rule id * @param page page number * @param jsonContext json format context * @return Result<XxxxDO> */ Result<XxxxDO> fetchDataByRuleId(Long ruleId, Integer page, String jsonContext);
- 所有的枚举类型字段必须要有注释,说明每个数据项的用途。
- 所有的类都必须添加创建者信息。
- 所有的覆写方法,必须加@Override注解。(注:可以让编译器提前发现错误)
- 所有编程相关的命名均不能以下划线或美元符号开始
- 抽象类命名使用Abstract或Base开头
- 方法内部单行注释,在被注释语句上方另起一行,使用//注释。方法内部多行注释使用/* */注释。注意与代码对齐。
- 方法名、参数名、成员变量、局部变量都统一使用lowerCamelCase,必须遵从驼峰形式
- 注意 Math.random() 这个方法返回是double类型,注意取值的范围[0,1)(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。
Negative example: Long randomLong =(long) (Math.random() * 10); Positive example: Long randomLong = new Random().nextLong();
- 测试类命名以它要测试的类的名称开始,以Test结尾
- 类、类属性、类方法的注释必须使用javadoc规范,使用/**内容*/格式,不得使用//xxx方式和/*xxx*/方式。
说明:在IDE编辑窗口中,javadoc方式会提示相关注释,生成javadoc可以正确输出相应注释;在IDE中,工程调用方法时,不进入方法即可悬浮提示方法、参数、返回值的意义,提高阅读效率。 /** * * XXX class function description. * */ public class XxClass implements Serializable { private static final long serialVersionUID = 113323427779853001L; /** * id */ private Long id; /** * title */ private String title; /** * find by id * * @param ruleId rule id * @param page start from 1 * @return Result<Xxxx> */ public Result<Xxxx> funcA(Long ruleId, Integer page) { return null; } }
- 类名使用UpperCamelCase风格,必须遵从驼峰形式,但以下情形例外:(领域模型的相关命名)DO / BO / DTO / VO / DAO
- 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors各个方法的弊端: 1)newFixedThreadPool和newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。 2)newCachedThreadPool和newScheduledThreadPool: 主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。 Positive example 1: //org.apache.commons.lang3.concurrent.BasicThreadFactory ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build()); Positive example 2: ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); //Common Thread Pool ExecutorService pool = new ThreadPoolExecutor(5, 200, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); pool.execute(()-> System.out.println(Thread.currentThread().getName())); pool.shutdown();//gracefully shutdown Positive example 3: <bean id="userThreadPool" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> <property name="corePoolSize" value="10" /> <property name="maxPoolSize" value="100" /> <property name="queueCapacity" value="2000" /> <property name="threadFactory" value= threadFactory /> <property name="rejectedExecutionHandler"> <ref local="rejectedExecutionHandler" /> </property> </bean> //in code userThreadPool.execute(thread);
- 获取当前毫秒数:System.currentTimeMillis(); 而不是new Date().getTime();
注:这是从效率考虑,不用生成冗余的DAT对象 -
说明:如果想获取更加精确的纳秒级时间值,用System.nanoTime。在JDK8中,针对统计时间等场景,推荐使用Instant类。 public class TimeMillisDemo { public static void main(String args[]) { // Positive example: long a = System.currentTimeMillis(); // Negative example: long b = new Date().getTime(); System.out.println(a); System.out.println(b); } }
返回类型为基本数据类型,return包装数据类型的对象时,自动拆箱有可能产生NPE
public int method() { Integer a = null; return a; }
- 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed 导致的性能下降。说明:Random实例包括java.util.Random 的实例或者 Math.random()的方式。
说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。 ThreadFactory namedThreadFactory = new ThreadFactoryBuilder() .setNameFormat("demo-pool-%d").build(); ExecutorService singleThreadPool = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy()); singleThreadPool.execute(()-> System.out.println(Thread.currentThread().getName())); singleThreadPool.shutdown();
-
避免用Apache Beanutils进行属性的copy。
说明:Apache BeanUtils性能较差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。 TestObject a = new TestObject(); TestObject b = new TestObject(); a.setX(b.getX()); a.setY(b.getY());
- 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。(注:需要了解JVM机制)
- 除常用方法(如getXxx/isXxx)等外,不要在条件判断中执行复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量,以提高可读性。
说明:很多if语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢? Negative example: if ((file.open(fileName, "w") != null) && (...) || (...)) { ... } Positive example: boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... }
- 集合初始化时,指定集合初始值大小。
说明:HashMap使用如下构造方法进行初始化,如果暂时无法确定集合大小,那么指定默认值(16)即可。 Negative example: Map<String, String> map = new HashMap<String, String>(); Positive example: Map<String, String> map = new HashMap<String, String>(16);
---栖息之鹰(一个外表懒洋洋的内心有激情的程序员)
此博客为笔者原著,转载时请注明出处,谢谢!