阿里代码规范阅读笔记

本文主要介绍了阿里Java编程规约中的一些关键点,包括类名与异常命名规范、布尔变量命名、枚举命名、equals与hashCode的使用、循环中的字符串拼接、线程池的创建与使用、volatile关键字的作用以及switch语句对String的支持等,旨在提升代码质量和并发安全性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

编程规约

6。【强制】抽象类名用Abstract 或 Base 开头;异常类名用Exception结尾;测试类以要测试的类名 + Test后缀
规范和集合源码的规范一致,如ArrayList就继承了AbstractList,LinkedList继承了AbstractSequentialList,以及AbstractSet,AbstractMap等等
实习时测试代码全写到一个类(逃
8。 【强制】POJO类中布尔类型变量都不要加is前缀,否则部分框架解析时会引起序列化错误
boolean isSuccess; 用IDEA自动生成getter方法时,方法是 public boolean isSuccess() {...} ,然而boolean success;生成的也是这个;
所以一些框架根据getter方法名反向解析会解析到 success这个域名
为嘛不生成isIsSuccess? 是因为奇怪吗

13。 在常量与变量命名时,表示类型的名词放在词尾
eg. ArrayDeque, PriorityQueue, XXXCOUNT

14。如果在类中用到了设计模式,要体现在命名上
eg. Factory, AbstractFactory,Singleton,Builder,Prototype,Adapter,Bridge,Filter,Composite,Decorator,Facade,Flyweight,Proxy,Chain of Responsibility(???),Command,Interpreter,Iterator,Mediator,Memento,State,Observer,Strategy,Template,Visitor

17。枚举类名带上Enum后缀,枚举成员名称需要全大写,单词间用下划线隔开。
枚举是特殊的类,成员变量全为类常量,而且构造方法强制为私有
eg.

public enum BooleanEnum {
    TRUE,FALSE;
}

会被编译成

public static final BooleanEnum TRUE;
    descriptor: LBooleanEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final BooleanEnum FALSE;
    descriptor: LBooleanEnum;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

6。 equals 容易报空指针异常,应使用常量或确定的值来调用equals。 eg. "test.equals(s)";
7。 Integer之间的比较,全部用equals方法

Integer a = 1, b = 1;
System.out.println(a == b);

上面两行代码编译后的字节码是:

 0: iconst_1
 1: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
 4: astore_1
 5: iconst_1
 6: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
 9: astore_2
10: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: aload_2
15: if_acmpne     22
18: iconst_1
19: goto          23
22: iconst_0
23: invokevirtual #4                  // Method java/io/PrintStream.println:(Z)V
26: return

15行:if_acmpne 语义是比较栈顶两引用的数值(即地址)是否相等,不等则跳转
故 == 对于Integer来说是比较引用而不是值

8。float, double(浮点数)之间的等值判断不能用 ==,包装类不能用equals来判断
大部分小数存在精度差
可以用BigDecimal计算 或 定义最小精度差

21。循环体内,字符串连接使用StringBuilder

String str = "start";
str += "hello";
// 编译后
 0: ldc           #2                  // String start
 2: astore_1
 3: new           #3                  // class java/lang/StringBuilder
 6: dup
 7: invokespecial #4                  // Method java/lang/StringBuilder."<init>":()V
10: aload_1
11: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
14: ldc           #6                  // String hello
16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
22: astore_1
23: return

原来字符串拼接是通过StringBuilder实现 > <

23。慎用Object的clone方法拷贝对象。 因为默认实现是浅拷贝,clone对象内成员引用和原对象成员引用相等(引用了同一个堆中的对象)。

1。【强制】关于hashCode和equals方法,遵循如下规则:

  • 只要覆写equals,就必须覆写hashCode
    原因是在 Set 或 Map 中,hashCode 会和 equals 配合使用来判重,比如HashMap的add方法
    if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p;
    这就要求如果 equals 返回 true,则hashCode返回值必须相等。

11。不要在foreach循环里进行元素的remove, add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁 不明白…

16。高度注意Map类集合K/V能不能存储null值的情况
Hashtable not null not null
ConcurrentHashMap not null not null
TreeMap not null null
HashMap null null

TreeMap key 不可以为null可以理解为需要排序
ConcurrentHashMap k/v禁止为null,主要是因为不确定性,比如get() 返回为null时,无法判断value是null 还是原本就不存在这个映射. 对于非并发的HashMap 来说可以通过containsKey判断,并发的ConcurrentHashMap,调用containsKey后,数据可能已经发生变化,这种不确定性没有解决

3。【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
使用线程池可以避免不必要的创建,销毁线程的开销。线程数量过多会导致过度切换,消耗内存

4。【强制】线程不能用Executors创建,而是要通过ThreadPoolExecutor创建,这样更好设定线程池参数
FixedThreadPool, SingleThreadExecutor 的workQueue为 LinkedBlockingQueue,capacity 默认为 Integer.MAX_VALUE,可能会堆积大量请求,导致OOM
CachedThreadPool 的maximumPoolSize为Integer.MAX_VALUE,可能会创建大量线程,导致OOM

9。【强制】在使用阻塞等待获取锁(Lock)的方式中,Lock.lock()必须在try代码块之外,并且在加锁与try代码块之间不能存在任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。

这条设定避免了很多可能出现的问题, unlock()操作会检查 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException();。故unlock操作之前必须保证当前线程成功执行lock操作。故lock() 要放在try代码块之前,这样try包住的内容就可以当成一个临界区,lock是进入点,即使lock抛出异常也不会进入临界区,也不会执行unlock操作。
locktry代码块之间不允许任何可能抛出异常的方法调用,这点原因很明显,如果出现异常,就不会释放锁。

这样分析起来lock放在try第一句也行,但是代码规范中为什么强制放在try外部呢? 查看Lock接口的一些说明:

     * <p>A {@code Lock} implementation may be able to detect erroneous use
     * of the lock, such as an invocation that would cause deadlock, and
     * may throw an (unchecked) exception in such circumstances.  The
     * circumstances and the exception type must be documented by that
     * {@code Lock} implementation.

某些Lock的实现上,lock操作可能会进行死锁监测,进而抛出unchecked异常

17。 volatile 解决多线程内存不可见的问题,对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。???

2。【强制】当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断
反例: 猜猜下面代码输出什么, 先说结论 java.lang.NullPointerException

public static void main(String[] args) {
        new Demo().method(null);
    }

    private void method(String s) {
        switch (s) {
            case "sth":
                System.out.println("is sth");
                break;
            case "null":
                System.out.println("is null");
                break;
            default:
                System.out.println("default");
        }
    }

String能用于switch是借助int类型实现,如下:

4: aload_2
5: invokevirtual #5                  // Method java/lang/String.hashCode:()I
8: lookupswitch  { // 2
         114215: 36
        3392903: 50
        default: 61
   }
36: aload_2
37: ldc           #6                  // String sth
39: invokevirtual #7                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
42: ifeq          61
45: iconst_0
46: istore_3
47: goto          61
50: aload_2
51: ldc           #8                  // String null
53: invokevirtual #7                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
56: ifeq          61
59: iconst_1
60: istore_3
61: iload_3
62: lookupswitch  { // 2
              0: 88
              1: 99
        default: 110
   }

首先比较的是hashCode值,之后再通过equals二次判断(hashCode提高速度,equals保证准确), 这也是为嘛要求重写equals()也要同时重些hashCode方法的原因.

猜测如果多个字符串hashCode相同,编译后的字节码是这种形式
lookuoswitch {
12345: 36, 37, 38

}
然后 if else if else if else if
为了验证,编译下这个代码

switch (s) {
    case "Aa":
        System.out.println("is Aa");
        break;
    case "BB":
        System.out.println("is BB");
        break;
    default:
        System.out.println("default");
}
---------------------
8: lookupswitch  { // 1
            2112: 28
         default: 53
    }
28: aload_2
29: ldc           #4                  // String BB
31: invokevirtual #7                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
34: ifeq          42
37: iconst_1
38: istore_3
39: goto          53
42: aload_2
43: ldc           #8                  // String Aa
45: invokevirtual #7                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z
48: ifeq          53

猜错啦:),原来这里也是做了去重的优化

3。【强制】if/else/for/while/do 语句中必须使用大括号
刷LeetCode,如果if代码块只有一行,或者卫语句,还是喜欢不加大括号

7。不要在其他表达式(尤其是条件表达式)中,插入复制语句。影响代码理解
感同身受,点名批评写HashMap源码的大师(逃,可能优化到极致就是反人类

第二遍阅读此书(v1.5.0华山版), 其他的突然就看不懂了…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值