日常开发归纳

1、使用Hibernate validation、Spring valid 对接收的前端参数进行数据校验,减少if-else冗余代码。ps:注意异常处理。

2、阿里巴巴Java 开发手册中容易忽视的:

1)、各层命名规约:
    A)、Service/DAO 层方法命名规约
        1.1)、获取单个对象的方法用 get 做前缀。
        1.2)、获取多个对象的方法用 list 做前缀,复数形式结尾如:listObjects。
        1.3)、获取统计值的方法用 count 做前缀。
        1.4)、插入的方法用 save/insert 做前缀。
        1.5)、删除的方法用 remove/delete 做前缀。
        1.6)、修改的方法用 update 做前缀。
    B)、领域模型命名规约
        1.1)、数据对象:xxxDO,xxx 即为数据表名。
        1.2)、数据传输对象:xxxDTO,xxx 为业务领域相关的名称。
        1.3)、展示对象:xxxVO,xxx 一般为网页名称。
        1.4)、POJO 是 DO/DTO/BO/VO 的统称,禁止命名成 xxxPOJO。
2)、关于基本数据类型与包装数据类型的使用标准如下:
    2.1)、【强制】所有的 POJO 类属性必须使用包装数据类型。
    2.2)、【强制】RPC 方法的返回值和参数必须使用包装数据类型。
    2.3)、【推荐】所有的局部变量使用基本数据类型。
	说明:POJO 类属性没有初值是提醒使用者在需要使用时,必须自己显式地进行赋值,任何
NPE 问题,或者入库检查,都由使用者来保证。
	正例:数据库的查询结果可能是 null,因为自动拆箱,用基本数据类型接收有 NPE 风险。
	反例:比如显示成交总额涨跌情况,即正负 x%,x 为基本数据类型,调用的 RPC 服务,调用
不成功时,返回的是默认值,页面显示为 0%,这是不合理的,应该显示成中划线。所以包装
数据类型的 null 值,能够表示额外的信息,如:远程调用失败,异常退出。
3)、【强制】序列化类新增属性时,请不要修改 serialVersionUID 字段,避免反序列失败;如
果完全不兼容升级,避免反序列化混乱,那么请修改 serialVersionUID 值。
说明:注意 serialVersionUID 不一致会抛出序列化运行时异常。
4)、【强制】POJO 类必须写 toString 方法。使用 IDE 中的工具:source> generate toString
时,如果继承了另一个 POJO 类,注意在前面加一下 super.toString。
说明:在方法执行抛出异常时,可以直接调用 POJO 的 toString()方法打印其属性值,便于排
查问题。
5)、【强制】使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全
一样的数组,大小就是 list.size()。
	说明:使用 toArray 带参方法,入参分配的数组空间不够大时,toArray 方法内部将重新分配
内存空间,并返回新数组地址;如果数组元素个数大于实际所需,下标为[ list.size() ]
的数组元素将被置为 null,其它数组元素保持原值,因此最好将方法入参数组大小定义与集
合元素个数一致。
	正例:
        List<String> list = new ArrayList<String>(2);
        list.add("guan");
        list.add("bao");
        String[] array = new String[list.size()];
        array = list.toArray(array); 
    反例:直接使用 toArray 无参方法存在问题,此方法返回值只能是 Object[]类,若强转其它
类型数组将出现 ClassCastException 错误。
6)、【强制】使用工具类 Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方
法,它的 add/remove/clear 方法会抛出 UnsupportedOperationException 异常。
	说明:asList 的返回对象是一个 Arrays 内部类,并没有实现集合的修改方法。Arrays.asList
体现的是适配器模式,只是转换接口,后台的数据仍是数组。
    String[] str = new String[] { "you", "wu" };
    List list = Arrays.asList(str);
	第一种情况:list.add("yangguanbao"); 运行时异常。
	第二种情况:str[0] = "gujin"; 那么 list.get(0)也会随之修改。
7)、【强制】不要在 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);
            }
        }
	说明:以上代码的执行结果肯定会出乎大家的意料,那么试一下把“1”换成“2”,会是同样的
结果吗?
8)、【推荐】集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。
	说明:菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。
	正例:
		// <> diamond 方式
		HashMap<String, String> userCache = new HashMap<>(16);
        // 全省略方式
        ArrayList<User> users = new ArrayList(10); 
9)、【推荐】集合初始化时,指定集合初始值大小。
	说明:HashMap 使用 HashMap(int initialCapacity) 初始化。
	正例:initialCapacity = (需要存储的元素个数 / 负载因子) + 1。注意负载因子(即 loader
factor)默认为 0.75,如果暂时无法确定初始值大小,请设置为 16(即默认值)。
	反例:HashMap 需要放置 1024 个元素,由于没有设置容量初始大小,随着元素不断增加,容
量 7 次被迫扩大,resize 需要重建 hash 表,严重影响性能。
10)、【推荐】使用 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 值组合集合。
11)、【强制】主键索引名为 pk_字段名;唯一索引名为 uk_字段名;普通索引名则为 idx_字段名。
	说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
12)、【强制】小数类型为 decimal,禁止使用 float 和 double。
	说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
13)、【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
14)、【强制】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索
引效率。
15)、【强制】表必备三字段:id, gmt_create, gmt_modified。
	说明:其中 id 必为主键,类型为 bigint unsigned、单表时自增、步长为 1。gmt_create,gmt_modified 的类型均为 datetime 类型,前者现在时表示主动创建,后者过去分词表示被动更新。
16)、【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。
	说明:如果预计三年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
17)、【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
	说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必然有脏数据产生。
18)、【强制】超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时,保证被关联的字段需要有索引。
	说明:即使双表 join 也要注意表索引、SQL 性能。
19)、【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL 并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
	正例:先快速定位需要获取的 id 段,然后再关联:
    SELECT
		a.* 
	FROM
		表1 a,
		( SELECT id FROM 表1 WHERE 条件 LIMIT 100000, 20 ) b 
	WHERE
		a.id = b.id
20)、【强制】不要使用 count(列名)或 count(常量)来替代 count(*),count(*)是 SQL92 定义的标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
	说明:count(*)会统计值为 NULL 的行,而 count(列名)不会统计此列为 NULL 值的行。
21)、【强制】当某一列的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为NULL,因此使用 sum()时需注意 NPE 问题。
	正例:可以使用如下方式来避免 sum 的 NPE 问题:
		SELECT IF(ISNULL(SUM(g)),0,SUM(g))FROM table;
22)、【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
	说明:以学生和成绩的关系为例,学生表中的 student_id是主键,那么成绩表中的 student_id则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
23)、【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性。
24)、【强制】数据订正(特别是删除、修改记录操作)时,要先 select,避免出现误删除,确认无误才能执行更新语句。
25)、【推荐】in 操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控制在 1000 个之内。
26)、【强制】POJO 类的布尔属性不能加 is,而数据库字段必须加 is_,要求在 resultMap 中进行字段与属性之间的映射。
	说明:参见定义 POJO 类以及数据库字段定义规定,在<resultMap>中增加映射,是必须的。在 MyBatis Generator 生成的代码中,需要进行对应的修改。

3、MySQL处理特殊字符:

​ 3.1、MySQL数据库:修改my.cnf (windows为my.ini)

[client] 
default-character-set = utf8mb4 
[mysql] 
default-character-set = utf8mb4 
[mysqld] 
character-set-client-handshake = FALSE 
character-set-server = utf8mb4 
collation-server = utf8mb4_unicode_ci 
init_connect='SET NAMES utf8mb4'

​ 3.2、修改database,table,column字符集

ALTER DATABASE database_name CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
ALTER TABLE table_name CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER TABLE table_name CHANGE column_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;   

​ 3.3、修改数据库连接URL:

jdbc:mysql://127.0.0.1:3306/xxx?useunicode=true&characterEncoding=utf8

​ TO

jdbc:mysql://127.0.0.1:3306/xxx

4、配置事务AOP后,项目启动报错

Q:BeanPostProcessor before instantiation of bean failed
A:缺少AspectJ 注解
<!-- AspectJ 注解-->
<dependency>
	<groupId>org.aspectj</groupId>
	<artifactId>aspectjweaver</artifactId>
	<version>1.6.8</version>
</dependency>

5、主表、副表同时更新

ps:主表、副表必须有关联字段。
UPDATE   t1
JOIN   t2
ON   t1.t2_id   =   t2.id
SET   t1.xxx  =  '',t2.yyy  =  ''
WHERE   t1.id  =  1

6、MyBatis动态SQL:varchar类型判空

<if test="xxx != null and xxx.trim() != ''">   
 	and   xxx = #{xxx}
</if>

7、尽量减少对变量的重复计算

