版本号 | 版本名 | 更新日期 | 备注 |
---|---|---|---|
1.3.0 | 终极版 | 2017.09.25 | 单元测试规约,IDE代码规约插件 |
1.3.1 | 记念版 | 2017.11.30 | 修正部分描述 |
1.4.0 | 详尽版 | 2018.05.20 | 增长设计规约大类,共16条 |
1.5.0 | 华山版 | 2019.06.19 | 详细更新见下面 |
本笔记主要基于华山版
(1.5.0)的总结。华山版具体更新以下:java
Java开发手册
的限定词阿里巴巴
switch
的NPE问题、浮点数的比较、无泛型限制、锁的使用方式、判断表达式、日期格式等IFNULL
的判断、集合的toArray
、日志处理等enum
示例、finally
的return
示例等。PDF下载地址: https://pan.baidu.com/s/1K-GZ_CzRC0igIxMgLGVtZQ
密码:关注行无际
的微信公众号:it_wild
,回复java开发手册
程序员
POJO
(Plain Ordinary Java Object): 在本手册中,POJO专指只有setter、getter、toString的简单类,包括DO、DTO、BO、VO等。GAV
(GroupId、ArtifactctId、Version): Maven坐标,是用来惟一标识jar包。OOP
(Object Oriented Programming): 本手册泛指类、对象的编程处理方式。ORM
(Object Relation Mapping): 对象关系映射,对象领域模型与底层数据之间的转换,本文泛指ibatis, mybatis等框架。NPE
(java.lang.NullPointerException): 空指针异常。SOA
(Service-Oriented Architecture): 面向服务架构,它能够根据需求经过网络对松散耦合的粗粒度应用组件进行分布式部署、组合和使用,有利于提高组件可重用性,可维护性。IDE
(Integrated Development Environment): 用于提供程序开发环境的应用程序,通常包括代码编辑器、编译器、调试器和图形用户界面等工具,本《手册》泛指 IntelliJ IDEA 和eclipse。OOM
(Out Of Memory): 源于java.lang.OutOfMemoryError,当JVM没有足够的内存来为对象分配空间而且垃圾回收器也没法回收空间时,系统出现的严重情况。一方库
:本工程内部子项目模块依赖的库(jar包)。二方库
:公司内部发布到中央仓库,可供公司内部其它应用依赖的库(jar包)。三方库
:公司以外的开源库(jar包)。正则表达式
alibaba
/ youku
/ hangzhou
等UpperCamelCase
风格,但如下情形例外:DO
/ BO
/ DTO
/ VO
/ AO
/ PO
/ UID
等。如:UserDO
/ XmlService
/ TcpUdpDeal
lowerCamelCase
风格,必须听从驼峰形式;localValue
/ getHttpMessage
/ inputUserId
MAX_STOCK_COUNT
/ CACHE_EXPIRED_TIME
Abstract
或Base
开头;异常类命名使用Exception
结尾;测试类命名以它要测试的类的名称开始,以Test
结尾int[] arrayDemo;
包名
统一使用小写
,点分隔符之间有且仅有一个天然语义的英语单词。包名统一使用单数
形式,可是类名
若是有复数含义,类名可使用复数形式
。包名com.alibaba.ai.util
,类名为MessageUtils
(此规则参考spring
的框架结构)AtomicReferenceFieldUpdater
startTime
/ workQueue
/ nameList
/ TERMINATED_THREAD_COUNT
设计模式
,在命名时需体现出具体模式(将设计模式体如今名字中,有利于阅读者快速理解架构设计理念)。如: class OrderFactory
/ class LoginProxy
/ class ResourceObserver
public
也不要加),保持代码的简洁性,并加上有效的Javadoc
注释。尽可能不要在接口里定义变量,若是必定要定义变量,确定是与接口方法相关,而且是整个应用的基础常量。接口方法签名void commit();
,接口基础常量String COMPANY = "alibaba";
Service
和DAO
类,基于SOA
的理念,暴露出来的服务必定是接口,内部的实现类用Impl
的后缀与接口区别。如CacheServiceImpl
实现CacheService
接口形容能力
的接口
名称,取对应的形容词为接口名(一般是–able
的形容词)如 AbstractTranslator
实现Translatable
接口Enum
后缀,枚举成员名称须要全大写
,单词间用下划线
隔开。(说明:枚举其实就是特殊的类,域成员均为常量,且构造方法被默认强制是私有。)枚举名字为ProcessStatusEnum
的成员名称:SUCCESS
/ UNKNOWN_REASON
各层命名规约:spring
A) Service/DAO 层方法命名规约
- 获取单个对象的方法用
get
作前缀。- 获取多个对象的方法用
list
作前缀,复数形式结尾如:listObjects
。- 获取统计值的方法用
count
作前缀。- 插入的方法用
save
/insert
作前缀。- 删除的方法用
remove
/delete
作前缀。- 修改的方法用
update
作前缀。
B) 领域模型命名规约数据库
- 数据对象:
xxxDO
,xxx即为数据表名。- 数据传输对象:
xxxDTO
,xxx为业务领域相关的名称。- 展现对象:
xxxVO
,xxx通常为网页名称。- POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。
反例:编程
AbstractClass
“缩写”命名成condition
“缩写”命名成POJO类中布尔类型
变量都不要加is
前缀,不然部分框架解析会引发序列化错误
。设计模式
定义为基本数据类型
Boolean isDeleted
的属性,它的方法也是isDeleted()
,RPC框架在反向解析的时候,“误觉得”对应的属性名称是deleted
,致使属性获取不到,进而抛出异常。数组
"Id#taobao_"
+ tradeId;long
或者Long
赋值时,数值后使用大写的L
,不能是小写的l
,小写容易跟数字1混淆,形成误解。CacheConsts
下;系统配置相关常量放在类ConfigConsts
下。enum
类型来定义。tab
字符。int second = (int)first + 2;
不一样逻辑、不一样语义、不一样业务的代码之间插入一个空行
分隔开来以提高可读性(说明:任何情形,没有必要插入多个空行进行隔开
。)缓存
对象引用
访问此类的静态变量
或静态方法
,无谓增长编译器解析成本,直接用类名来访问便可@Override
注解@Deprecated
注解,并清晰地说明采用的新接口或者新服务是么。equals
方法容易抛空指针异常,应使用常量或肯定有值的对象来调用equals,"test".equals(object);
【推荐使用 java.util.Objects#equals(JDK7 引入的工具类】equals
方法比较。【在-128至127这个区间以外的全部数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断】DO
类时,属性类型要与数据库字段类型相匹配。数据库字段的bigint
必须与类属性的Long
类型相对应。BigDecimal(double)
的方式把double
值转化为BigDecimal
对象(在精确计算或值比较的场景中可能会致使业务逻辑异常)。BigDecimal g = new BigDecimal(0.1f);
实际的存储值为:0.10000000149
。正例:优先推荐入参为String
的构造方法,或使用BigDecimal
的valueOf
方法,此方法内部其实执行了Double
的toString
,而Double的toString
按double
的实际能表达的精度对尾数进行了截断。BigDecimal recommend1 = new BigDecimal("0.1"); BigDecimal recommend2 = BigDecimal.valueOf(0.1);
1)【强制】全部的POJO类属性必须使用包装数据类型。2)【强制】RPC方法的返回值和参数必须使用包装数据类型。3) 【推荐】全部的局部变量使用基本数据类型。
【说明:POJO类属性没有初值是提醒使用者在须要使用时,必须本身显式地进行赋值,任何NPE问题,或者入库检查,都由使用者来保证。正例:数据库的查询结果多是null,由于自动拆箱,用基本数据类型接收有NPE风险。反例:好比显示成交总额涨跌状况,即正负x%,x为基本数据类型,调用的RPC服务,调用不成功时,返回的是默认值,页面显示为0%,这是不合理的,应该显示成中划线。因此包装数据类型的null值,可以表示额外的信息,如:远程调用失败,异常退出。
】POJO
类时,不要设定任何属性默认值
。【反例:POJO 类的 createTime 默认值为 new Date(),可是这个属性在数据提取时并无置入具体值,在更新其它字段时又附带更新了此字段,致使建立时间被修改为当前时间。】serialVersionUID
字段,避免反序列失败;若是彻底不兼容升级,避免反序列化混乱,那么请修改serialVersionUID
值。(说明:注意serialVersionUID不一致会抛出序列化运行时异常。)init
方法中。POJO
类必须写toString
方法。使用IDE中的工具:source> generate toString时,若是继承了另外一个POJO类,注意在前面加一下super.toString
。【说明:在方法执行抛出异常时,能够直接调用 POJO 的 toString()
方法打印其属性值,便于排查问题】POJO
类中,同时存在对应属性xxx的isXxx()
和getXxx()
方法。【说明:框架在调用属性 xxx 的提取方法时,并不能肯定哪一个方法必定是被优先调用到】split
方法获得的数组时,需作最后一个分隔符后有无内容的检查,不然会有抛IndexOutOfBoundsException
的风险getter/setter
方法中,不要增长业务逻辑,增长排查问题的难度StringBuilder
的append
方法进行扩展final
能够声明类(不容许被继承的类,如String
类)、成员变量(不容许修改引用的域对象)、方法、以及本地变量(不容许运行过程当中从新赋值的局部变量),避免上下文重复使用一个变量,使用final能够强制从新定义一个变量,方便更好地进行重构Object
的clone
方法来拷贝对象,对象clone
方法默认是浅拷贝,若想实现深拷贝需覆写clone
方法实现域对象的深度遍历式拷贝。1)若是不容许外部直接经过new来建立对象,那么构造方法必须是private。2)工具类不容许有public或default构造方法。3)类非static 成员变量而且与子类共享,必须是protected。 4)类非static成员变量而且仅在本类使用,必须是private。5)类static成员变量若是仅在本类使用,必须是private。 6)如果static成员变量,考虑是否为final。7)类成员方法只供类内部调用,必须是 private。8)类成员方法只对继承类公开,那么限制为protected。
【说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦】浮点数
之间的等值判断,基本数据类型不能用==来比较,包装数据类型不能用equals 来判断。【浮点数采用“尾数+阶码”的编码方式,相似于科学计数法的“有效数字+指数”的表示方式】// 反例 float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; if (a == b) { // 预期进入此代码快,执行其它业务逻辑 // 但事实上 a==b 的结果为 false } Float x = Float.valueOf(a); Float y = Float.valueOf(b); if (x.equals(y)) { // 预期进入此代码快,执行其它业务逻辑 // 但事实上 equals 的结果为 false } // 正例 // (1)指定一个偏差范围,两个浮点数的差值在此范围以内,则认为是相等的 float a = 1.0f - 0.9f; float b = 0.9f - 0.8f; float diff = 1e-6f; if (Math.abs(a - b) < diff) { System.out.println("true"); } // (2)使用BigDecimal来定义值,再进行浮点数的运算操做 // BigDecimal构造的时候注意事项 见上文 BigDecimal a = new BigDecimal("1.0"); BigDecimal b = new BigDecimal("0.9"); BigDecimal c = new BigDecimal("0.8"); BigDecimal x = a.subtract(b); BigDecimal y = b.subtract(c); if (x.equals(y)) { System.out.println("true"); }
hashCode
和equals
的处理,遵循以下规则:1)只要覆写equals
,就必须覆写hashCode
。2)由于Set
存储的是不重复的对象,依据hashCode
和equals
进行判断,因此Set
存储的对象必须覆写这两个方法。3)若是自定义对象做为Map
的键,那么必须覆写hashCode
和equals
。【说明:String
已覆写hashCode
和equals
方法,因此咱们能够愉快地使用String
对象做为key
来使用】ArrayList
的subList
结果不可强转成ArrayList
,不然会抛出ClassCastException
异常,即java.util.RandomAccessSubList cannot be cast to java.util.ArrayList【说明:subList
返回的是ArrayList
的内部类SubList
,并非ArrayList
而是ArrayList
的一个视图,对于SubList
子列表的全部操做最终会反映到原列表上】Map
的方法keySet()/values()/entrySet()
返回集合对象时,不能够对其进行添加元素操做,不然会抛出UnsupportedOperationException
异常Collections
类返回的对象,如:emptyList()/singletonList()
等都是immutable list,不可对其进行添加或者删除元素的操做【反例:若是查询无结果,返回 Collections.emptyList()
空集合对象,调用方一旦进行了添加元素的操做,就会触发UnsupportedOperationException
异常。】subList
场景中,高度注意对原集合元素的增长或删除,均会致使子列表的遍历、增长、删除产生ConcurrentModificationException
异常toArray(T[] array)
,传入的是类型彻底一致、长度为0的空数组【反例:直接使用toArray无参方法存在问题,此方法返回值只能是Object[]类,若强转其它类型数组将出现ClassCastException错误。】// 正例 List<String> list = new ArrayList<>(2); list.add("行无际"); list.add("itwild"); String[] array = list.toArray(new String[0]); /* 说明: 使用toArray带参方法,数组空间大小的length: 1)等于0,动态建立与size相同的数组,性能最好 2)大于0但小于size,从新建立大小等于size的数组,增长GC负担 3)等于size,在高并发状况下,数组建立完成以后,size正在变大的状况下,负面影响与上相同 4)大于size,空间浪费,且在size处插入null值,存在NPE隐患 */
Collection
接口任何实现类的addAll()
方法时,都要对输入的集合参数进行NPE判断 【说明:在ArrayList#addAll
方法的第一行代码即Object[] a = c.toArray();
其中c为输入集合参数,若是为null,则直接抛出异常。】Arrays.asList()
把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear
方法会抛出UnsupportedOperationException
异常【说明:asList
的返回对象是一个Arrays
内部类,并无实现集合的修改方法。Arrays.asList
体现的是适配器模式,只是转换接口,后台的数据还是数组。】String[] str = new String[] { "it", "wild" }; List list = Arrays.asList(str); // 第一种状况:list.add("itwild"); 运行时异常 // 第二种状况:str[0] = "changed1"; 也会随之修改 // 反之亦然 list.set(0, "changed2");
泛型通配符<? extends T>
来接收返回的数据,此写法的泛型集合不能使用add方法,而<? super T>
不能使用get方法,做为接口调用赋值时易出错。【说明:扩展说一下PECS
(Producer Extends Consumer Super)原则:第1、频繁往外读取内容的,适合用<? extends T>
。第2、常常往里插入的,适合用<? super T>
】安全
这个地方我以为有必要简单解释一下(
行无际
本人的我的理解哈,有不对的地方欢迎指出),上面的说法可能有点官方或者难懂。其实咱们一直也是这么干的,不过没注意而已。举个最简单的例子,用泛型
的时候,若是你遍历
(read
)一个List,你是否是但愿List里面装的越具体越好啊,你但愿里面装的是Object
吗,若是里面装的是Object
那么你想一想你会有多痛苦,每一个对象都用instanceof
判断一下再类型强转
,因此这个方法的参数List主要用于遍历
(read
)的时候,大多数状况你可能会要求里面的元素最大是T
类型,即用<? extends T>
限制一下。再看你往List里面插入
(write
)数据又会怎么样,为了灵活性和可扩展性,你立刻可能就要说我固然但愿List里面装的是Object
了,这样我什么类型的对象都能往List里面写啊,这样设计出来的接口的灵活性和可扩展性才强啊,若是里面装的类型太靠下(假定继承层次从上往下
,父类在上,子孙类在下),那么位于上级的不少类型的数据你就没法写入了,这个时候用<? super T>
来限制一下最小是T
类型。下面咱们来看Collections.copy()
这个例子。
// 这里就要求dest的List里面的元素类型 不能在src的List元素类型 之下 // 若是dest的List元素类型位于src的List元素类型之下,就会出现写不进dest public static <T> void copy(List<? super T> dest, List<? extends T> src) { //....省略具体的copy代码 } // 下面再看我写的测试代码就更容易理解了 static class Animal {} static class Dog extends Animal {} static class BlackDog extends Dog {} @Test public void test() throws Exception { List<Dog> dogList = new ArrayList<>(2); dogList.add(new BlackDog()); dogList.add(new BlackDog()); List<Animal> animalList = new ArrayList<>(2); animalList.add(new Animal()); animalList.add(new Animal()); // 错误,没法编译经过 Collections.copy(dogList, animalList); // 正确 Collections.copy(animalList, dogList); // Collections.copy()的泛型参数就起做到了很好的限制做用 // 编译期就能发现类型不对 }
instanceof
判断,避免抛出ClassCastException
异常。// 反例 List<String> generics = null; List notGenerics = new ArrayList(10); notGenerics.add(new Object()); notGenerics.add(new Integer(1)); generics = notGenerics; // 此处抛出 ClassCastException 异常 String string = generics.get(0);
foreach
循环里进行元素的remove/add
操做。remove
元素请使用Iterator
方式,若是并发操做,须要对Iterator对象加锁
// 正例 List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); Iterator<String> iterator = list.iterator(); while (iterator.hasNext()) { String item = iterator.next(); if (删除元素的条件) { iterator.remove(); } } // 反例 for (String item : list) { if ("1".equals(item)) { list.remove(item); } }
Comparator
实现类要知足以下三个条件,否则Arrays.sort
,Collections.sort
会抛IllegalArgumentException
异常【说明:三个条件以下 1)x,y 的比较结果和 y,x 的比较结果相反。2)x>y,y>z,则x>z。 3) x=y,则x,z比较结果和y,z 比较结果相同。】// 反例:下例中没有处理相等的状况 new Comparator<Student>() { @Override public int compare(Student o1, Student o2) { return o1.getId() > o2.getId() ? 1 : -1; } };
// 正例 // diamond 方式,即<> HashMap<String, String> userCache = new HashMap<>(16); // 全省略方式 ArrayList<User> users = new ArrayList(10);
集合初始化时,指定集合初始值大小【说明:HashMap
使用HashMap(int initialCapacity)
初始化。】
正例:
initialCapacity
= (须要存储的元素个数 / 负载因子) + 1。注意负载因子(即loader factor
)默认为0.75,若是暂时没法肯定初始值大小,请设置为16(即默认值)。
反例:
HashMap
须要放置1024个元素,因为没有设置容量初始大小,随着元素不断增长,容量7次被迫扩大,resize
须要重建hash
表,严重影响性能。
使用entrySet
遍历Map
类集合KV,而不是keySet
方式进行遍历。【说明:keySet
实际上是遍历了2次,一次是转为Iterator
对象,另外一次是从hashMap
中取出 key
所对应的value
。而entrySet
只是遍历了一次就把key
和value
都放到了entry中,效率更高。若是是JDK8,使用Map.forEach
方法。】
正例:
values()
返回的是V值集合,是一个list集合对象;keySet()
返回的是K值集合,是一个Set集合对象;entrySet()
返回的是K-V
值组合集合。
高度注意Map
类集合K/V
能不能存储null
值的状况,以下表格:
集合类 | Key | Value | Super | 说明 |
---|---|---|---|---|
Hashtable | 不容许为null | 不容许为null | Dictionary | 线程安全 |
ConcurrentHashMap | 不容许为null | 不容许为null | AbstractMap | 锁分段技术(JDK8:CAS) |
TreeMap | 不容许为null | 容许为null | AbstractMap | 线程不安全 |
HashMap | 容许为null | 容许为null | AbstractMap | 线程不安全 |
反例:因为
HashMap
的干扰,不少人认为ConcurrentHashMap
是能够置入null
值,而事实上,存储null
值时会抛出NPE
异常。
sort
)和稳定性(order
),避免集合的无序性(unsort
)和不稳定性(unorder
)带来的负面影响。【说明:有序性是指遍历的结果是按某种比较规则依次排列的。稳定性指集合每次遍历的元素次序是必定的。如:ArrayList
是 order/unsort
;HashMap
是unorder/unsort
;TreeSet
是order/sort
。】Set
元素惟一的特性,能够快速对一个集合进行去重操做,避免使用List
的contains
方法进行遍历、对比、去重操做// 正例:自定义线程工厂,而且根据外部特征进行分组,好比机房信息 public class UserThreadFactory implements ThreadFactory { private final String namePrefix; private final AtomicInteger nextId = new AtomicInteger(1); // 定义线程组名称,在 jstack 问题排查时,很是有帮助 UserThreadFactory(String whatFeaturOfGroup) { namePrefix = "From UserThreadFactory's " + whatFeaturOfGroup + "-Worker-"; } @Override public Thread newThread(Runnable task) { String name = namePrefix + nextId.getAndIncrement(); Thread thread = new Thread(null, task, name, 0, false); System.out.println(thread.getName()); return thread; } }
线程池不容许使用Executors
去建立,而是经过ThreadPoolExecutor
的方式,这样的处理方式让写的同窗更加明确线程池的运行规则,规避资源耗尽的风险
说明:
Executors
返回的线程池对象的弊端以下:
1)
FixedThreadPool
和SingleThreadPool
:
容许的请求队列长度为Integer.MAX_VALUE
,可能会堆积大量的请求,从而致使OOM
。
2)
CachedThreadPool
:
容许的建立线程数量为Integer.MAX_VALUE
,可能会建立大量的线程,从而致使OOM
。
SimpleDateFormat
是线程不安全的类,通常不要定义为static
变量,若是定义为static
,必须加锁。【说明:若是是JDK8的应用,可使用Instant
代替Date
,LocalDateTime
代替Calendar
,DateTimeFormatter
代替SimpleDateFormat
,官方给出的解释:simple beautiful strong immutable thread-safe
。】// 正例:注意线程安全。亦推荐以下处理 private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() { @Override protected DateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } };
ThreadLocal
变量,尤为在线程池场景下,线程常常会被复用,若是不清理自定义的ThreadLocal
变量,可能会影响后续业务逻辑和形成内存泄露等问题。尽可能使用try-finally
块进行回收// 正例 objectThreadLocal.set(userInfo); try { // ... } finally { objectThreadLocal.remove(); }
RPC
方法。】在使用阻塞等待获取锁的方式中,必须在try
代码块以外,而且在加锁方法与try
代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally
中没法解锁
说明一:若是在
lock
方法与try
代码块之间的方法调用抛出异常,那么没法解锁,形成其它线程没法成功获取锁。
说明二:若是
lock
方法在try
代码块以内,可能因为其它方法抛出异常,致使在finally
代码块中,unlock
对未加锁的对象解锁,它会调用AQS
的tryRelease
方法(取决于具体实现类),抛出IllegalMonitorStateException
异常。
说明三:在
Lock
对象的lock
方法实现中可能抛出unchecked
异常,产生的后果与说明二相同
// 正例 Lock lock = new XxxLock(); // ... lock.lock(); try { doSomething(); doOthers(); } finally { lock.unlock(); } // 反例 Lock lock = new XxxLock(); // ... try { // 若是此处抛出异常,则直接执行 finally 代码块 doSomething(); // 不管加锁是否成功,finally 代码块都会执行 lock.lock(); doOthers(); } finally { lock.unlock(); }
// 正例 Lock lock = new XxxLock(); // ... boolean isLocked = lock.tryLock(); if (isLocked) { try { doSomething(); doOthers(); } finally { lock.unlock(); } }
version
做为更新依据【说明:若是每次访问冲突几率小于20%,推荐使用乐观锁,不然使用悲观锁。乐观锁的重试次数不得小于3次。】Timer
运行多个TimeTask
时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,若是在处理定时任务时使用ScheduledExecutorService
则没有这个问题CountDownLatch
进行异步转同步操做,每一个线程退出前必须调用countDown
方法,线程执行代码注意catch异常,确保countDown
方法被执行到,避免主线程没法执行至await
方法,直到超时才返回结果【说明:注意,子线程抛出异常堆栈,不能在主线程try-catch
到。】Random
实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一seed
致使的性能降低【说明:Random
实例包括java.util.Random
的实例或者Math.random()
的方式。正例:在JDK7以后,能够直接使用APIThreadLocalRandom
,而在JDK7以前,须要编码保证每一个线程持有一个实例】(double-checked locking)
实现延迟初始化的优化问题隐患(可参考 The "Double-Checked Locking is Broken" Declaration),推荐解决方案中较为简单一种(适用于JDK5及以上版本),将目标属性声明为volatile
型。// 注意 这里的代码并不是出自官方的《java开发手册》 // 参考 https://blog.csdn.net/lovelion/article/details/7420886 public class LazySingleton { // volatile除了保证内容可见性还有防止指令重排序 // 对象的建立其实是三条指令: // 一、分配内存地址 二、内存地址初始化 三、返回内存地址句柄 // 其中二、3之间可能发生指令重排序 // 重排序可能致使线程A建立对象先执行一、3两步, // 结果线程B进来判断句柄已经不为空,直接返回给上层方法 // 此时对象尚未正确初始化内存,致使上层方法发生严重错误 private volatile static LazySingleton instance = null; private LazySingleton() { } public static LazySingleton getInstance() { // 第一重判断 if (instance == null) { synchronized (LazySingleton.class) { // 第二重判断 if (instance == null) { // 建立单例实例 instance = new LazySingleton(); } } } return instance; } } // 既然这里提到 单例懒加载,还有这样写的 // 参考 https://blog.csdn.net/lovelion/article/details/7420888 class Singleton { private Singleton() { } private static class HolderClass { // 由Java虚拟机来保证其线程安全性,确保该成员变量只能初始化一次 final static Singleton instance = new Singleton(); } public static Singleton getInstance() { return HolderClass.instance; } }
volatile
解决多线程内存不可见问题。对于一写多读,是能够解决变量同步问题,可是若是多写,一样没法解决线程安全问题。【说明:若是是count++
操做,使用以下类实现:AtomicInteger count = new AtomicInteger();
count.addAndGet(1);
若是是JDK8,推荐使用LongAdder
对象,比AtomicLong
性能更好(减小乐观锁的重试次数)。】HashMap
在容量不够进行resize
时因为高并发可能出现死链,致使CPU飙升,在开发过程当中可使用其它数据结构或加锁来规避此风险ThreadLocal
对象使用static
修饰,ThreadLocal
没法解决共享对象的更新问题【说明:这个变量是针对一个线程内全部操做共享的,因此设置为静态变量,全部此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,全部此类的对象(只要是这个线程内定义的)均可以操控这个变量】switch
括号内的变量类型为String
而且此变量为外部参数时,必须先进行null
判断。public class SwitchString { public static void main(String[] args) { // 这里会抛异常 java.lang.NullPointerException method(null); } public static void method(String param) { switch (param) { // 确定不是进入这里 case "sth": System.out.println("it's sth"); break; // 也不是进入这里 case "null": System.out.println("it's null"); break; // 也不是进入这里 default: System.out.println("default"); } } }
if/else/for/while/do
语句中必须使用大括号。【说明:即便只有一行代码,避免采用单行的编码方式:if (condition) statements;
在高并发场景
中,避免使用”等于”判断做为中断或退出的条件。【说明:若是并发控制没有处理好,容易产生等值判断被“击穿”的状况,使用大于或小于的区间判断条件来代替。】
反例:判断剩余奖品数量等于 0 时,终止发放奖品,但由于并发处理错误致使奖品数量瞬间变成了负数,这样的话,活动没法终止。
if-else
方式,这种方式能够改写成下面代码:【说明:若是非使用if()...else if()...else...
方式表达逻辑,避免后续代码维护困难,请勿超过3
层】if (condition) { ... return obj; } // 接着写 else 的业务逻辑代码;
超过3层的
if-else
的逻辑判断代码可使用卫语句
、策略模式
、状态模式
等来实现。其中卫语句即代码逻辑先考虑失败、异常、中断、退出等直接返回的状况,以方法多个出口的方式,解决代码中判断分支嵌套的问题,这是逆向思惟的体现。
// 示例代码 public void findBoyfriend(Man man) { if (man.isUgly()) { System.out.println("本姑娘是外貌协会的资深会员"); return; } if (man.isPoor()) { System.out.println("贫贱夫妻百事哀"); return; } if (man.isBadTemper()) { System.out.println("银河有多远,你就给我滚多远"); return; } System.out.println("能够先交往一段时间看看"); }
getXxx/isXxx
)等外,不要在条件判断中执行其它复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提升可读性。【说明:不少if
语句内的逻辑表达式至关复杂,与、或、取反混合运算,甚至各类方法纵深调用,理解成本很是高。若是赋值一个很是好理解的布尔变量名字,则是件使人爽心悦目的事情。】// 正例 // 伪代码以下 final boolean existed = (file.open(fileName, "w") != null) && (...) || (...); if (existed) { ... } // 反例 // 哈哈,这好像是ReentrantLock里面有相似风格的代码 // 连Doug Lea的代码都拿来当作反面教材啊 // 早前就听别人说过“编程不识Doug Lea,写尽Java也枉然!!!” public final void acquire(long arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) { selfInterrupt(); } }
// 反例 public Lock getLock(boolean fair) { // 算术表达式中出现赋值操做,容易忽略 count 值已经被改变 threshold = (count = Integer.MAX_VALUE) - 1; // 条件表达式中出现赋值操做,容易误认为是 sync==fair return (sync = fair) ? new FairSync() : new NonfairSync(); }
try-catch
操做(这个try-catch
是否能够移至循环体外)if (x < 628)
来表达x小于628。反例:使用 if (!(x >= 628))来表达x小于628。】下列情形,须要进行参数校验
1) 调用频次低的方法。2)执行时间开销很大的方法。此情形中,参数校验时间几乎能够忽略不计,但若是由于参数错误致使中间执行回退,或者错误,那得不偿失。3)须要极高稳定性和可用性的方法。4)对外提供的开放接口,无论是
RPC/API/HTTP
接口。5)敏感权限入口。
下列情形,不须要进行参数校验:
1)极有可能被循环调用的方法。但在方法说明里必须注明外部参数检查要求。 2)底层调用频度比较高的方法。毕竟是像纯净水过滤的最后一道,参数错误不太可能到底层才会暴露问题。通常
DAO
层与Service
层都在同一个应用中,部署在同一台服务器中,因此DAO
的参数校验,能够省略。3)被声明成private
只会被本身代码所调用的方法,若是可以肯定调用方法的代码传入参数已经作过检查或者确定不会有问题,此时能够不校验参数。
Javadoc
规范,使用/**内容*/
格式,不得使用// xxx
方式。【说明:在IDE编辑窗口中,Javadoc
方式会提示相关注释,生成 Javadoc
能够正确输出相应注释;在IDE中,工程调用方法时,不进入方法便可悬浮提示方法、参数、返回值的意义,提升阅读效率。】/* */
注释,注意与代码对齐枚举类型
字段必需要有注释,说明每一个数据项的用途特殊注释标记,请注明标记人与标记时间。注意及时处理这些标记,经过标记扫描,常常清理此类标记。线上故障有时候就是来源于这些标记处的代码。
1)待办事宜(
TODO
):(标记人,标记时间,[预计处理时间])表示须要实现,但目前还未实现的功能。这其实是一个Javadoc
的标签,目前的Javadoc
还没
有实现,但已经被普遍使用。只能应用于类,接口和方法(由于它是一个Javadoc标签)。2)错误,不能工做(FIXME
):(标记人,标记时间,[预计处理时间])
在注释中用FIXME
标记某代码是错误的,并且不能工做,须要及时纠正的状况。
Pattern pattern = Pattern.compile(“规则”);
】Math.random()
这个方法返回是double
类型,注意取值的范围0≤x<1
(可以取到零值,注意除零异常),若是想获取整数类型的随机数,不要将x放大10的若干倍而后取整,直接使用Random
对象的nextInt
或者nextLong
方法。System.currentTimeMillis();
而不是new Date().getTime();
【说明:若是想获取更加精确的纳秒级时间值,使用System.nanoTime()
的方式。在JDK8中,针对统计时间等场景,推荐使用Instant
类。】pattern
中表示年份统一使用小写的y
。【说明:日期格式化时,yyyy
表示当天所在的年,而大写的YYYY
表明是 week in which year(JDK7以后引入的概念),意思是当天所在的周属于的年份,一周从周日开始,周六结束,只要本周跨年,返回的YYYY
就是下一年。另外须要注意:表示月份是大写的M,表示分钟则是小写的m,24小时制的是大写的H,12小时制的则是小写的h 。正例:new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
】///
)来讲明注释掉代码的理由】