阿里java开发手册阅读心得

最近抽空学习了下阿里之前开放出来的java开发手册,其中提出的规约有的是基于代码书写规范的统一,有的是基于代码的安全性、执行效率考虑的。其中提到的大部分要求在实际编码中都是很好的建议,也有个别建议在实际项目中可能并不合适,要视项目的规模、场景决定。这里总结了其中感觉比较有价值的一些规范:
 
1.所有的覆写方法,必须加@Override注解
这一点自己还是很有感触的,在很多项目代码中经常看到覆写父类的方法上不带该注解,通常情况下编译也不会报错。加上该注解就是明确告知代码阅读者该方法是多态的,同时提供了编译期检查,如果继承族上无该方法签名或方法签名被改动,编译器会报错。
 
2.避免通过一个类的对象引用访问此类的静态变量或静态方法
通过对象访问静态变量或静态方法会增加编译器解析成本,应该直接用类名来访问。在追求性能的代码中需要注意这一点
 
3.使用常量或确定有值的对象来调用equals
NPE问题是java中最常见的问题,凡是涉及NPE问题的地方都需要注意。如果用null对象调用equals,会报空指针异常。所以好的习惯是使用"test".equals(object),而非object.equals("test")。如果觉得别扭,JDK7之后的版本可以使用Objects.equals(a,b)
 
4.使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有IndexOutOfBoundsException的风险。
说明:
String str = "a,b,c,,";
String[] ary = str.split(",");
// 预期大于3,结果是3
System.out.println(ary.length);
 
5.循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
反例:
String str = "start";
for (int i = 0; i < 100; i++) {
        str = str + "hello";
}
通过反编译可以发现,java代码在执行String的加法操作时,实际使用的是StringBuilder的append方法,所以,在该例子中,
反编译出的字节码文件显示每次循环都会new出一个StringBuilder对象,然后进行append操作,最后通过toString方法返回String对象,造成内存资源浪费。
正确写法为:
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("start");
for (int i = 0; i < 100; i++) {
    strBuilder.append("hello");
}
String str = strBuilder.toString();
 
6.慎用Object的clone方法来拷贝对象。
说明:对象的clone方法默认是浅拷贝,若想实现深拷贝需要重写clone方法实现域对象的深度遍历式拷贝。
java中但凡涉及对象赋值的地方基本都是拷贝的引用,包括等号赋值、集合框架中的添加对象等。在需要用到clone时先考虑清楚是否确实需要对象的内存副本,再结合需要的拷贝深度去实现对象内部包含对象的clone方法,有时可能还要进一步实现对象的对象的对象中的clone方法
 
7.在Set或Map的键使用对象时,hashCode和equals要同时重写
哈希表是数组加链表的结构,hashCode决定了对象在数组中的位置,equals决定了对象在链表中的位置
 
8.不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式
 
9.Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常
(1) x,y的比较结果和y,x的比较结果相反。
(2) x>y,y>z,则x>z。
(3) x=y,则x,z比较结果和y,z比较结果相同。
这里要注意的是在实现compare的方法时,要对两个对象的'>','<','='三种情况都要考虑到。'='的情况往往是容易被忽略的。
 
10.集合初始化时,指定集合初始值大小。
这一点在项目代码中也是经常被忽略的一点,未指定初始大小的集合,会有一个默认值。在容量元素不断增加,超过限度时,集合会进行resize的操作,这里涉及到对象的引用复制。在强调性能的应用中,需要考虑到这一点。
 
11.使用entrySet遍历Map类集合KV,而不是keySet方式进行遍历。
这里也是基于性能考虑。在遍历Map时我们通常会用到键和值,使用entrySet遍历的话再加上map.get("key"),会多一次遍历的过程。
 
12.线程池不要使用Executors去创建,而是通过ThreadPoolExecutor的方式
通过Executors创建的4个常见的线程池存在如下弊端:
FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
在多线程场景下尽量使用ThreadPoolExecutor创建线程池,根据实际业务场景和并发情况合理设置核心线程数、最大线程数、任务队列类型(阻塞或非阻塞)、队列长度等参数。可以边设置边调试,找到最适合业务运行的参数配置。
 
13.并发场景下使用锁时的考虑
锁是在多线程编程场景下基于线程安全对线程公共资源的访问控制手段,其功能实现是以性能开销为代价的。使用时遵循以下原则:
能用无锁方式实现,尽量用无锁的方式
锁的粒度尽量小(能锁区块,就不要锁方法;能锁对象,就不要锁类)
在写少读多的场景,推荐用乐观锁的方式(锁失败重试的方式,而非阻塞式的锁)。java的concurrent包中的锁实现就是基于CAS的乐观锁
 
14.volatile使用注意事项
volatile解决的是java多线程下的内存可见性问题,保证了变量的写操作对读操作的可见性(底层实现方式为编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序)。当操作是非原子操作时,单纯依靠对变量使用volatile就会有问题。
例如,在执行简单的count++的操作时,该操作是由变量加1和变量赋值两个步骤构成,只对变量volatile在多线程统计场景中会出现统计少于实际值的情况。这时应该尝试使用concurrent包中的AtomicInteger(基于CAS的乐观锁实现,该类存在的ABA问题结合实际使用场景决定是否需要考虑)
 
15.在高并发场景中,避免使用”等于”判断作为中断或退出的条件。
说明:如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替。
posted @ 2019-07-16 17:39  二十二花生  阅读(664)  评论(0编辑  收藏  举报