好习惯助你编写高质量 Java 代码
Posted 一起来搬砖呀
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了好习惯助你编写高质量 Java 代码相关的知识,希望对你有一定的参考价值。
参考:《编写高质量代码改善 Java 程序的 151 个建议》
1、不要在常量和变量中出现易混淆的字母
- 包名全小写,类名首字母大写,常量全部大写并使用下划线进行分割,变量采用驼峰命名法等,这些都是最基本的 Java 编码规范,
- 字母 l 还有大写字母 O 尽量不要与数字混用,避免理解偏差,如果必须混合使用,字母 l 最好大写 L,字母 O 增加注释,字母 l 作为长整型时必须大写,使用
1L
而不是1l
2、不要让常量蜕变为变量
-
常量在编译期间就必须确定值,要保证值在运行期间不变。例如下面代码
interface Const public static final int RAND_CONST = new Random().nextInt();
3、三元操作符的类型一致
-
三元运算符是 if-else 的简化写法,避免下面代码导致的类型转换
public static void main(String[]args) int i = 80; String a = String.valueOf(i < 100 ? 90 : 100); String b = String.valueOf(i < 100 ? 90 : 100.0); System.out.println(a.equals(b));
4、视情况使用静态导入
- 静态导入语法(import static)目的是为了减少字符输入量,提高代码阅读性,但如果滥用静态导入会使程序难以阅读和维护。缺少了类名的修饰,是哪一个类的属性、方法都要思考一下。所以对于静态导入,要遵循两个规则:
不使用 *,除非是导入静态常量类(只包含常量的类或者接口)
、方法名是有明确,清晰表象的工具类
5、显示声明 UID
我们实现 Serializable 接口需要增加 Serial Version ID,类实现 Serializable 是为了可持久化,通过 SerialVersionUID,也叫做流标识符,即类的版本定义,JVM 在反序列化时,会比较数据流中的 serialVersionUID 与 类中的 SerialVersionUID 是否相同,如果不同会抛出异常 InvalidClassException。
- 比如网络传输或者本地存储,JVM 是根据 SerialVersionUID 来判断一个类的版本的,通过判断 SerialVersionUID 可以避免对象的不一致,反序列化实现了版本的向上兼容
6、用偶判断,不用奇数判断
-
判断一个数是奇数还是偶数,能被 2 整除的数是偶数,不能被 2 整除的数是奇数
如下代码,先后使用多个数字判断奇偶 1、2、0、-1、-2 String str = num % 2 == 1 ? "奇数" : "偶数"; 1 -> 奇数 2 -> 偶数 0 -> 偶数 -1 -> 偶数 -2 -> 偶数
Java 处理取余计算代码如下:
// 模拟取余计算,dividend 被除数,divisor 为除数 public static int remainder (int divdend, int divisor) return dividend - dividend / divisor * divisor; // 在输入 -1 的时候,运算结果为 -1,所以被判定为偶数,我们应该判断是否是偶数 String str = num % 2 == 0 ? "奇数" : "偶数";
7、包装类型要做 null 值校验
- 包装对象和拆箱对象可以自由转换,但是 null 值并不能转化为基本类型,在包装类型参与运算时,我们需要做 null 值校验
8、避免在构造函数中初始化其他类
-
构造函数是一个类初始化必须执行的代码,决定了类的初始化效率,如果构造函数比较复杂,而且还关联了其他类,可能产生意想不到的效果
class Father Father() new Other(); class Son extends Father public void doSomething() System.out.println("Hello world"); class Other public Other() new Son() public static void main (String[] args) Son s = new Son(); s.doSomething();
上面这段代码如果运行会报出 StackOverflowError 的异常,因为在声明变量 s 的时候,调用 Son 的无参构造函数,JVM 默认调用了父类 Father 的无参构造函数,接着 Father 类又初始化了 Other 类,Other 类又调用了 Son 类,一个死循环就诞生了,直到栈内存消耗完为止。
9、asList 产生的 List 对象不可更改
- Arrays.asList() 返回的不是 java.util.ArrayList,而是 Arrays 工具类的一个私有的静态内部类,父类是 AbstractList,Arrays 的内部类 ArrayList 只实现了父类的 5 个方法:size、toArray、get、set、contains。asList 返回的是一个长度不可变的列表,数组多长,转换的列表是多长
10、反射访问属性或者方法时将 Accessible 设置为 true
-
反射执行一个方法,先根据 isAccessible 返回值确定能否执行,返回值为 false 则需要调用 setAccessible(true) ,最后调用 invoke 执行方法,代码如下:
Method method = ......; if (!method.isAccessible()) method.setAccessable(true); method.invoke(obj, args);
通过反射执行方法时,必须在 invoke 前检查 Accessible 属性,这是一个好习惯,但是方法对象的 Accessible 属性并不是用来决定是否可以访问的
Accessible 属性其实并不是我们理解的访问权限,而是值是否更容易获得,是否进行安全检查。动态修改一个类或方法或者执行方法时都会受到 Java 安全体系制约,Accessible 可以由我们来决定是否要避过安全体系的检查。
Accessible 属性只是用来判断是否需要进行安全检查的,如果不需要就直接执行,可以大幅度提升系统性能。由于取消了安全检查,也可以运行 private 方法、访问 private 私有属性了
11、volatile 不能保证数据同步
每个线程都运行在栈内存中,每个线程都有自己的工作内存(Working Memory,比如寄存器 Register、高速缓冲存储器 Cache 等),线程的计算一般是通过工作内存进行交互的
线程初始化加载变量到工作内存中,线程运行中读取直接从工作内存中读取,写入先写入到工作内存再刷新到主内存。多线程可能会出现不同线程持有的公共变量不同步
- 变量加上 volatile 关键字后,可以确保每个线程对本地变量的访问和修改都是与主内存交互,而不是与本线程的工作内存交互的
- volatile 关键字只能保证线程获取变量可以获取最新的值,并不能保证线程安全,数据是同步的
12、使用 CountDownLatch 协调子线程
- CountDownLatch 是一个倒数的计数器,我们可以设置一个计数,在每个线程运行完后执行 countDown 将计数器减1,所有线程全部结束后计数器为0,将子线程的结果组合起来返回
13、提升 Java 性能的基本方法
-
不要再循环条件中计算
// 在条件中计算每循环一遍就要计算一次 while (i < count*2) // doSomething // 应该替换为下面这种 int total = count*2; while (i < total) // doSomething
-
缩小变量的作用范围
定义变量应该尽可能缩小变量作用域,可以加快 GC 的回收
-
频繁字符串操作使用 StringBuilder 或者 StringBuffer
频繁对 String 进行操作可能生成多个 String 对象,如果有大量追加操作使用 StringBuilder 或者 StringBuffer 性能会好很多
14、调整 JVM 参数以提升性能
性能提升不能全靠加机器,我们写的 Java 程序都在 JVM 中运行,程序代码如果优化好了,感觉性能还是比较低的话,还可以进行 JVM 的优化。JVM 优化同时也要兼顾系统稳定性
-
调整堆内存大小
JVM 中有堆内存和栈内存,栈由线程开辟,线程结束就会回收,所以栈内存的大小一般不会对性能有太大影响,但会影响系统稳定性,如果超过栈内存容量会抛出 StackOverflowError 异常,可以通过 java -Xss 来设置栈内存大小来解决此类问题
堆内存不能随意调整,一般对象都会在堆中创建、使用、销毁,堆内存大小会影响到系统性能。设置最大内存使用 -Xmx 设置最小内存使用 -Xms,单位都是 m
-
调整堆内存中各分区比例
一般情况下,新生代和老年代的比例在 1:3 设置命令如下
java -XX:NewSize=32m -XX:MaxNewSize=640m -XX:MaxPermSize=1280m -XX:NewRatio=5
-
变更 GC 的垃圾回收策略
使用并行垃圾回收、定义回收的线程数量命令:
java -XX: +UseParallelGC -XX: ParallelGCThreads=20
垃圾回收策略:
- UseSerialGC:使用串行 GC(默认值)
- ScavengeBeforeFullGC:新生代优先于 FullGC 执行
- UseCon从MarkSweepGC:老年代使用并发标记交换算法进行 GC
以上是关于好习惯助你编写高质量 Java 代码的主要内容,如果未能解决你的问题,请参考以下文章