JAVA编程规约之我不知系列
JAVA编程规约之我不知系列 命名风格
- 接口类中的方法和属性不要家人和修饰符号(public也不要加),保持代码的简洁性,并加上有效的Javadoc注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
- get、list、count、save/insert、remove、update作为前缀。
- 数据对象DO、数据传输对象DTO、展示对象VO。
- 不允许魔法值(即未经定义的常量)直接出现在代码中。
- long和Long初始赋值时候,使用大写的L。
- 缓存常量CacheConsts,系统配置常量ConfigConsts。
- 如果是大括号内为空,则简洁地写成{}即可。
- if/for/while/switch/do等保留字与括号之间都必须加空格。
- 任何二目、三目运算符的左右两边都需要加上一个空格。(=、&&、+…)
- 采用4个空格缩进,禁止使用tab字符。IDEA设置tab未4个空格时,请勿勾选Use tab character;而在eclipse中,必须勾选insert space for tabs。
- 注释的双斜线与注释内容之间有且仅有一个空格。
- 单行字符数限制不超过120个,超出需要换行,换行时遵循如下原则:
1). 第二行相对第一行缩进4个空格,从第三行开始,不再继续缩进。
2). 运算符与下文一起换行。
3). 方法调用的点符号与下文一起换行。
4). 方法调用时,多个参数,需要换行时,在逗号后进行。
5). 在括号前不要换行。 - 方法体内的执行语句组、变量的定义语句组、不同的业务逻辑之间或者不同的语义之间插入一个空行。相同业务逻辑和语义之间不需要插入空行。
- 避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。
- 所有的复写方法,必须加@override注解。
- 相同参数类型,相同业务含义,才可以使用java的可变参数,避免使用Object。(提倡同学们精良不用可变参数编程)
- 所有的相同类型的包装类对象之间值的比较,全部使用equals方法比较。
- 关于基本数据类型与包装数据类型的使用标准如下:
1). 所有的POJO类属性必须使用包装数据类型。
2). RPC方法的返回值和参数必须使用包装数据类型。
3). 所有的局部限量使用基本数据类型。 - 定义DO/DTO/VO等POJO类时,不要设定任何属性的默认值
- 序列化类新增属性时,请不要修改serialVersionUID字段,避免反序列失败;如果完全不兼容升级,避免反序列化混乱,那么请修改serialVersionUID值。
- 构造方法里禁止加入任何业务逻辑,如果有初始化逻辑,请放在init方法中。
- POJO类必须写toString方法。如果继承了另一个POJO类,注意加一下super.toString使用IDE的中工具直接生成。
- 使用索引访问用String的split方法得到的数组时,需做最后一个分隔符后有无内容的检查,否则会有抛IndexOutOfBoundsException的风险。
String str = "a,b,c,,";
String[] ary = str.split(",");
// 预期大于3,结果是3
System.out.println(arg.length);
- 类中方法定义的顺序是:公有方法或保护方法>私有方法>getter/setter方法。
- getter/setter方法中,不要增加业务逻辑,增加排查问题的难度。
- 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展。
- final可以声明类、成员变量、方法、以及本地变量,下列情况使用final关键字:
1). 不允许被继承的类。
2). 不允许修改应用的域对象。
3). 不允许被重写的方法。
4). 不允许运行过程中重新复制的局部变量。
5). 避免上下文重复使用一个变量,使用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。
- 关于hashCode和equals的处理,遵循如下规则:
1). 只要重写equals,就必须重写hashCode。
2). 因为Set存储的是不重复的对象,依据hashCode和equals进行判断,所以Set存储的对象必须重写这两个方法。
3). 如果自定义对象作为Map的键,那么必须重写hashCode和equals。 - ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCaseException异常。
- 在subList场景中,==高度注意==对原集合元素个数的修改,会导致子列表的遍历、增加、删除均会产生ConcurrentModificationExecption异常。
- 使用集合转数组的方法,必须使用集合的toArray(T[] array),传入的是类型完全一样的数组,大小就是list.size()。
- 使用工具类Arrays.asList()把数组转换成集合时,不能使用其修改集合相关的方法,它的add/remove/clear方法会抛出UnsupportedOperationException异常。
String[] str = new String[]{"you", "wu"};
List list = Arrays.asList(str);
list.add("wowwj");
// 运行是异常
str[0] = "gsdf";
// 那么list.get(0)也会随之修改
- 泛型通配符
new Comparator() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1;
// 没有处理相等的情况,实际使用中可能会出现异常
}
}
- 集合初始化时,指定集合初始值大小。
- 使用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 | 线程不安全 |
集合类 | 有序性 | 稳定性 |
---|---|---|
ArrayList | unsort | order |
HashMap | unsort | unorder |
TreeSet | sort | unorder |
- 获取单例对象需要保证线程安全,其中的方法也要保证线程安全。
说明:资源驱动类、工具类、单例工厂类都需要注意。 - 创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
}
- 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
- 线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
*说明: Executors返回的线程池对象的弊端如下:
1). FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2). CachedThreadPool和ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。* - SimpleDateFormat是线程不安全的类,一般不要定义为static变量,如果定义为static,必须加锁,或者使用DateUtils工具类。
// 注意线程安全,使用DateUtils。亦推荐如下处理:
private static final ThreadLocal df = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
}
说明:如果是JDB8的应用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。
6. 高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。
说明:尽可能使加锁的代码块工作量尽可能小,避免在锁代码块中调用RPC方法。
7. 对多个资源、数据库表、对象同时加锁时,需要保持一致的加锁顺序,否则可能会造成死锁。
说明:线程一需要对表A、B、C依次全部加锁后才可以进行更新操作,那么线程二的加锁顺序也必须是A、B、C,否则可能出现死锁。
8. 并发修改同一记录时,避免更新丢失,需要加锁。要么在应用层加锁,要么在缓存加锁,要么在数据库层使用乐观锁,使用version作为更新依据。
说明:如果每次访问冲突概率小于20%,推荐使用乐观锁,否则使用悲观锁。乐观锁的重试次数不得小于3次。
9. 多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
10. 使用CountDownLatch进行异步转同步操作,每个线程退出前必须调用countDown方法,线程执行代码注意catch异常,确保countDown方法被执行到,避免主线程无法执行至await方法,直到超时才返回结果。
说明:注意,子线程抛出异常堆栈,不能再主线程try-catch到。
11. 避免Random实例被多线程使用,虽然共享该实例是线程安全的,但会因竞争同一个seed导致的性能下降。
说明:Random实例包括java.util.Random的实例或者Math.random()的方式。
12. 在并发场景下,通过双重检查锁(double-checked locking)实现延迟初始化的优化问题隐患,推荐解决方案中较为简单一种(适用于JDK5及以上版本),将目标属性声明为colatile型。
class Singleton {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) synchronized(this) {
if (helper == null) {
helper = new Helper();
}
}
return helper;
}
// other methods and fields...
}
- volatile解决多线程内存不可见问题。对于一写多读,是可以解决变量同步问题,但是如果多写,同样无法解决线程安全问题。如果是count++操作,使用如下类实现:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是JDK8,推荐使用LongAdder对象,比AtomicLong性能更好(减少乐观锁的重试次数)。
- HashMap在容量不够进行resize时由于高并发可能出现死链,导致CPU飙升,在开发过程中可以使用其它数据结构或加锁来规避此风险。
- ThreadLocal无法解决共享对象的更新问题,ThreadLocal对象建议使用static修饰。这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。
- 在一个switch块中,都必须包含一个default语句并放在最后,即使它什么代码也没有。
- 表达异常的分支时,少用if-else方式,这种方式可以改写成:
if (condition) {
...
return obj;
}
// 接着写else的业务逻辑代码;
- 除常用方法外,不要在条件判断中执行其他复杂的语句,将复杂逻辑判断的结果赋值给一个有意义的布尔变量名,以提高可读性。
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
- 循环体重的语句要考量性能,以下操作尽量移至循环体外处理,如定义对象、变量、获取数据库连接,进行不必要的try-catch操作。
- 下列情形,需要进行参数校验:
1). 调用频次低的方法
2). 执行时间开销很大的方法
3). 需要极高稳定性和可用性的方法
4). 对外提供的开放接口
5). 敏感权限接口 - 下列情形,不需要进行参数校验:
1). 极有可能被循环调用的方法
2). 底层调用频度比较高的方法
3). 被声明成private只会被自己代码所调用的方法
- 在使用正则表达式时,利用好其预编译功能,可以有效加快正则匹配速度。
说明:不要在方法体内定义:Pattern pattern = Pattern.compile(规则); - 后台输送给页面的变量必须加$!{var}——中间的感叹号。
说明:如果var=null或者不存在,那么${var}会直接显示在页面上。 - 注意 Math.random() 这个方法返回是double类型,注意取值的范围0≤x<1(能够取到零值,注意除零异常),如果想获取整数类型的随机数,不要将x放大10的若干倍然后取整,直接使用Random对象的nextInt或者nextLong方法。
- 获取当前毫秒数System.currentTimeMillis(); 而不是new Date().getTime(); 说明:如果想获取更加精确的纳秒级时间值,使用System.nanoTime()的方式。在JDK8中,针对统计时间等场景,推荐使用Instant类。
- 任何数据结构的构造或初始化,都应指定大小,避免数据结构无限增长吃光内存。
- 及时清理不再使用的代码段或配置信息。
*说明:对于垃圾代码或过时配置,坚决清理干净,避免程序过度臃肿,代码冗余。
正例:对于暂时被注释掉,后续可能恢复使用的代码片断,在注释代码上方,统一规定使用三个斜杠(///)来说明注释掉代码的理由。*
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 艾略特的交易法则“遵循自然规律”
- 标签、语法规范、内联框架、超链接、CSS的编写位置、CSS语法、开发工具、块和内联、常用选择器、后代元素选择器、伪类、伪元素。
- 事件代理
- 难道你仅会钻规则的漏洞吗()
- Java|Java OpenCV图像处理之SIFT角点检测详解
- java中如何实现重建二叉树
- 数组常用方法一
- 【Hadoop踩雷】Mac下安装Hadoop3以及Java版本问题
- 今年是人生最好一年的战略规划