参考:《编写高质量代码改善 Java 程序的 151 个建议》
1L 而不是 1l常量在编译期间就必须确定值,要保证值在运行期间不变。例如下面代码
interface Const {
public static final int RAND_CONST = new Random().nextInt();
}
三元运算符是 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));
}
不使用 *,除非是导入静态常量类(只包含常量的类或者接口)、方法名是有明确,清晰表象的工具类我们实现 Serializable 接口需要增加 Serial Version ID,类实现 Serializable 是为了可持久化,通过 SerialVersionUID,也叫做流标识符,即类的版本定义,JVM 在反序列化时,会比较数据流中的 serialVersionUID 与 类中的 SerialVersionUID 是否相同,如果不同会抛出异常 InvalidClassException。
判断一个数是奇数还是偶数,能被 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 ? "奇数" : "偶数";
构造函数是一个类初始化必须执行的代码,决定了类的初始化效率,如果构造函数比较复杂,而且还关联了其他类,可能产生意想不到的效果
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 类,一个死循环就诞生了,直到栈内存消耗完为止。
反射执行一个方法,先根据 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 私有属性了
每个线程都运行在栈内存中,每个线程都有自己的工作内存(Working Memory,比如寄存器 Register、高速缓冲存储器 Cache 等),线程的计算一般是通过工作内存进行交互的
线程初始化加载变量到工作内存中,线程运行中读取直接从工作内存中读取,写入先写入到工作内存再刷新到主内存。多线程可能会出现不同线程持有的公共变量不同步
不要再循环条件中计算
// 在条件中计算每循环一遍就要计算一次
while (i < count*2){
// doSomething
}
// 应该替换为下面这种
int total = count*2;
while (i < total) {
// doSomething
}
缩小变量的作用范围
定义变量应该尽可能缩小变量作用域,可以加快 GC 的回收
频繁字符串操作使用 StringBuilder 或者 StringBuffer
频繁对 String 进行操作可能生成多个 String 对象,如果有大量追加操作使用 StringBuilder 或者 StringBuffer 性能会好很多
性能提升不能全靠加机器,我们写的 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
垃圾回收策略: