Java开发手册之编程规约

时隔一年多,再次开始更新博客,各位粉丝们久等了。大家是不是以为我像大多数开发者一样三分钟热度,坚持了一年半载就放弃了,其实不是。在过去的一年时间我学习了《Java编程思想》这本书,因为都是写基础性的东西,所以没必要在博客上写出来。现在终于把700多页的编程思想看完了,回归博客,开始整理一波Java开发手册。

1.类名使用 UpperCamelCase 风格,但以下情形例外:DO / BO / DTO / VO / AO /PO / UID 等

2.方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格。

3.常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚,不要嫌名字长。

4.抽象类命名使用 Abstract 或 Base 开头;异常类命名使用 Exception 结尾;测试类命名以它要测试的类的名称开始,以 Test 结尾。

5.类型与中括号紧挨相连来表示数组。

6.POJO 类中的任何布尔类型的变量,都不要加 is 前缀,否则部分框架解析会引起序列化错误。比如,一个属性的名字定义为 boolean isChinese(是否中国人),框架为其生成的get方法名字你觉得是什么?是isChinese(),而不是isIsChinese(),这样就导致,在某些框架反射解析的时候,遇到isChinese()方法,他会觉得,有一个变量叫做chinese,而isChinese()是该变量对应的get方法。

7.避免在子父类的成员变量之间、或者不同代码块的局部变量之间采用完全相同的命名,使可读性降低。这样写,Java是支持的,因为子类继承后的覆盖嘛,还有变量的生命周期以最近的代码块为准。但是这样写,在项目中,别人看你的代码,很容易看的晕头转向。

8.在 long 或者 Long 赋值时,数值后使用大写的 L,不能是小写的 l,小写容易跟数字混淆,造成误解。其实不用L作为后缀也默认是long型数据,不过为了规范还是要标明。

9.不要使用一个常量类维护所有常量,要按常量功能进行归类,分开维护。

10.如果是大括号内为空,则简洁地写成{}即可,大括号中间无需换行和空格;

11.避免通过一个类的对象引用访问此类的静态变量或静态方法,无谓增加编译器解析成本,直接用类名来访问即可。

