《 阿里巴巴 Java开发手册》读后感

原创: 公众号 Java3y 《阿里巴巴 Java开发手册》读后感

PDF官方地址:

一、Java相关

  1. POJO是DO/DTO/BO/VO的统称,禁止命名为xxxPOJO

  2. 获取多个对象的方法中list作为前缀

  3. 获取统计值的方法用count作为前缀

  4. POJO类中的布尔类型(Boolean)的变量都不要加is前缀,否则部分框架解析会引起序列化错误

    • 如果你的变量名带is的话,比如isActive,框架解析的时候可能就当成active了。
  5. 如果是形容能力的接口名称,取对应的形容词为接口名(通常是-able的形式)

  6. 不允许任何魔法值(未经预先定义的常量)直接出现在代码中

  7. Object的euqals方法容易抛出空指针异常,应使用常量或者有值的对象来调用equals。推荐使用java.util.Object#equals工具类

  8. 所有POJO类的属性全部使用包装数据类型,RPC的返回值和参数必须使用包装数据类型,所有的局部变量都使用基本数据类型。定义VO/DTO/DO等POJO类时,不要设定任何属性的默认值

    • 如果你的类属性使用int这样的基本数据类型,默认值是0。一般情况下该变量没有赋值,一般想表达的是不存在(null),而不是0。
  9. 构造方法禁止加入任何的业务逻辑,如果初始化逻辑可以放在init方法中。set/get方法也不要增加业务逻辑。

    • 如果set/get方法放入业务逻辑,有时候排查问题就变得很麻烦了
  10. 工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。比如说add、clear、remove

  11. 在JDK7以及以上版本中,Comparator要满足三个条件,不然调用Arrays.sort()或者Collections.sort()会报异常。

    • x,y 的比较结果和 y,x 的比较结果相反
    • 传递性:x>y并且y>z,那么x一定大于z
    • 对称性:x=y,则 x,z 比较结果和y,z比较结果相同
  12. 使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历

    • keySet遍历了两次,一次是转成Iterator对象,一次是从hashMap中取出key所对应的value,如果JDK8可以使用Map.foreach方法
  13. 线程资源必须由线程池提供,不允许在应用中自行显示创建线程。线程池不允许用Executors创建,通过ThreadPoolExecutor的方式创建,这样的处理方式能够让编写代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。

  14. SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类

    • 如果是JDK8应用,可以使用Instant(针对时间统计等场景)代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat
  15. 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed导致性能下降

    • 在JDK7之后,可以直接使用API ThreadLocalRandom,而在JDK7 之前,需要编码保证每个线程持有一个实例。
  16. 类、类属性、类方法的注释必须使用 Javadoc 规范,使用 /**内容*/ 格式,不得使用 //xxx 方式

  17. 所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释,除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。所有的类都必须添加创建者和创建日期

  18. 对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码的上方,使用三个斜杠///来说明注释代码的理由

  19. 保证单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试之间不能互相调用,也不能依赖执行的先后顺序

  20. 高并发服务器建议调小TCP协议的time_await超时时间,调大最大事件句柄数(fd),

1.1值得说明的点

一、不允许任何魔法值(未经预先定义的常量)直接出现在代码中

例子:

1
2
3
4
5
6
7
8
9
10
11
Negative example:
//Magic values, except for predefined, are forbidden in coding.
if (key.equals("关注公众号:Java3y")) {
//...
}

Positive example:
String KEY_PRE = "关注公众号:Java3y";
if (KEY_PRE.equals(key)) {
//...
}

ps:我猜是把先常量定义出来,后续引用/修改的时候就很方便了。

二、Object的euqals方法容易抛出空指针异常,应使用常量或者有值的对象来调用equals。推荐使用java.util.Object#equals工具类

java.util.Object#equals的源码(已经判断null的情况了)

1
2
3
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}

三、工具类Arrays.asList()把数组转成List时,不能使用其修改集合的相关方法。

因为返回的ArrayList是一个内部类,并没有实现集合的修改方法。后台的数据仍是数组,这里体现的是适配器模式。

ArrayList在这里是内部类

四、在JDK7以及以上版本中,Comparator要满足自反性,传递性,对称性,不然调用Arrays.sort()或者Collections.sort()会报异常。