​ 明确一个概念,对方法的调用,即使方法中只有一句语句,也是有消耗的,包括创建栈帧、调用方法时保护现场、调用方法完毕时恢复现场等。所以例如下面的操作:

for (int i = 0; i < list.size(); i++){...}

​ 建议替换为:

for (int i = 0, int length = list.size(); i < length; i++){...}

​ 这样,在list.size()很大的时候,就减少了很多的消耗

8、慎用异常

​ 异常对性能不利。抛出异常首先要创建一个新的对象,Throwable接口的构造函数调用名为fillInStackTrace()的本地同步方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,Java虚拟机就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。

9、不要在循环中使用try…catch…,应该把其放在最外层

10、如果能估计到待添加的内容长度,为底层以数组方式实现的集合、工具类指定初始长度

​ 10.1、比如ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder为例:

​ A)、StringBuilder() // 默认分配16个字符的空间

​ B)、StringBuilder(int size) // 默认分配size个字符的空间

扫描二维码关注公众号,回复: 9930018 查看本文章

​ C)、StringBuilder(String str) // 默认分配16个字符+str.length()个字符空间

​ 可以通过类(这里指的不仅仅是上面的StringBuilder)的来设定它的初始化容量,这样可以明显地提升性能。比如StringBuilder吧,length表示当前的StringBuilder能保持的字符数量。因为当StringBuilder达到最大容量的时候,它会将自身容量增加到当前的2倍再加2,无论何时只要StringBuilder达到它的最大容量,它就不得不创建一个新的字符数组然后将旧的字符数组内容拷贝到新字符数组中—-这是十分耗费性能的一个操作。试想,如果能预估到字符数组中大概要存放5000个字符而不指定长度,最接近5000的2次幂是4096,每次扩容加的2不管,那么:

​ (1)、在4096 的基础上,再申请8194个大小的字符数组,加起来相当于一次申请了12290个大小的字符数组,如果一开始能指定5000个大小的字符数组,就节省了一倍以上的空间;

​ (2)、把原来的4096个字符拷贝到新的的字符数组中去。

​ 这样,既浪费内存空间又降低代码运行效率。所以,给底层以数组实现的集合、工具类设置一个合理的初始化容量是错不了的,这会带来立竿见影的效果。但是,注意,像HashMap这种是以数组+链表实现的集合,别把初始大小和你估计的大小设置得一样,因为一个table上只连接一个对象的可能性几乎为0。初始大小建议设置为2的N次幂,如果能估计到有2000个元素,设置成new HashMap(128)、new HashMap(256)都可以。

11、乘法和除法使用移位操作

例如:

for (val = 0; val < 100000; val += 5){a = val * 8;b = val / 2;}

用移位操作可以极大地提高性能,因为在计算机底层,对位的操作是最方便、最快的,因此建议修改为:

for (val = 0; val < 100000; val += 5){a = val << 3;b = val >> 1;}

移位操作虽然快,但是可能会使代码不太好理解,因此最好加上相应的注释。

12、循环内不要不断创建对象引用

​ 例如:

for (int i = 1; i <= count; i++){Object obj = new Object();}

​ 这种做法会导致内存中有count份Object对象引用存在,count很大的话,就耗费内存了,建议为改为:

Object obj = null;for (int i = 0; i <= count; i++) { obj = new Object(); }

​ 这样的话,内存中只有一份Object对象引用,每次new Object()的时候,Object对象引用指向不同的Object罢了,但是内存中只有一份,这样就大大节省了内存空间了。

13、尽量使用HashMap、ArrayList、StringBuilder,除非线程安全需要,否则不推荐使用Hashtable、Vector、StringBuffer,后三者由于使用同步机制而导致了性能开销。

14、不要将数组声明为public static final

​ 因为这毫无意义,这样只是定义了引用为static final,数组的内容还是可以随意改变的,将数组声明为public更是一个安全漏洞,这意味着这个数组可以被外部类所改变。

15、尽量在合适的场合使用单例

​ 使用单例可以减轻加载的负担、缩短加载的时间、提高加载的效率,但并不是所有地方都适用于单例,简单来说,单例主要适用于以下三个方面:

​ 15.1、控制资源的使用,通过线程同步来控制资源的并发访问。

​ 15.2、控制实例的产生,以达到节约资源的目的。

​ 15.3、控制数据的共享,在不建立直接关联的条件下,让多个不相关的进程或线程之间实现通信。

16、尽量避免随意使用静态变量

