好习惯助你编写高质量 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一起来搬砖呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值