《阿里巴巴 Java 开发手册》学习笔记

说明:这是本人阅读手册后印象较为深刻的一些规范,可视为学习笔记;某些规范原封不动,某些规范会有补充说明,有助于理解规范;

一、编程规约

(一)命名规约

  1. 【强制】常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。
  2. 【强制】抽象类命名用Abstract或Base开头;异常类用Exception结尾;测试类用它要测试的类名作为前缀,Test结尾
  3. 【强制】中括号作为数组类型的一部分:String[] args;
  4. 【强制】POJO类中布尔类型的变量,都不要加is,否则部分框架解析会出现异常;反例:定义属性 Boolea isMan; 它的方法也是isMan(),RPC框架反向解析时会误以为它的属性是man,导致属性取不到,从而抛出异常;
  5. 杜绝完全不规范的命名,避免忘文不知义 ,随意缩写可能会严重降低代码的可阅读性
  6. 【参考】各层命名规约:

A) Service/DAO 层方法命名规约
1) 获取单个对象的方法用 get 做前缀。
2) 获取多个对象的方法用 list 做前缀。
3) 获取统计值的方法用 count 做前缀。
4) 插入的方法用 save(推荐)或 insert 做前缀。
5) 删除的方法用 remove(推荐)或 delete 做前缀。
6) 修改的方法用 update 做前缀。

B) 领域模型命名规约
1) 数据对象:xxxDO,xxx 即为数据表名。
2) 数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
3) 展示对象:xxxVO,xxx 一般为网页名称。
4) POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。

(二)常量定义

  1. 【强制】不允许出现任何魔法值(即未经定义的常量)直接出现在代码中。
    反例:String key = "goodsId#taobao_" + goodsId;
       map.put(key, value);
  2. 【推荐】如果变量值仅在一个范围内变化,且带有名称之外的延伸属性,定义为枚举类。下面
    正例中的数字就是延伸信息,表示星期几。
    正例:public Enum { MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6),
    SUNDAY(7);}

(三)格式规约

  1. 【推荐】方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义
    之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
    说明:没有必要插入多个空行进行隔开
  2. 【强制】单行字符数限制不超过 120 个,超出需要换行,换行时遵循如下原则:
    1)第二行相对第一行缩进 4 个空格,从第三行开始,不再继续缩进,参考示例。
    2)运算符与下文一起换行。
    3)方法调用的点符号与下文一起换行。
    4)方法调用中的多个参数需要换行时,在逗号后进行。
    5)在括号前不要换行,见反例。
    正例:
StringBuilder sb = new StringBuilder(); 
// 超过 120 个字符的情况下,换行缩进 4 个空格,点号和方法名称一起换行
sb.append("Jack").append("Ma")... 
	.append("alibaba")... 
	.append("alibaba")... 
	.append("alibaba");

反例:

StringBuilder sb = new StringBuilder(); 
// 超过 120 个字符的情况下,不要在括号前换行
sb.append("Jack").append("Ma")...append 
	("alibaba"); 
// 参数很多的方法调用可能超过 120 个字符,不要在逗号前换行
method(args1, args2, args3, ... 
	, argsX)

(四)OOP 规约

  1. 【强制】所有覆写的方法,必须要加上@Overried注解
    说明:getObject和get0bject的问题,一个是字母的O,一个是数字的0,加上该注解可以让编译器准确地判断是否覆盖成功;另外,在抽象类中对修改接口签名时,其实现类马上会编译报错。
  2. 【强制】Object的equals方法容易抛出空指针异常,应使用常量或者确定有值的对象来调用equals方法;
  3. 【强制】所有相同类型的包装类对象之间值的比较,全部使用equals比较
    说明: 对于Integer var = i在-128~127之间的赋值,Integer对象会在Integer.cache上产生,会复用已有对象,在这个区间内的对象都可以 == 用来比较值的大小;但是, 当表达式Integer i是这个区间外的数据时,i会通过新建integer对象来产生:new Integer(i),并不会复用已有对象,此时用 == 来判断会抛异常;所以规范推荐使用equals来判断;
  4. 关于包装数据类型和基础数据类型的使用标准如下:
    1)【强制】所有的POJO类属性必须使用包装数据类型
    2)【强制】RPC方法的返回值和参数必须使用包装数据类型
    3)【推荐】所有的局部变量必须使用基本数据类型
  5. 【强制】序列化类不要轻易修改serialVersionUID字段,避免反序列化失败。如果是完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
    说明:serialVersionUID是用来验证版本一致性的,所以在做兼容性升级的时候,不要修改序列化类中的serialVersionUID; 而在做非兼容性升级(版本升级),需要修改serialVersionUID,这样可以避免序列化混乱;
    为什么阿里巴巴要求程序员谨慎修改serialVersionUID 字段的值?

如果一个类实现了Serializable接口,一定要记得定义serialVersionUID,否则会发生异常。可以在IDE中通过设置,让他帮忙提示,并且可以一键快速生成一个serialVersionUID。

  1. 【强制】构造方法里禁止加入任何业务逻辑(确保构造方法逻辑清晰,确保代码复用性),如果有业务逻辑,请放在init方法中(然后构造函数调用init方法);

