java代码规范学习
一、编程规约
【推荐】接口类中的方法和属性不要加任何修饰符号(public 也不要加),保持代码的简洁性,并加上有效的 Javadoc 注释。尽量不要在接口里定义变量,如果一定要定义变量,肯定是与接口方法相关,并且是整个应用的基础常量。
说明:Java的interface中,接口是比抽象类更高一层的抽象,接口只是对一类事物属性和行为的更高次抽象;对修改关闭,对扩展开放,可以说是java中开闭原则《六、开闭原则》的一种体现。
接口中的方法和变量的默认修饰符分别为:
- 接口中的方法会被隐式的指定为 public abstract (只能是 public abstract,其他修饰符都会报错)。
- 接口中的变量会被隐式的指定为 public static final 变量(并且只能是 public,用 protected、private 修饰会报编译错误)
注:
- 接口是隐式抽象的,当声明一个接口的时候,接口名前面不必使用 abstract 关键字修饰。
- 接口中每一个方法也是隐式抽象的,声明时同样不需要 abstract 关键字修饰。
所以变量和方法分别有两种书写方法:
//变量: 1:public static final String name = "张三"; 2:String name = "张三"; //【推荐】 //方法: 1:public abstract List<String> getUserNames(Long companyId); 2:List<String> getUserNames(Long companyId); //【推荐】
看下下面的反例,在IDE中会报错:
【推荐】慎用 Object 的 clone 方法来拷贝对象
jdk clone 方法默认是浅拷贝,若想实现深拷贝需覆写 clone 方法实现域对象的深度遍历式拷贝 。Java中的对象拷贝,有浅拷贝和深拷贝两种,如果没有搞清楚这两者的区别,那么可能会给自己的代码埋下隐患。
【推荐】类成员与方法访问控制从严
1) 如果不允许外部直接通过 new 来创建对象,那么构造方法必须是 private。
2) 工具类不允许有 public 或 default 构造方法。
3) 类非 static 成员变量并且与子类共享,必须是 protected。
4) 类非 static 成员变量并且仅在本类使用,必须是 private。
5) 类 static 成员变量如果仅在本类使用,必须是 private。
6) 若是 static 成员变量,考虑是否为 final。
7) 类成员方法只供类内部调用,必须是 private。
8) 类成员方法只对继承类公开,那么限制为 protected。
说明:任何类、方法、参数、变量,严控访问范围。过于宽泛的访问范围,不利于模块解耦。思考:如果是一个 private 的方法,想删除就删除,可是一个 public 的 service 成员方法或成员变量,删除一下,不得手心冒点汗吗?变量像自己的小孩,尽量在自己的视线内,变量作用域太大,无限制的到处跑,那么你会担心的。
【强制】泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。
【推荐】集合泛型定义时,在 JDK7 及以上,使用 diamond 语法或全省略。
说明:菱形泛型,即 diamond,直接使用<>来指代前边已经指定的类型。
正例:
// <> diamond 方式
HashMap<String, String> userCache = new HashMap<>(16);
// 全省略方式
ArrayList<User> users = new ArrayList(10);
【强制】不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。
【推荐】使用 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 值组合集合。
【参考】合理利用好集合的有序性(sort)和稳定性(order),避免集合的无序性(unsort)和不稳定性(unorder)带来的负面影响。
有序性是指遍历结果是按照某种比较规则依次排列的。稳定性是指每次遍历的元素次序是一定的。(难道有相同的值的元素出现:遍历结果相同,遍历元素却不同?)
ArrayList order/unsort
HaspMap unorder/unsort
TreeSet order/sort
利用set 元素的唯一的特性,可以快速对一个集合进行去重操作,避免使用List的contains方法进行遍历、对比、去重操作。
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors 返回的线程池对象的弊端如下:
1)FixedThreadPool 和 SingleThreadPool:
允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
2)CachedThreadPool 和 ScheduledThreadPool:
允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
《ThreadPoolExecutor之一:使用基本介绍》《ThreadPoolExecutor之四:jdk实现的ScheduledThreadPoolExecutor》
【强制】在服务接口的定义中,参数可以使用枚举值,在返回值的DTO中禁止使用枚举值。
这条规范是微服务的交互模式中的读者容错模式在实践中的一个实例,
之所以在参数(入参)中允许使用枚举值,是因为如果服务的提供者升级了接口,增加了枚举值,若服务的消费者并没有感知,则服务的消费者得知新的枚举值可以传递新的枚举值了;
但是如果在接口的返回DTO中使用了枚举值,并且因为某种原因增加了枚举值,则服务消费者在反序列化枚举时就会报错,因此在返回值中我们应该使用字符串等相互认可的类型,来做到双方的兼容性,并实现读者容错模式。