这里只记录一些个人认为需要注意或经常忽略的点,并对部分点进行重点分析。
alibaba
);MAX _ STOCK _ COUNT
而不能写成MAX _ COUNT
;Abstract
或Base
开头;异常类命名使用Exception
结尾;测试类 Test
结尾;is
前缀 ,否则部分框架解析会引起序列化错误,比如定义基本数据类型Boolean isDeleted
的属性,那么该对象自动生成该属性的对应getter
方法就是isDeleted()
,RPC(Remote Procedure Call Protocol远程过程调用协议)框架框架在反向解析的时候,误以为对应的isDeleted
属性名称是deleted
,导致属性获取不到,进而抛出异常;com.alibaba.ai.util
、类名为MessageUtils
;int a
这样的形式要避免出现;public class OrderFactory
、public class LoginProxy
、public class ResourceObserver
等;public
也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量;Enum
后缀,枚举成员名称需要全大写,单词间用下划线隔开;get
做前缀;list
做前缀,复数形式结尾如:listObjects
;count
做前缀;save/insert
做前缀;remove/delete
做前缀;update
做前缀;xxxDO
,xxx
即为数据表名;xxxDTO
,xxx
为业务领域相关的名称;xxxVO
,xxx
一般为网页名称;DO/DTO/BO/VO
的统称,禁止命名成xxxPOJO
;long
或Long
赋值时,数值后面使用L
进行标识,不要使用l
标识,小写容易跟数字1混淆,造成误解;CacheConsts
下,系统配置相关常量放在类ConfigConsts
下;main(String[] args) {
if
/for
/while
/switch
/do
等保留字与括号之间都必须加空格,比如if (1 == 1)
;=
、逻辑运算符&&
、加减乘除符号等)的左右两边都需要加一个空格,比如if (1 == 1)
;(
前换行)不要换行;@Override
注解(我经常性的不写(●ˇ∀ˇ●));Java
的可变参数(即f(int... a)
这样的形式,这个方法可以传入多个int
类型的参数,不是固定死的,只能传几个参数,注意和数组的区别,这两货无法重载,奇葩啊,然后重载方法时,固定参数的方法比可变参数的优先等级高),避免使用Object
,同时提倡不使用可变参数变成;@Deprecated
注解,并清晰地说明采用的新接口或者新服务是什么,(因为接口可能被其他人调用了,所以能想改就改,只能丢弃,不能修改!);Object
的equals
方法容易抛空指针异,应使用常量或确定有值的对象来调用equals
,推荐使用Objects.equals(str1, str2)
,这样可以避免空指针异常;equals
方法比较,对于Integer var = ?
在-128至127范围内的赋值,Integer
对象是在 IntegerCache.cache
产生,会复用已有对象,这个区间内的Integer
值可以直接使用==
进行 equals
方法进行判断。长见识了U•ェ•*U,具体测试戳;基本数据类型和包装类的使用标准:
null
,因为自动拆箱,用基本数据类型接收有NPE风险,即空指针异常,下同);定义DO
/DTO
/VO
等POJO类时,不要设定任何属性默认值,因为属性在数据提取时可能并没有置入具体值,在更新其它字段时又附带更新了此字段;
serialVersionUID
字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么修改serialVersionUID
值,serialVersionUID
不一致会抛出序列化运行时异常;init
方法中;toString
方法,如果继承了另一个POJO
类,注意在前面加一下super.toString
,主要是为了便于排查问题;xxx
的isXxx()
和getXxx()
方法;getter
/setter
方法;Object
的clone
方法来拷贝对象,对象的clone
方法默认是浅拷贝,若想实现深拷贝需要重写 clone
方法实现域对象的深度遍历式拷贝;new
来创建对象,那么构造方法必须是private
;public
或default
构造方法;static
成员变量并且与子类共享,必须是protected
;static
成员变量并且仅在本类使用,必须是private
;static
成员变量如果仅在本类使用,必须是private
;static
成员变量,考虑是否为final
;private
;protected
;hashCode
和equals
的处理: equals
,就必须重写hashCode
;Set
存储的是不重复的对象,依据hashCode
和equals
进行判断,所以Set
存储的对象必须重写这两个方法;Map
的键,那么必须重写hashCode
和equals
;Arrays.asList()
把数组转换成集合时,不能使用其修改集合相关的方法,它的add
/remove
/clear
方法会抛出UnsupportedOperationException
异常(【重要】:asList
的返回对象是一个 Arrays
内部类,并没有实现集合的修改方法。Arrays.asList
体现的是适配器模式,只是转换接口,后台的数据仍是数组); extends T >
来接收返回的数据,此写法的泛型集合不能使用add
方法,而 super T>
不能使用get
方法,作为接口调用赋值时易出错。PECS(Producer Extends Consumer Super) 原则:第一、频繁往外读取内容的,适合用 extends T >
,第二、经常往里插入的,适合用 super T>
;foreach
循环里进行元素的remove
/add
操作。remove
元素请使用Iterator
方式,如果并发操作,需要对Iterator
对象加锁;Comparator
实现类要满足如下三个条件,不然Arrays.sort
,Collections.sort
会报IllegalArgumentException
异常,三个条件: <>
来指代前边已经指定的类型)或全省略,比如:List list1 = new ArrayList<>();
和List list2 = new ArrayList();
;Map map = new HashMap(20);
,HashMap(int initialCapacity)
初始化时,initialCapacity = (需要存储的元素个数 / 负载因子) + 1
。注意负载因子(即loaderfactor
)默认为0.75, 如果暂时无法确定初始值大小,请设置为16(即默认值),如果一个HashMap
需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容量7次被迫扩大, resize
需要重建hash表,严重影响性能,具体扩容参看HashMap
的源码;entrySet
遍历Map
类集合KV
,而不是keySet
方式进行遍历,keySet
其实是遍历了2次,一次是转为Iterator
对象,另一次是从hashMap
中取出key
所对应的value
。而entrySet
只是遍历了一次就把key
和value
都放到了entry
中,效率更高。如果是JDK 8,使用Map.foreach
方法;ArrayList
是order
/unsort
;HashMap
是unorder
/unsort
;TreeSet
是order
/sort
。Set
元素唯一的特性,可以快速对一个集合进行去重操作,避免使用List
的contains
方法进行遍历、对比、去重操作;Executors
去创建,而是通过ThreadPoolExecutor
的方式,这样可以让写的同学更加明确线程池的运行规则,规避资源消耗的风险,说明。SimpleDateFormat
是线程不安全的类,一般不要定义为static
变量,如果定义为static
,必须加锁,或者使用 DateUtils
工具类,说明;version
(也可以使用时间戳)作为更新依据,如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次;Timer
运行多个TimeTask
时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService
则没有这个问题;CountDownLatch
进行异步转同步操作,每个线程退出前必须调用countDown
方法(创建的时候new CountDownLatch(int x)
,每次调用countDown
方法则x
减一,在调用countDownLatch.await()
方法时,将会等待所有调用countDown
方法的线程才会继续执行),线程执行代码注意catch
异常,确保countDown
方法被执行到,避免主线程无法执行至await
方法,直到超时才返回结果。volatile
解决多线程内存不可见问题。对于一写多读的场景,是可以解决变量同步问题,但是如果多写的场景,同样无法解决线程安全问题,说明。HashMap
在容量不够进行resize
时由于高并发可能出现死链,导致CPU飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险;ThreadLocal
无法解决共享对象的更新问题,ThreadLocal
对象建议使用static
修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量 ,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量;switch
块内,每个case
要么通过break
/return
等来终止,要么注释说明程序将继续执行到哪一个 case
为止;在一个switch
块内,都必须包含一个default
语句并且放在最后,即使空代码;if-else
方式,这种方式的可以做一些优化;getXxx
/isXxx
)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性,说明;RPC
/API
/HTTP
接口;DAO
层与Service
层都在同一个应用中,部署在同一台服务器中,所以DAO
的参数校验,可以省略;private
只会被自己代码所调用的方法,如果能够确定调用方法的代码传入参数已经做过检查或者肯定不会有问题,此时可以不校验参数;$!{var}
(必须加中间的感叹号,如果var
等于null
或者不存在,那么${var}
会直接显示在页面上,而加了!
就不会);Math.random()
这个方法返回是double
类型,注意取值的范围0≤x<1(能够取到零值,注意除零异常 ),如果想获取整数类型的随机数,不要将x
放大10的若干倍然后取整,直接使用Random
对象的nextInt
或者nextLong
方法;System.currentTimeMillis();
而不是new Date().getTime();
,如果想获取更加精确的纳秒级时间值,使用System.nanoTime()
,在JDK8中,针对统计时间等场景,推荐使用Instant
类;///
)来说明注释掉代码的理由;is_xxx
的方式命名(但POJO类中的任何布尔类型的变量,都不要加is
前缀),数据类型是unsigned tinyint
(1表示是,0表示否);pk_ 字段名
(primary key),唯一索引名为uk _字段名
(unique key),普通索引名则为idx _字段名
;decimal
,禁止使用float
和double
(都有精度损失的问题),若存储的数据范围超过decimal
的范围,建议将数据拆成整数和小数分开存储;char
定长字符串类型;id
、gmt_create
(主动创建的时间)和gmt_modified
(最近更新的时间),其中id
必为主键,类型为bigint unsigned
、单表时自增、步长为1, gmt_create
和gmt_modified
的类型均为datetime
类型;业务名称_表的作用
,如alipay_task
/force_project
/trade_config
;varchar
超长字段,更不能是text
字段;insert
速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生;join
,需要join
的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引;count(列名)
或count(常量)
来替代count(*)
,count(*)
是SQL 92定义的标准统计行数的语法,跟数据库无关,跟NULL
和非NULL
无关;ISNULL()
来判断是否为NULL
值,NULL
与任何值的直接比较都为NULL
;count
为0应直接返回,避免执行后面的分页语句;select
,避免出现误删除,确认无误才能执行更新语句;in
操作能避免则避免,若实在避免不了,需要仔细评估in
后边的集合元素数量,控制在1000个之内;*
作为查询的字段列表,需要哪些字段必须明确写明,MyBatis中通常使用
标记复用SQL,然后使用
调用复用的SQL,而且便于维护;is
,而数据库字段必须加is _
,要求在resultMap中进行字段与属性之间的映射,MyBatis Generator生成的代码中,需要进行对应的修改;sql.xml
配置参数使用:#{}
、#param#
,不要使用${}
此种方式容易出现SQL注入;gmt_modified
字段值为当前时间;【问题】
问题1:在foreach
循环里进行元素的remove
/add
操作时,给出的案例如下:
// 正例
List list = new ArrayList<>();
list.add("1");
list.add("2");
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if ("1".equals(item)) {
iterator.remove();
}
}
// 反例
for (String item : list) {
if ("1".equals(item)) {
list.remove(item);
}
}
确实没有看出区别,作者说
以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的
结果吗?
问题2:在控制语句那一块,有一条规约为:
在高并发场景中,避免使用“等于”判断作为中断或退出的条件,如果并发控制没有处理好,容易产生等值判断被“击穿”的情况,使用大于或小于的区间判断条件来代替;
使用==
可能被击穿可以理解,为什么用区间就不会出现被击穿?
解答2:这里主要是考虑的一旦为0的那个时刻没有抓到,而抓到那个变量的时候早就变成了负数,那么==0
这个condition内部的逻辑完了,因为它不会再回到0的那一刻让内部逻辑执行一遍,但是如果改成<=0
的逻辑就没有这个问题,就算被击穿,至少内部的逻辑还是会执行,减少损失程度。
【分析】
测试代码:
Integer integer1 = 128;
Integer integer2 = 128;
System.out.println(integer1==integer2);
System.out.println(integer1.equals(integer2));
结果是false
和true
,长见识了……
Executors
返回线程池的弊端FixedThreadPool
和SingleThreadPool
: Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致OOM(内存溢出异常);CachedThreadPool
和ScheduledThreadPool
: Integer.MAX_VALUE
,可能会创建大量的线程,从而导致OOM;SimpleDateFormat
的处理一般推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() {
@ Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
JDK8中可以使用Instant
代替Date
,LocalDateTime
代替Calendar
,DateTimeFormatter
代替 SimpleDateFormat
。
推荐使用原子类解决变量在多写场景下的线程安全问题,以count++
操作为例,主要实现如下:
AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
JDK8,推荐使用LongAdder
对象,比AtomicLong
性能更好(减少乐观锁的重试次数)。
if-else
语句的优化 正常的if-else
语句可以改写为下面的这种形式:
if(condition) {
...
return obj;
}
这样做的目的是便于维护,如果非得使用if()...else if()...else...
方式表达逻辑,避免后续代码维护困难,禁止超过3层;超过3层的if-else
的逻辑判断代码可以使用卫语句(可能是因为圈复杂度)、策略模式、状态模式等来实现,其中卫语句示例如下:
public void today() {
if (isBusy()) {
System.out.println(“change time.”);
return;
}
if (isFree()) {
System.out.println(“go to travel.”);
return;
}
System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”);
return;
}
if
判断中跟太多逻辑判断,影响代码阅读,不如将复杂的条件抽离出来,给一个判断的结果传到if
语句中,比如:
// 正确的写法
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
// 错误的写法
if ((file.open(fileName, "w") != null) && (...) || (...)) {
...
}
对于订单业务中,肯定存在用户和订单这两张表(实际可能是用户和商品之间存在一个关联表,这个例子不好,只用于理解),user_name
字段必定是在user
表中,对于用户的增删改查都很方便,然后订单信息表中搞了一个user_id
来关联两者之间的关系,如果想查询到一个订单和具体用户之间的关联关系,就要通过订单表中的user_id
去查询用户表中的用户名字,这样就要作join
操作,当用户数量达到一定程度后,这种查询会很有压力(在那么多用户中查找一个用户),基于这样的需求允许在订单表中加入user_name
(原本应属于用户表中)这样的冗余字段来提高查询性能,定位到订单信息就可以直接在该行记录中找到原来所需要的具体用户的名字。