好习惯助你编写高质量 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 整除的数是奇数

    如下代码,先后使用多个数字判断奇偶 120-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 性能的基本方法

  1. 不要再循环条件中计算

    // 在条件中计算每循环一遍就要计算一次
    while (i < count*2)
      // doSomething
    
    // 应该替换为下面这种
    int total = count*2;
    while (i < total) 
      // doSomething
    
    
  2. 缩小变量的作用范围

    定义变量应该尽可能缩小变量作用域,可以加快 GC 的回收

  3. 频繁字符串操作使用 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
    

    垃圾回收策略:

    1. UseSerialGC:使用串行 GC(默认值)
    2. ScavengeBeforeFullGC:新生代优先于 FullGC 执行
    3. UseCon从MarkSweepGC:老年代使用并发标记交换算法进行 GC

以上是关于好习惯助你编写高质量 Java 代码的主要内容,如果未能解决你的问题,请参考以下文章

有没有办法优化此代码以查找数字的除数?

一个数的除数列表

PC逆向之代码还原技术,第六讲汇编中除法代码还原以及原理第二讲,被除数是正数 除数非2的幂

HTML学习 JavaScript(运算符)

如何在循环 discord.js 中减去和除数

JAVA自除数——力扣每日一题(2022.03.31)