随着CMMI体系结构的推广,对于编码方面的语言的使用和规范上也有了统一的要求。在Java的编码风格、格式、规范等,已经在《编码规范》的Java部分进行了详细的定义,同时,也包括一些对性能优化方面的建议,最后结合《编码检查单》对相应的Java的格式、规范以及性能方面进行检查。但是,《编码规范》在性能优化方面的建议虽然有却不是很多,下面将结合Java编写经验以及参考实际项目组在系统优化方面的建议针对Java的编程方面提出一些优化方面的建议。
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 {
voidmethod (Vector vector) {
for (int i = 0; i < vector.size (); i++)
; // 执行操作
}
}
更正:
class CEL_fixed {
voidmethod (Vector vector) {
int size = vector.size ()
for (int i = 0; i < size; i++)
; // 执行操作
}
}
7、使用finally释放资源
程序中使用到的资源应当被释放,以避免资源泄漏。这最好在finally块中去做。不管程序执行的结果如何,finally块总是会执行的,以确保资源的正确关闭。
例子:
import java.io.*;
public class CS {
publicstatic void main (String args[]) {
CS cs = new CS ();
cs.method ();
}
publicvoid method () {
try {
FileInputStream fis = new FileInputStream ("CS.java");
int count = 0;
while (fis.read () != -1)
count++;
System.out.println (count);
fis.close ();
} catch (FileNotFoundException e1) {
} catch (IOException e2) {
}
}
}
更正:
在最后一个catch后添加一个finally块。
8、设置访问实例内变量的getter/setter方法为final
简单的getter/setter方法应该被置成final,这会告诉编译器,这个方法不会被重载,所以,可以变成”inlined”
例子:
class MAF {
publicvoid setSize (int size) {
_size = size;
}
privateint _size;
}
更正:
class DAF_fixed {
finalpublic void setSize (int size) {
_size = size;
}
privateint _size;
}
9、避免不需要的instanceof操作
如果左边的对象的静态类型等于右边的,instanceof表达式返回永远为true。
例子:
public class UISO {
publicUISO () {}
}
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");
}
}
更正:
删掉不需要的instanceof操作。
class Dog extends UISO {
voidmethod () {
Dog d;
System.out.println ("Dog is an UISO");
System.out.println ("UISO is an UISO");
}
}
10、少用new关键词创建类的实例
用new关键词创建类的实例时,构造函数链中的所有构造函数都会被自动调用。但如果一个对象实现了Cloneable接口,我们可以调用它的clone()方法。clone()方法不会调用任何类构造函数。
在使用设计模式(Design Pattern)的场合,如果用Factory模式创建对象,则改用clone()方法创建新的对象实例非常简单。例如,下面是Factory模式的一个典型实现:
public static CreditgetNewCredit() {
return new Credit();
}
改进后的代码使用clone()方法,如下所示:
private static Credit BaseCredit =new Credit();
public static CreditgetNewCredit() {
return (Credit)BaseCredit.clone();
}
上面的思路对于数组处理同样很有用。
11、避免不需要的造型操作
所有的类都是直接或者间接继承自Object。同样,所有的子类也都隐含的“等于”其父类。那么,由子类造型至父类的操作就是不必要的了。
例子:
class UNC {
String_id = "UNC";
}
class Dog extends UNC {
voidmethod () {
Dog dog = new Dog ();
UNC animal = (UNC)dog; // not necessary.
Object o = (Object)dog; // notnecessary.
}
}
更正:
class Dog extends UNC {
voidmethod () {
Dog dog = new Dog();
UNC animal = dog;
Object o = dog;
}
}
12、不要在循环中调用synchronized(同步)方法
方法的同步需要消耗相当大的资料,在一个循环中调用它绝对不是一个好主意。
例子:
import java.util.Vector;
public class SYN {
publicsynchronized void method (Object o) {
}
privatevoid test () {
for (int i = 0; i < vector.size(); i++) {
method (vector.elementAt(i)); // violation
}
}
privateVector vector = new Vector (5, 5);
}
更正:
不要在循环体中调用同步方法,如果必须同步的话,推荐以下方式:
import java.util.Vector;
public class SYN {
publicvoid method (Object o) {
}
private void test () {
synchronized{//在一个同步块中执行非同步方法
for (int i = 0; i < vector.size(); i++) {
method (vector.elementAt(i));
}
}
}
privateVector vector = new Vector (5, 5);
}
13、将try/catch块移出循环
把try/catch块放入循环体内,会极大的影响性能,性能会有明显的下降。
例子:
import java.io.FileInputStream;
public class TRY {
voidmethod (FileInputStream fis) {
for (int i = 0; i < size; i++) {
try{ // violation
_sum += fis.read();
} catch (Exception e) {}
}
}
privateint _sum;
}
更正:
将try/catch块移出循环
void method (FileInputStream fis){
try {
for (int i = 0; i < size; i++) {
_sum += fis.read();
}
} catch (Exception e) {}
}
14、对于常量字符串,用String代替StringBuffer
常量字符串并不需要动态改变长度。
例子:
public class USC {
Stringmethod () {
StringBuffer s = new StringBuffer ("Hello");
String t = s + "World!";
return t;
}
}
更正:
把StringBuffer换成String,如果确定这个String不会再变的话,这将会减少运行开销提高性能。
15、不要在循环体中实例化变量
在循环体中实例化临时变量将会增加内存消耗
例子:
import java.util.Vector;
public class LOOP{
voidmethod (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{
voidmethod (Vector v) {
Object o;
for (int i=0;i<v.size();i++) {
o = v.elementAt(i);
}
}
}
16、确定StringBuffer的容量
StringBuffer的构造器会创建一个默认大小(通常是16)的字符数组。在使用中,如果超出这个大小,就会重新分配内存,创建一个更大的数组,并将原先的数组复制过来,再丢弃旧的数组。在大多数情况下,你可以在创建StringBuffer的时候指定大小,这样就避免了在容量不够的时候自动增长,以提高性能。
例子:
public class RSBC {
voidmethod () {
StringBuffer buffer = new StringBuffer(); // violation
buffer.append ("hello");
}
}
更正:
确定StringBuffer提供的大小。
public class RSBC {
voidmethod () {
StringBuffer buffer = new StringBuffer(MAX);
buffer.append ("hello");
}
privatefinal int MAX = 100;
}
以上对一些经常出现的对效率有所影响Java的优化建议进行了详细的描述,其实在实践过程中仍然有许多大家需要注意和优化的地方,譬如:使用System.arraycopy()代替通过来循环复制数组;为Vectors和Hashtables定义初始大小;查找单个字符的话,用charAt()代替startsWith();对于boolean值,避免不必要的等式判断等等,这些都能或多或少的提高效率。对于未涉及的Java优化的最佳实践,在实际项目组编写Java的过程中,通过跟踪程序,精确到平台程序的具体方法,从执行效率和算法逻辑两方面进行优化。以后通过项目的不断积累,会有越来越多好的方法和实践展现在大家面前。