原文:(P8)
领域模型命名规约如下:
- 数据对象: xxxDO,xx为数据表名。
- 数据传输对象:xxxDTO,xxx为业务领域相关的名称。
- 展示对象: xxxVO,xxx一般为网页命名。
- POJO是DO/DTO/BO/VO的统称,禁止命名成xxxPOJO。
1.1 JavaBean
JavaBean: 符合一定规范编写的Java类。
1.2 POJ
POJO: 就是普通的JavaBean,为了避免和EJB混淆所创造的简称,包括DO/DTO/BO/VO。
1.3 Entity
Entity: 实体类,对应表中的部分数据,类似DTO。
原文:(p10)
如果变量值仅在一个范围内变化,则用enum类型来定义。
2.1 Enum(value)
/**
* 当枚举类只有一个属性value的时候
*/
public enum SeasonEnum {
// 枚举值
SPRING(1), SUMMER(2), AUTUMN(3), WINTER(4);
// 属性
private Integer value;
// 构造函数
SeasonEnum(Integer value) {
this.value = value;
}
public Integer value() {
return value;
}
}
2.2 Enum(value, name)
/**
* 当枚举类有两个属性value、name的时候
*/
public enum SeasonEnum {
// 枚举值
SPRING(1, "spring"),
SUMMER(2, "summer"),
AUTUMN(3, "autumn"),
WINTER(4, "winter");
// 属性
private Integer value;
private String name;
// 静态map容器,用于存放value和SeasonEnum的对应关系
private static Map<Integer, SeasonEnum> valueMap = new HashMap();
// 初始化
static {
Arrays.asStream(SeasonEnum.values()).forEach(e -> valueMap.put(e.value, e));
}
// 构造函数
SeasonEnum(Integer value, String name) {
this.value = value;
this.name = name;
}
// 获取枚举类
public SeasonEnum of(Integer value) {
return valueMap.get(value);
}
// 获取value
public Integer value() {
return value;
}
// 获取name
public Integer name() {
return name;
}
}
原文:(p18)
推荐使用java.util.Objects.equals(Object obj, Object obj)(JDK7引入的工具类)。
原文:(p18)
所有整形包装类对象之间值的比较,全部使用equals方法。
说明:
对于Integer var = ? 在 -128~127范围内的赋值,Integer对象是在IntegerCache.cache中产生的,会复用已有对象,这个区间内的Integer值可以直接使用 == 进行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会服用已有对象,这是一个大坑,推荐使用equals方法进行判断。
原文:(p19)
浮点数之间的等职判断,基本数据类型不能用==进行比较,包装数据类型不能用equals方法进行判断。
举例:
public static void main(String[] args) {
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)) {
// 预期进入此代码块,但是x.equals(y)的结果为false
}
// 结果全部为false
System.out.println("result1: " + ((0.05 + 0.01) == 0.06));
System.out.println("result2: " + ((1.0 - 0.42) == 0.58));
System.out.println("result3: " + ((4.015 * 100) == 401.5));
System.out.println("result4: " + ((123.3 / 100) == 1.233));
}
解决方案:
数字类型操作尽量使用BigDecimal类型。
原文:(p21)
禁止使用构造方法BigDecimal(double)的方式把double值转化为BigDecimal对象。
举例:
public static void main(String[] args) {
System.out.println(new BigDecimal(0.13));
// 输出结果为:0.13000000000000000444089209850062616169452667236328125
}
解决方案:
采用如下两种方式:
- new BigDecimal(Double.toString(0.13))
- BigDecimal.valueOf(0.13)
原文:(p31)
不要再foreach循环里进行元素的 remove/add 操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。
举例:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("1");
list.add("2");
for (String item : list) {
if ("1".equals(item)) {
// 会抛出ConcurrentModificationException
list.remove(item);
}
}
}
解决方案:
public static void main(String[] args) {
List<String> list = new ArrayList();
list.add("1");
list.add("2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if ("1".equals(iterator.next())) {
iterator.remove();
}
}
}
原文:(p33)
使用entrySet()遍历Map类集合K/V,而不是用keySet()方式遍历。
说明:
keySet 其实遍历了2次,一次是转为Iterator对象,另一次是从hashMap中取出key所对应的value。
如果是JDK8,使用Map.forEach方法。
举例:
public static void main(String[] args) {
Map<String, String> map = new HashMap();
map.put("name", "小明");
map.put("age", "18");
// entrySet()
for (Map.Entry<String, String> entry : map.entrySet()) {
System.out.println("(" + entry.getKey() + ", " + entry.getValue() + ")");
}
// JDK 8
map.forEach((key, value) -> System.out.println("(" + key + ", " + value + ")"));
}
原文:(p34)
高度注意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 线程不安全
原文:(p37)
SimpleDateFormat 是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
举例:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialVlue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
说明:
如果是JDK8 的应用,可以进行如下替换使用:
- Date -> Instant
- Calendar -> LocalDateTime
- SimpleDateFormat -> DateTimeFormatter
原文:(p43)
当switch括号内的变量类型为String并且此变量为外部参数时,必须先进行null判断,否则会抛NPE。
举例:
private static void method(String s) {
if (Objects.nonNull(s)) {
// 为null会抛NullPointerException
switch (s) {
case "a":
System.out.println(111);
break;
default:
System.out.println(222);
}
}
}
原文:(p50)
谨慎注释掉代码,要在上方详细说明,若不是简单地注释掉。如果无用,则删除。
说明:
代码被注释掉有两种可能性。
- 后续会恢复此段代码逻辑;
- 永久不用。
前者如果没有备注信息,难以知晓注释动机。后者可解删掉即可。
原文:(p52)
在使用正则表达式时,利用好其预编译功能,可以有效加快正则表达式匹配速度。
说明:
不要在方法体内定义Pattern:
Pattern pattern = Pattern.compile("规则");
原文:(p52)
避免用Apache BeanUtils进行属性的copy。
说明:
Apache BeanUtils 性能较差,可以使用其他方案比如是Spring BeanUtils、Cglib BeanCopier,注意均是浅克隆。
原文:(p53)
注意 Math.random() 这个方法返回的是 double类型,取值的范围是 0≤x<1(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt()或者nextLong()方法。
举例:
public static void main(String[] args) {
// 随机生成[0, 10)之间的整数
System.out.println(new Random().nextInt(10));
// 方法调用返回下一个从这个伪随机数生成器的序列中均匀分布的long值。
// 18~20位
System.out.println(Long.toString(l).length() + ", " + l);
}
原文:(p62)
应用中不可直接使用日志系统(Log4j、Logback)中的API,而应依赖使用日志框架 SLF4J 中的API。使用门面模式的日志架构,有利于维护和各个类的日志处理方式统一。
举例:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger logger = LoggerFactory.getLogger(Test.calss);
原文:(p64)
在生产环境(线上)中禁止直接使用 System.out 或 System.err 输出日志,或使用 e.printStackTrace()打印异常堆栈。
说明:
每次Jboss(一个基于Java的应用服务器程序)重启时标准日志输出文件与标准错误输出文件才滚动,如果大量输出送往这两个文件,容易造成文件大小超过操作系统大小限制。
原文:(p63)
在日志输出时,字符串变量之间的拼接使用占位符的方式。
说明:
因为String字符串的拼接会使用StringBuilder的append()方法,有一定的幸能损耗。使用占位符仅是替换动作,可以有效提升性能。
举例:
logger.debug("Processing trade with id: {} and symbol: {}", id, symbole);
原文:(p64)
异常信息应该包括两类:案发现场信息和异常堆栈信息。如果不处理,那么通过关键字 throws 往上抛出。
举例:
logger.error("{}_{}", 各类参数或对象.toString(), e.getMessage(), e);
原文:(p79)
表名不适用名词复数。
索引命名。
- 主键索引 -> pk_字段名
- 唯一索引 -> uk_字段名
- 普通索引 -> idx_字段名
小数类型为decimal,禁止使用float和double。
如果存储的字符串长度几乎相等,应该使用char定长字符串类型。
varchar是可变长字符串,不预先分配存储空间,长度不要超过5000个字符。如果存储长度大于此值,则应定义字段类型为text,独立出来一张表,用主键来对应,避免影响其他字段的索引效率。
表必备三个字段:id,create_time,update_time。
表的命名最好遵循”业务名称_表的作用“原则。
当表单行数超过500万行或者单表容量超过 2GB 时,才推荐进行分库分表。
(如果预计三年后的数据量无法达到这个级别,请不要在创建表时就分库分表)超过三个表禁止 join。需要join的字段,数据类型必须绝对一致;当多表关联查询时,保证被关联的字段需要有索引。