​ 要知道,当某个对象被定义为static的变量所引用,那么gc通常是不会回收这个对象所占有的堆内存的,如:

public class A{private static B b = new B();}

​ 此时静态变量b的生命周期与A类相同,如果A类不被卸载,那么引用B指向的B对象会常驻内存,直到程序终止。

17、迭代entrySet() 获取Map 的key 和value

​ 当循环中只需要获取Map 的主键key时,迭代keySet() 是正确的;但是,当需要主键key 和取值value 时,迭代entrySet() 才是更高效的做法,其比先迭代keySet() 后再去通过get 取值性能更佳。

反例:

//Map 获取value 反例:
HashMap<String, String> map = new HashMap<>();
for (String key : map.keySet()){
    String value = map.get(key);
}

正例:

//Map 获取key & value 正例:
HashMap<String, String> map = new HashMap<>();
for (Map.Entry<String,String> entry : map.entrySet()){
 String key = entry.getKey();
 String value = entry.getValue();
}

18、若需频繁调用Collection.contains 方法则使用Set

​ 在Java 集合类库中,List的contains 方法普遍时间复杂度为O(n),若代码中需要频繁调用contains 方法查找数据则先将集合list 转换成HashSet 实现,将O(n) 的时间复杂度将为O(1)。

反例:

//频繁调用Collection.contains() 反例
List<Object> list = new ArrayList<>();
for (int i = 0; i <= Integer.MAX_VALUE; i++){
    //时间复杂度为O(n)
    if (list.contains(i))
    System.out.println("list contains "+ i);
}

正例:

//频繁调用Collection.contains() 正例
List<Object> list = new ArrayList<>();
Set<Object> set = new HashSet<>();
for (int i = 0; i <= Integer.MAX_VALUE; i++){
    //时间复杂度为O(1)
    if (set.contains(i)){
        System.out.println("list contains "+ i);
    }
}

19、工具类中屏蔽构造函数

​ 工具类是一堆静态字段和函数的集合,其不应该被实例化;但是,Java 为每个没有明确定义构造函数的类添加了一个隐式公有构造函数,为了避免不必要的实例化,应该显式定义私有构造函数来屏蔽这个隐式公有构造函数。

反例:

public class PasswordUtils {
//工具类构造函数反例
private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);

public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";

public static String encryptPassword(String aPassword) throws IOException {
    return new PasswordUtils(aPassword).encrypt();
}
}

正例:

public class PasswordUtils {
//工具类构造函数正例
private static final Logger LOG = LoggerFactory.getLogger(PasswordUtils.class);

//定义私有构造函数来屏蔽这个隐式公有构造函数
private PasswordUtils(){}

public static final String DEFAULT_CRYPT_ALGO = "PBEWithMD5AndDES";

public static String encryptPassword(String aPassword) throws IOException {
    return new PasswordUtils(aPassword).encrypt();
}
}

20、MySQL_int(10) 是什么意思?

Mysql int(10) 中 10 指的是:该字段下能输出显示的最大数字长度。括号里的数字叫数据的宽度,不同的数据类型对宽度的处理也不一样:

​ 20.1、整数类型:这里显示的宽度和数据类型的取值范围是没有任何关系的,显示宽度只是指明Mysql最大可能显示的数字个数,数值的位数小于指定的宽度时会由空格填充;

如果插入了大于显示宽度的值,只要该值不超过该类型的取值范围,数值依然可以插入,而且能够显示出来。

如果你不设置宽度,系统将添加默认的宽度tinyint(4)、smallint(6)、mediumint(9)、int(11)、bigint(20),这些默认的宽度是跟该类型的取值范围长度相关。

​ 20.2、字符串类型:字符串类型这个宽度才真的用上了。不管是char还是varchar,宽度都定义了字符串的最大长度;例如上面的 password varchar(20),如果你输入了一个21个字符的密码,那么保存和显示的只会是前20个字符,你将丢失一个字符信息,char同理。由于varchar是变长存储的,所以实际开发中我们一般都把varchar的宽度设为最长255,它会根据实际数据长度变化,反正你没用完它也不会浪费空间。char是定长存储,定义多长就是多长。

​ 20.3、浮点和日期等数据类型:对数据的宽度没有要求,一般也不设置,默认是0。

发布了22 篇原创文章 · 获赞 321 · 访问量 8538

猜你喜欢

转载自blog.csdn.net/BUG_call110/article/details/104946298