12.接口过时必须加@Deprecated 注解,并清晰地说明采用的新接口或者新服务是什么。
13.推荐使用java.util.Objects类中的equals(JDK7 引入的工具类)来判断两个对象是否相等。
14.对于Integer类型的数据比较是否相等。结论:在-128到127之间直接用==进行比较,结果正确,超出此范围,则结果不正确(int 无此限制)。原因:首先我们应该好奇的是,为什么超出范围之后比较结果就不正确还是为什么在范围内的比较结果正确?其实,Integer是一个类,哪怕他是基本数据类型对应的封装类,那他也是一个类,一个类的两个对象用==进行比较,实际上比较的不是他的值,而是他的内存地址。Integer a = 128,Integer b =128,你用a==b,不是比较128和128之间是否相等,而是比较a对象和b对象是否相等,实际是比较a对象的地址和b对象的地址是否相等,打印出来就是@123那一串的比较。所以结果肯定是错的。那为什么在-128到127之间的Integer对象比较结果又正确呢?这里涉及到Integer类的缓存机制。为了提高效率,对Integer对象进行赋值的时候,假如值在范围之外,Java是new了一个Integer对象,并将值放进去,这是一个全新的对象,所以在范围之外的Integer对象用==比较都是比较两个新new的Integer对象的地址。而对于在范围之内的Integer对象,比如Integer a=1,Java并没有new一个Integer对象,Java对-128到127之内的所有值都自动全部在加载的时候就已经生成了其对应的Integer对象,当你在代码中使用Integer a =1的时候,实际上是取出在缓存中值为1的那个Integer对象拿来用。所以Integer a=1,和Integer b =1,并不是新new了两个Integer对象,各自赋值为1,而是都用的是同一个已经在缓存中的Integer对象,所以对于这两个对象的比较,其实是同一个对象的比较,因此结果是true
15.对于浮点数的比较要特别注意。基本数据类型float,不能用==比较,封装类型Float不能用equals比较。原因:float,浮点,顾名思义浮点数采用“尾数+阶码”的编码方式,类似于科学计数法的“有效数字+指数”的表示方式。二进制无法精确表示大部分的十进制小数,所以对于浮点数的比较,要设置一个允许的范围,只要在范围内,就可以算作相等。完全相等是不太可能的。或者将浮点数转换为bigdecimal类型,然后再做运算与比较。
16.禁止使用构造方法 BigDecimal(double)的方式把 double 值转化为 BigDecimal 对象。BigDecimal(double)存在精度损失风险,在精确计算或值比较的场景中可能会导致业务逻辑异常。优先推荐入参为 String 的构造方法,或使用 BigDecimal 的 valueOf 方法,此方法内部其实执行了Double 的 toString,而 Double 的 toString 按 double 的实际能表达的精度对尾数进行了截断。
17.所有的 POJO 类属性必须使用包装数据类型。RPC 方法的返回值和参数必须使用包装数据类型。原因:基本数据类型会有默认值,而封装类不会。在某些业务场景中,值为null代表了一些含义(比如程序返回异常,存储异常等),假如使用基本数据类型,则自动赋值为默认值,这样使用者不知道该值是真的实际生产中的值,还是由于实际数据没查出来,而默认的值。而使用封装类就不会出现这种情况。还有,对于数据库中查出来的值是可能出现null的,对null值进行自动拆箱转换为基本数据类型时会报空指针异常。
18.深坑:“a,b,c,,,”.split(","),你们觉得这个split的结果数组的length是多少?我一直以为,数组长度是逗号个数加一,实际上!数组长度只有3!也就是说,如果你的字符串末尾有一段是空的,即便有逗号作为分隔符,最后的数组是直接给忽略了的。
19.对于字符串拼接的坑。相信大家看过面试题的都知道字符串拼接的坑。但是大家了解的可能不是很详细。咱们一步一步分析一下字符串拼接的问题。String s = "aaa",s=s+"nnn"。相比于StringBuiler的append方法,为什么大家都说直接用+对string对象进行拼接效率不高呢?可能有人会说,在使用+拼接字符串的时候,编译器会不断地new新的string对象,然后抛弃旧的string对象,最后完成拼接,所以效率慢。这样说是错的!可能在Java的历史中曾经这样处理过,但是在1.5的时候编译器早都不这样处理了,可能更早。编译器会自动将string对象转换为stringbuilder对象,然后调用append方法,最后调用tostring方法返回。那么到这里有人问了,那为什么有人说用+号拼接字符串效率低呢?编译器已经自动为我们做优化了啊!其实,确实编译器自动优化了。但是在某一个特殊情况下,编译器优化的不够彻底。比如一个字符串在循环体中进行拼接。编译器实际做法是,每一次循环都会生成一个Stringbuilder对象,然后调用append方法拼接,在循环结束的时候,回收该stringbuilder对象,在下一次循环开始的时候,重新生成另一个StringBuilder对象,这样就造成了资源浪费。
20.获取当前时间的毫秒数,你会怎么办?有同学说,简单!new Date().getTime(),呵呵,这样做倒是可以,但是还有更简便的方法System.currentTimeMillis();这样不仅写法简单,而且不用生成新的对象,直接使用的 时System对象。
21.在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要使用含有参数类型为 BinaryOperator,参数名为 mergeFunction 的方法,否则当出现相同 key值时会抛出 IllegalStateException 异常。
22.在使用 java.util.stream.Collectors 类的 toMap()方法转为 Map 集合时,一定要注意当 value 为 null 时会抛 NPE 异常。
23.ArrayList 的 subList 结果不可强转成 ArrayList,否则会抛出 ClassCastException 异常。说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList 而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。
24.Collections 类返回的对象,如:emptyList()/singletonList()等都是 immutable list,不可对其进行添加或者删除元素的操作。
25.在 subList 场景中,高度注意对父集合元素的增加或删除,均会导致子列表的遍历、增加、删除产生 ConcurrentModificationException 异常。这是因为subList所产生的是在原List的基础上的视图,当原list改变之后视图就可以说是报废了。
26.使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。说明:如果使用另一个不带参数的toArray,其返回值为Object数组,因为你没给人家参数,人家也不知道类型,只能生成Object数组。而数组臭名昭著的一点就是不能进行强转。具体原因就不说了,算了还是提一嘴吧。类型转换,分为父类型转换为子类型,或者子类型转换为父类型。子类型对象可以直接赋值给父类型,因为子类就是父类的特殊情况,比如男人(子类)是人(父类)。但是当父类引用(指向父类的对象的引用)转换为子类引用的时候,就需要强制转换。能不能转换成功,需要看这个引用指向的真正底层对象是父类的还是子类的。如果真正指向的是子类的,那么转换就可以成功。而不带参数的toArray其底层直接就是new的一个Object数组,想要将其强转为子类型当然是行不通的。单数如果底层实际是一个子类型(比如String)类型的数组,只不过方法返回的时候当作Object数组返回,这个时候再强转为String数组,是可以成功的。
27.线程池不允许使用Executors创建,要使用ThreadPoolExecutor创建,因为前者在创建线程池的过程中有如下弊端:或允许的请求队列过长,或允许创建的线程数量过多。
28.SimpleDateFormat类是线程不安全的类,使用时需要加锁。
29.多线程处理定时任务时Timer运行多个TimerTask时,只要其中一个task抛出异常,其他所有的task都会停止运行,ScheduledExecutorService则不会有这个问题。
30.在高并发的场景中,避免使用==0作为判断逻辑终止的条件,因为如果并发处理不当,可能会直接从1跳过判断条件直接到-1。
31.三目运算符一定要注意空指针异常的问题。说明:在三目运算符中if(true)?a:b;如果a和b有一个时基本数据类型,一个是封装类,则会先将封装类拆箱为基本数据类型再计算。假如一个是范围较小的Integer一个是范围较大的Float,会执行拆箱操作,将Integer转换为int再升级为float再计算,所以在拆箱过程中容易出现空指针异常。
32.获取随机数的两种方法。第一种Math.random()注意该方法返回的是double类型的数据,范围是0(包含)到1(不包含),所以处理的时候要注意,可能出现为0 的结果。第二种就是正经的随机数类Random,方法多,好用。如果要取随机整数,直接用Random类就好,不要使用Math类生成double再去乘以10或100.
原文地址:https://www.cnblogs.com/xiaoao/p/12874654.html