1、慎用异常
异常对性能不利。抛出异常首先要创建一个新的对象。Throwable接口的构造函数调用名为fillInStackTrace()的本地方法,fillInStackTrace()方法检查堆栈,收集调用跟踪信息。只要有异常被抛出,VM就必须调整调用堆栈,因为在处理过程中创建了一个新的对象。异常只能用于错误处理,不应该用来控制程序流程。
2、不要重复初始化变量
默认情况下,调用类的构造函数时,Java会把变量初始化成确定的值:所有的对象被设置成null,整数变量(byte、short、int、long)设置成0,float和double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键词创建一个对象时,构造函数链中的所有构造函数都会被自动调用。
3、尽量指定类的final修饰符
带有final修饰符的类是不可派生的。在Java核心API中,有许多应用final的例子,例如java.lang.String。为String类指定final防止了人们覆盖length()方法。另外,如果指定一个类为final,则该类所有的方法都是final。Java编译器会寻找机会内联所有的final方法(这和具体的编译器实现有关)。此举能够使性能平均提高50%。
4、尽量使用局部变量
调用方法时传递的参数以及在调用中创建的临时变量都保存在栈中,速度较快。其他变量,如静态变量、实例变量等,都在堆中创建,速度较慢。另外,依赖于具体的编译器,局部变量还可能得到进一步优化。
5、使用移位操作代替乘法和除法
符号性质的乘除是一个很“昂贵”的操作,使用移位操作将会更快更有效
考虑下面的代码:
for (val = 0; val<100000; val+=5) { alterX = val * 8; myResult = val * 2; }
用移位操作替代乘法操作可以极大地提高性能。下面是修改后的代码:
for (val = 0; val <100000; val+= 5) { alterX = val <<3; myResult = val <<1; }
修改后的代码不再做乘以8的操作,而是改用等价的左移3位操作,每左移1位相当于乘以2。相应地,右移1位操作相当于除以2。值得一提的是,虽然移位操作速度快,但可能使代码比较难于理解,所以最好加上一些注释。
但是使用移位操作这个方法是一把双刃剑,除非是在一个非常大的循环内,性能非常重要,而且你很清楚你自己在做什么,使用这种方法比较合算。否则提高性能所带来的程序晚读性的降低将是不合算的。
6、避免在循环条件中使用复杂表达式
在不做编译优化的情况下,在循环中,循环条件会被反复计算,如果不使用复杂表达式,而使循环条件值不变的话,程序将会运行的更快。
import java.util.Vector; class CEL { void method(Vector vector) { for (int i = 0; i < vector.size(); i++) ; // 执行操作 } }
更正:
class CEL_fixed { void method (Vector vector) { int size = vector.size(); for (int i = 0; i < size; i++) ; // 执行操作 } }
7、使用finally释放资源
程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。
8、设置访问实例内变量的getter/setter方法为final
class MAF { public void setSize(int size) { _size = size; } private int _size; }
更正:
class MAF { final public void setSize(int size) { _size = size; } private int _size; }
9、避免不需要的instanceof操作
如果左边的对象的静态类型等于右边的,instanceof表达式返回永远为true。所以也根本不需要做判断。
public class UISO { public UISO(){} } class Dog extends UISO { void method(Dog dog, UISO u) { Dog d = dog; if (d instanceof UISO) // always true. System.out.println("Dog is a UISO"); UISO uiso = u; if (uiso instanceof Object) // always true. System.out.println("uiso is an Object"); } }
10、少用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。
在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:
public static CreditgetNewCredit() { return new Credit(); }
改进后:
private static Credit BaseCredit = new Credit(); public static CreditgetNewCredit() { return (Credit) BaseCredit.clone(); }
11、避免不需要的强转操作
所有的类都是直接或者间接继承自Object。同样,所有的子类也都隐含的“等于”其父类。那么,由子类造型至父类的操作就是不必要的了。
12、不要在循环中调用synchronized(同步)方法
方法的同步需要消耗相当大的资料,在一个循环中调用它绝对不是一个好主意。
public class SYN { public synchronized void method(Object o) { } private void test() { for (int i = 0; i < vector.size(); i++) { method(vector.elementAt(i)); // violation } } private Vector vector = new Vector(5, 5); }
更正:
public class SYN { public void method(Object o) { } private void test () { //在一个同步块中执行非同步方法 synchronized (vector) { for (int i = 0; i < vector.size(); i++) { method (vector.elementAt(i)); } } } private Vector vector = new Vector(5, 5); }
13、将try/catch块移出循环
把try/catch块放入循环体内,会极大的影响性能,性能会有明显的下降。
import java.io.FileInputStream; public class TRY { void method(FileInputStream fis) { for (int i = 0; i < size; i++) { try { // violation _sum += fis.read(); } catch (Exception e) { } } } private int _sum; }
更正:
void method(FileInputStream fis) { try { // violation for (int i = 0; i < size; i++) { _sum += fis.read(); } } catch (Exception e) { } }
14、对于常量字符串,用String代替StringBuffer
常量字符串并不需要动态改变长度。把StringBuffer换成String,如果确定这个String不会再变的话,这将会减少运行开销提高性能。
public class USC { String method() { StringBuffer s = new StringBuffer("Hello"); String t = s + "World!"; return t; } }
15、不要在循环体中实例化变量
在循环体中实例化临时变量将会增加内存消耗
import java.util.Vector; public class LOOP { void method(Vector v) { for (int i = 0; i < v.size(); i++) { Object o = new Object(); o = v.elementAt(i); } } }
更正:
import java.util.Vector; public class LOOP { void method(Vector v) { Object o; for (int i = 0; i < v.size(); i++) { o = v.elementAt(i); } } }
16、确定StringBuffer的容量
StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。
public class RSBC { void method() { StringBuffer buffer = new StringBuffer(); // violation buffer.append("hello"); } }
更正:
public class RSBC { void method() { StringBuffer buffer = new StringBuffer(MAX); buffer.append("hello"); } private final int MAX = 100; }
以上对一些经常出现的对效率有所影响Java的优化建议进行了详细的描述,其实在实践过程中仍然有许多大家需要注意和优化的地方,譬如:使用System.arraycopy()代替通过来循环复制数组;为Vectors和Hashtables定义初始大小;查找单个字符的话,用charAt()代替startsWith();对于boolean值,避免不必要的等式判断等等,这些都能或多或少的提高效率。对于未涉及的Java优化的最佳实践,在实际项目组编写Java的过程中,通过跟踪程序,精确到平台程序的具体方法,从执行效率和算法逻辑两方面进行优化。