无规矩不成方圆,同样在编码时也要遵循一定的规范,因为就会成为面试过程中的必问点,而大多数就以阿里的规范为主,近期看了文档做一个记录,相当于一个建议的阿里规范;
- 官网地址:https://developer.aliyun.com/topic/java2020?spm=a2c6h.12873639.0.0.51a270feIboA1i
关于 hashCode 和 equals 的处理,遵循如下规则:
判断所有集合内部的元素是否为空,使用 isEmpty() 方法,而不是 size() == 0 的方式;
在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要使用参数类型
为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key 时会抛出
IllegalStateException异常;
//正例
Map<String, Double> map = pairArrayList.stream()
.collect(Collectors.toMap(Pair::getKey, Pair::getValue, (v1, v2) -> v2));
//反例 抛出 IllegalStateException 异常
Map<Integer, String> map = Arrays.stream(departments)
.collect(Collectors.toMap(String::hashCode, str -> str));
在使用 java.util.stream.Collectors 类的 toMap() 方法转为 Map 集合时,一定要注意当 value
为 null 时会抛NPE异常;
ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常:
java.util.RandomAccessSubList cannot be cast to java.util.ArrayList;
使用 Map 的方法 keySet() / values() / entrySet() 返回集合对象时,不可以对其进行添加元素
操作,否则会抛出 UnsupportedOperationException 异常;
Collections 类返回的对象,如:emptyList() / singletonList() 等都是 immutable list,不可
对其进行添加或者删除元素的操作;
在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删
除产生 ConcurrentModificationException 异常;
使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为
0 的空数组;
使用 Collection 接口任何实现类的 addAll() 方法时,要对输入的集合参数进行 NPE 判断;
使用工具类 Arrays.asList() 把数组转换成集合时,不能使用其修改集合相关的方法,它的 add
/ remove / clear 方法会抛出 UnsupportedOperationException 异常;
泛型通配符 extends T>
来接收返回的数据,此写法的泛型集合不能使用 add 方法,而 super T>
不能使用 get 方法,两者在接口调用赋值的场景中容易出错;
extends T>
>,经常往里插入的,适合用 super T>
;在无泛型限制定义的集合赋值给泛型限制的集合时,在使用集合元素时,需要进行
instanceof 判断,避免抛出 ClassCastException 异常;
不要在 foreach 循环里进行元素的 remove / add 操作。remove 元素请使用 iterator 方式,
如果并发操作,需要对 iterator 对象加锁;
在 JDK7 版本及以上,Comparator 实现类要满足如下三个条件,不然 Arrays.sort,
Collections.sort 会抛 IllegalArgumentException异常;
泛型集合使用时,在 JDK7 及以上,使用 diamond 语法或全省略;
// diamond 方式,即<>
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);
集合初始化时,指定集合初始值大小;
使用 entrySet 遍历 Map 类集合 KV,而不是 keySet 方式进行遍历;
高度注意 Map 类集合 K / V 能不能存储 null 值的情况:
集合类 | key | value | super | 说明 |
---|---|---|---|---|
Hashtable | 不允许为 null | 不允许为 null | Dictionary | 线程安全 |
TreeMap | 不允许为 null | 允许为 null | AbstractMap | 线程不安全 |
ConcurrentHashMap | 不允许为 null | 不允许为 null | AbstractMap | 锁分段技术(JDK8:CAS) |
HashMap | 允许为 null | 允许为 null | AbstractMap | 线程不安全 |
合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定
性(unorder)带来的负面影响;
利用 Set 元素唯一的特性,可以快速对一个集合进行去重操作,避免使用 List 的
contains() 进行遍历去重或者判断包含操作;
获取单例对象需要保证线程安全,其中的方法也要保证线程安全;
创建线程或线程池时请指定有意义的线程名称,方便出错时回溯;
线程资源必须通过线程池提供,不允许在应用中自行显式创建线程;
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方
式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险;
SimpleDateFormat 是线程不安全的类,一般不要定义为 static 变量,如果定义为 static,必须
加锁,或者使用 DateUtils 工具类;
必须回收自定义的 ThreadLocal 变量,尤其在线程池场景下,线程经常会被复用,如果不清理
自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用try-finally 块进行回收;
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}
高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁;
对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁;
在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没
有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁;
在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。
锁的释放规则与锁的阻塞等待方式相同;
并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么
在数据库层使用乐观锁,使用 version 作为更新依据;
多线程并行处理定时任务时,Timer 运行多个 TimeTask 时,只要其中之一没有捕获抛出的异
常,其它任务便会自动终止运行,使用 ScheduledExecutorService 则没有这个问题;
资金相关的金融敏感信息,使用悲观锁策略;
使用 CountDownLatch 进行异步转同步操作,每个线程退出前必须调用 countDown 方法,线
程执行代码注意 catch 异常,确保 countDown 方法被执行到,避免主线程无法执行至 await 方法,直到超时才返回结果;
避免 Random 实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一 seed 导致
的性能下降;
volatile 解决多线程内存不可见问题对于一写多读,是可以解决变量同步问题,但是如果多
写,同样无法解决线程安全问题;
HashMap 在容量不够进行 resize 时由于高并发可能出现死链,导致 CPU 飙升,在开发过程
中注意规避此风险;
ThreadLocal 对象使用 static 修饰,ThreadLocal 无法解决共享对象的更新问题;
在一个 switch 块内,每个 case 要么通过 continue / break / return 等来终止,要么注释说明
程序将继续执行到哪一个 case 为止;在一个 switch 块内,都必须包含一个 default 语句并且放在最后,即使它什么代码也没有;
当 switch 括号内的变量类型为 String 并且此变量为外部参数时,必须先进行 null 判断;
在 if / else / for / while / do 语句中必须使用大括号;
三目运算符 condition ? 表达式 1:表达式 2 中,高度注意表达式 1 和 2 在类型对齐时,可能
抛出因自动拆箱导致的 NPE 异常;
Integer a = 1;
Integer b = 2;
Integer c = null;
Boolean flag = false;
// a*b 的结果是 int 类型,那么 c 会强制拆箱成 int 类型,抛出 NPE 异常
Integer result = (flag ? a * b : c);
在高并发场景中,避免使用“等于”判断作为中断或退出的条件;
当方法的代码总行数超过 10 行时,return / throw 等中断逻辑的右大括号后需要加一个空行;
表达异常的分支时,少用 if-else 方式,应该及时return终止;
除常用方法(如 getXxx / isXxx)等外不要在条件判断中执行其它复杂的语句,将复杂逻辑判
断的结果赋值给一个有意义的布尔变量名,以提高可读性;
不要在其它表达式(尤其是条件表达式)中,插入赋值语句;
循环体中的语句要考量性能,以下操作尽量移至循环体外处理;
避免采用取反逻辑运算符;
公开接口需要进行入参保护,尤其是批量操作的接口;
前后端数据列表相关的接口返回,如果为空,则返回空数组[]或空集合{};
服务端发生错误时,返回给前端的响应信息必须包含 HTTP 状态码,errorCode、
errorMessage、用户提示信息四个部分;
在前后端交互的 JSON 格式数据中,所有的 key 必须为小写字母开始的 lowerCamelCase
风格,符合英文表达习惯,且表意完整;
errorMessage 是前后端错误追踪机制的体现,可以在前端输出到 type=“hidden” 文字类控
件中,或者用户端的日志中,帮助我们快速地定位出问题;
对于需要使用超大整数的场景,服务端一律使用 String 字符串类型返回,禁止使用 Long 类型;
HTTP 请求通过 URL 传递参数时,不能超过 2048 字节;
HTTP 请求通过 body 传递内容时,必须控制长度,超出最大长度后,后端解析会出错;
在翻页场景中,用户输入参数的小于 1,则前端返回第一页参数给后端;后端发现用户输入的
参数大于总页数,直接返回最后一页;
服务器内部重定向必须使用 forward;外部重定向地址必须使用 URL 统一代理模块生成,否
则会因线上采用 HTTPS 协议而导致浏览器提示“不安全”,并且还会带来 URL 维护不一致的问题;
服务端返回的数据,使用 JSON 格式而非 XML;
前后端的时间格式统一为"yyyy-MM-dd HH:mm:ss",统一为 GMT;
在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度;
/**
* 首位非0的正整数
*/
private static Pattern IS_INTEGER = Pattern.compile("^[1-9]([0-9]*)$|^[0-9]$");
public Boolean check() {
return IS_INTEGER.matcher("dsf").matches();
}
避免用 ApacheBeanutils 进行属性的 copy;
velocity 调用 POJO 类的属性时,直接使用属性名取值即可,模板引擎会自动按规范调用 POJO
的 getXxx(),如果是 boolean 基本数据类型变量(boolean 命名不需要加 is 前缀),会自动调 isXxx()方法;
后台输送给页面的变量必须加 $!{var} ——中间的感叹号;
注意 Math.random() 这个方法返回是 double 类型,注意取值的范围 0 ≤ x < 1(能够
取到零值,注意除零异常),如果想获取整数类型的随机数,不要将 x 放大 10 的若干倍然后取
整,直接使用 Random 对象的 nextInt 或者 nextLong 方法;
枚举 enum(括号内)的属性字段必须是私有且不可变;
任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存;
及时清理不再使用的代码段或配置信息;
应用中不可直接使用日志系统(Log4j、Logback)中的 API,可使用SLF4J;
日志文件至少保存 15 天,因为有些异常具备以“周”为频次发生的特点;
根据国家法律,网络运行状态、网络安全事件、个人敏感信息操作等相关记录,留存的日志不少于六个月,并且进行网络多机备份;
在日志输出时,字符串变量之间的拼接使用占位符的方式;
String的拼接用的是StringBuilder的append的方式有性能损耗;
logger.debug("Processing trade with id : {} and symbol : {}", id, symbol);
对于 trace / debug / info 级别的日志输出,必须进行日志级别的开关判断;
生产环境禁止使用 System.out 或 System.err 输出或使用 e.printStackTrace() 打印异常堆栈;
异常信息应该包括两类信息:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字throws 往上抛出;
日志打印时禁止直接用 JSON 工具将对象转换成 String;
尽量用英文来描述日志错误信息,如果日志中的错误信息用英文描述不清楚的话使用中文描述即可,否则容易产生歧义;
为了保护用户隐私,日志文件中的用户敏感信息需要进行脱敏处理;
pk_字段名
;唯一索引名为 uk_字段名
;普通索引名则为 idx_
字段名;where a = ? and b = ? order by c;索引:a_b_c
SELECT t1.* FROM 表 1 as t1 , (select id from 表 1 where 条件 LIMIT 100000 , 20) as t2 where t1.id = t2.id
SELECT IFNULL(SUM(column) , 0) FROM table
开放API层:可直接封装Service接口暴露成RPC接口;通过Web封装成http接口;网关控制层;
终端显示层:各个端的模板渲染并执行显示的层。当前主要是 velocity 渲染,JS 渲染,JSP 渲染,移动端展示;
Web层:主要是对访问控制进行转发,各类基本参数校验,或者不复用的业务简单处理;
Service层:相对具体的业务逻辑服务层;
Manager层:通用业务处理层,
对第三方平台封装的层,预处理返回结果及转化异常信息,适配上层接口;
对 Service 层通用能力的下沉,如缓存方案、中间件通用处理;
与 DAO 层交互,对多个 DAO 的组合复用;
第三方服务:包括其它部门 RPC 服务接口,基础平台,其它公司的 HTTP 接口,如淘宝开放平台、支付宝付款服务、高德地图服务;
外部数据接口:外部(应用)数据存储服务提供的接口,多见于数据迁移场景中;
DO(Data Object):此对象与数据库表结构一一对应,通过 DAO 层向上传输数据源对象;
DTO(Data Transfer Object):数据传输对象,Service 或 Manager 向外传输的对象;
BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象;
Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输;
VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象;
POJO(Plain Ordinary Java Object):在本规约中,POJO 专指只有 setter / getter / toString 的简单类,包括DO / DTO / BO / VO 等;
DO(Data Object):阿里巴巴专指数据库表一 一对应的 POJO 类。此对象与数据库表结构一 一对应,通过 DAO 层向上传输数据源对象;
PO(Persistent Object):也指数据库表一 一对应的 POJO 类。此对象与数据库表结构一 一对应,通过 DAO 层向上传输数据源对象;
DTO(Data Transfer Object ):数据传输对象,Service 或 Manager 向外传输的对象;
BO(Business Object):业务对象,可以由 Service 层输出的封装业务逻辑的对象;
Query:数据查询对象,各层接收上层的查询请求。注意超过 2 个参数的查询封装,禁止使用 Map 类来传输;
VO(View Object):显示层对象,通常是 Web 向模板渲染引擎层传输的对象;
CAS(Compare And Swap):解决多线程并行情况下使用锁造成性能损耗的一种机制,这是硬件实现的原子操作。CAS 操作包含三个操作数:内存位置、预期原值和新值。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作;
GAV(GroupId、ArtifactId、Version):Maven 坐标,是用来唯一标识 jar 包;
OOP(Object Oriented Programming):本文泛指类、对象的编程处理方式;
AQS(AbstractQueuedSynchronizer):利用先进先出队列实现的底层同步工具类,它是很多上层同步实现类的基础,比如:ReentrantLock、CountDownLatch、Semaphore 等,它们通过继承 AQS 实现其模版方法,然后将 AQS子类作为同步组件的内部类,通常命名为 Sync;
ORM(Object Relation Mapping):对象关系映射,对象领域模型与底层数据之间的转换,本文泛指 iBATIS,mybatis 等框架;
NPE(java.lang.NullPointerException):空指针异常;
OOM(Out Of Memory):源于 java.lang.OutOfMemoryError,当 JVM 没有足够的内存来为对象分配空间并且垃圾回收器也无法回收空间时,系统出现的严重状况;
GMT(Greenwich Mean Time):指位于英国伦敦郊区的皇家格林尼治天文台的标准时间,因为本初子午线被定义在通过那里的经线。地球每天的自转是有些不规则的,而且正在缓慢减速,现在的标准时间是协调世界时(UTC),它由原子钟提供;
一方库:本工程内部子项目模块依赖的库(jar 包);
二方库:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar 包);
三方库:公司之外的开源库(jar 包);
这么多我们也不可能一时间都记住,因此就有了阿里规范的插件,可以安装在我们的编译器上实时的监测;
这样就可以实时监测我们写的代码了