(五)集合处理

  1. [强制] 关于hashCode和equals的处理,遵循如下规约
  • 只要重写equals,就必须重写hashCode
    why?
  • 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法
  • 如果自定义对象作为Map的键,那么必须重写hashCode和equals方法
    说明: String对象已经重写了这两个方法,所以我们可以很愉快地使用String类型对象作为Map的键
  1. 【强制】ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException异常
  • ArrayList.subList方法返回的只是视图,是 ArrayList 的内部类 SubList
  • subList的所有操作都是在操作源列表ArrayList,所以增删改都会以影响源列表。
  • 对ArrayList进行添加、删除的操作之后,在操作subList会抛出异常
  • 仅仅subList操作并不会抛出异常
  1. 【强制】 在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、删除均产生 ConcurrentModificationException 异常。
  2. 【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全
    一样的数组,大小就是 list.size()。
String[] array = new String[list.size()]; 
array = list.toArray(array);

说明:如果直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它类型数组将出现ClassCastException 错误

  1. 【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
    原因:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList体现的是适配器模式,只是转换接口,后台的数据仍是数组
  2. 【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,做为接口调用赋值时易出错。
    说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:1)频繁往外读取内容的,适合用上界 Extends。2)经常往里插入的,适合用下界 Super。
    代码方式解读:https://blog.csdn.net/WANGSHUANGQUAN_A/article/details/112789960

简而言之

  • 返回数据类型是上界通配符<? extends T>时,泛型集合元素是T/T的子类,get方法返回数据类型定义为T就能接到,而add时,并不清楚泛型集合元素具体是什么类型;如果原来是A类对象,add时是B类对象,那肯定会报错。
  • 返回数据类型是下界通配符<? super T>时,泛型集合元素是T或者是T的父类,add方法入参只要为T/T的父类就可以,而调用get(),只知道返回的是T/T的父类,但具体类名却不得知,没法准确接收;
  1. 【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

说明:foreach用的是list的remove/add方法,而
Iterator用的删除则是iterator自己的remove/add方法;如果foreach里使用了list.remove,就会出现并发修改异常;
foreach等价于for(int i=0; i<size; i++)
如果在for循环里进行删除操作,那么size会发生变化,循环体里面的size应该是一个固定值而不能是随循环体变化而变化的值,这样是会发生修改异常的;

关于list集合的remove和Iterator的remove

  1. 【推荐】集合初始化时,指定集合初始值大小;
    降低集合自动扩容的概率和频率;集合自动扩容就是新建数组,然后原来集合的元素重新计算插入新数组中,比较消耗性能;

  2. 【推荐】高度注意 Map 类集合 K/V 能不能存储 null 值的情况,如下表格:在这里插入图片描述

Hashtable和HashMap Hashtable计算hash是直接使用key的hashcode对table数组的长度直接进行取模:
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;

HashMap计算hash对key的hashcode进行了二次hash,以获得更好的散列值,然后对table数组长度取模:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

ConcurrentHashMap是使用了锁分段技术来保证线程安全
锁分段技术:首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。(hashtable则是锁住整个table,效率极低)

(六)并发处理

  1. 【强制】不允许直接使用Executors去创建线程池,而是使用ThreadPoolExecutor创建,使用前者创建有以下弊端:
    • FixedThreadPool和SingleThreadPool:允许请求队列的最大长度为Integer.MAX_VALUE,可能会堆积大量请求,造成OOM;
    • CachedThreadPool:允许创建线程的最大数量为Integer.MAX_VALUE,可能会创建大量线程,造成OOM;
  2. 【强制】必须调用threadLocalName.remove()回收自定义的ThreadLocal对象,避免造成内存泄漏;
  3. 使用锁时要考虑锁的开销,能使用无锁数据结构就不要使用锁,能使用对象锁就不要使用类锁,能锁代码块就不要锁整个方法,尽量使

(七)控制语句

  1. 【推荐】除常用方法(如 getXxx/isXxx)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性
    说明:很多 if 语句内的逻辑相当复杂,阅读者需要分析条件表达式的最终结果,才能明确什么样的条件执行什么样的语句,那么,如果阅读者分析逻辑表达式错误呢?
正例:
//伪代码如下
boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
 ...
} 
反例:
if ((file.open(fileName, "w") != null) && (...) || (...)) {
 ...
}
  1. 【参考】下列情形,需要进行参数校验:
    1) 调用频次低的方法。
    2) 执行时间开销很大的方法。此情形中,参数校验时间几乎可以忽略不计,但如果因为参数错误导致中间执行回退,或者错误,那得不偿失。
    3) 需要极高稳定性和可用性的方法。
    4) 对外提供的开放接口,不管是 RPC/API/HTTP 接口。
    5) 敏感权限入口。
  2. 【参考】下列情形,不需要进行参数校验:
    1) 极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。
    2) 底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。一般 DAO 层与 Service 层都在同一个应用中,部署在同一台服务器中,所以 DAO 的参数校验,可以省略。
    3) 被声明成 private 只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数。

(八)注释规约

  1. 【强制】类、类属性、类方法的注释必须使用 Javadoc 规范,使用/*内容/格式,不得使用//xxx 方式
  2. 【强制】所有的抽象方法(包括接口中的方法)必须要用 Javadoc 注释、除了返回值、参数、异常说明外,还必须指出该方法做什么事情,实现什么功能。
  3. 【参考】对于注释的要求:第一、能够准确反应设计思想和代码逻辑;第二、能够描述业务含义,使别的程序员能够迅速了解到代码背后的信息。完全没有注释的大段代码对于阅读者形同天书,注释是给自己看的,即使隔很长时间,也能清晰理解当时的思路;注释也是给继任者看的,使其能够快速接替自己的工作。

(九)其他

  1. 【推荐】任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值