The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y. (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an exception.)

The implementor must also ensure that the relation is transitive: ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0.

Finally, the implementor must ensure that compare(x, y)==0 implies that sgn(compare(x, z))==sgn(compare(y, z)) for all z.

  • 1) x,y 的比较结果和 y,x 的比较结果相反。
  • 2) 传递性:x>y,y>z,则 x>z。
  • 3) 对称性:x=y,则 x,z 比较结果和 y,z 比较结果相同。

反例:下例中没有处理相等的情况,实际使用中可能会出现异常:

1
2
3
4
5
6
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
}
}

使用entrySet遍历Map类集合K/V,而不是用keySet方式遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) throws InterruptedException {

HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("关注公众号:", "Java3y");
hashMap.put("坚持原创", "Java3y");
hashMap.put("点赞", "关注,转发,分享");


// 得到keySet,遍历keySet得到所有的key
Set<String> strings = hashMap.keySet();
Iterator<String> iterator = strings.iterator();
while (iterator.hasNext()) {

// HashMap的每个key
String key = iterator.next();

// 通过key可以获得对应的value,如果有看过HashMap的同学知道get方法的时间复杂度是O(1)
System.out.println("key = " + key + ", value = " + hashMap.get(key));
}

}

再来看一下源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 1. 得到keySet,如果不存在,则创建
public Set<K> keySet() {
Set<K> ks = keySet;
if (ks == null) {
ks = new KeySet();
keySet = ks;
}
return ks;
}

// 2.初始化ks (实际上就是Set集合[HashMap的内部类],在初始化时需要顺便初始化iterator)
ks = new AbstractSet<K>() {
public Iterator<K> iterator() {
return new Iterator<K>() {
private Iterator<Entry<K,V>> i = entrySet().iterator();

public boolean hasNext() {
return i.hasNext();
}

public K next() {
return i.next().getKey();
}

public void remove() {
i.remove();
}
};
}

};

再来看一下entrySet,可以直接拿到key和value,不用再使用get方法来得到value,所以比keySet更加推荐使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) throws InterruptedException {

HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("关注公众号:", "Java3y");
hashMap.put("坚持原创", "Java3y");
hashMap.put("点赞", "关注,转发,分享");


// 得到entrySet,遍历entrySet得到结果
Set<Map.Entry<String, String>> entrySet = hashMap.entrySet();
Iterator<Map.Entry<String, String>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> entry = iterator.next();
System.out.println("key = " + entry.getKey() + ", value = " + entry.getValue());
}
}

如果是JDK8的话,推荐直接使用Map.forEach()就好了,我们也来看看用法:

1
2
3
4
5
6
7
8
9
10
11
public static void main(String[] args) throws InterruptedException {

HashMap<String, String> hashMap = new HashMap<>();
hashMap.put("关注公众号:", "Java3y");
hashMap.put("坚持原创", "Java3y");
hashMap.put("点赞", "关注,转发,分享");


// forEach用法
hashMap.forEach((key, value) -> System.out.println("key = " + key + ", value = " + value));
}

其实在源码里边我们可以发现,forEach实际上就是封装了entrySet,提供forEach给我们可以更加方便地遍历Map集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// forEach源码
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}

五、SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。

有以下的例子可以正确使用SimpleDateFormat:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 1. 在方法内部使用,没有线程安全问题
private static final String FORMAT = "yyyy-MM-dd HH:mm:ss";
public String getFormat(Date date){
SimpleDateFormat dateFormat = new SimpleDateFormat(FORMAT);
return dateFormat.format(date);
}


// 2. 每次使用的时候加锁
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public void getFormat(){
synchronized (SIMPLE_DATE_FORMAT){
SIMPLE_DATE_FORMAT.format(new Date());
….;
}

// 3. 使用ThreadLocal,每个线程都有自己的SimpleDateFormat对象,互不干扰
private static final ThreadLocal<DateFormat> DATE_FORMATTER = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};

// 4. 使用DateTimeFormatter(This class is immutable and thread-safe.)

DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
System.out.println(timeFormatter.format(LocalDateTime.now()));
打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2015-2023 高行行
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信