Java 性能优化:面向对象及基础类型使用优化

性能优化是个大筐,很多东西都能往里面装。虽说性能优化的具体方面比较多,但万丈高楼从地起,这里还是从Java最基本的一些入门知识相关的使用优化进行一些做些总结和建议。如何连最基本的API使用都不会,或不会选择更高效的使用方式,那么Java的性能优化就是空谈,就更别提JVM优化 、Web优化这些更高级的性能优化知识。、

1 采用Clone()方式创建对象

Java中所有类都默认继承自java.lang.Object类,而Object类里面有一个clone()方法,JDK API的说明文档里面解释了这个方法会返回Object对象的一个拷贝。我们需要说明两点:一是拷贝对象返回的是一个新对象,而不是一个对象的引用地址;二是拷贝对象与new关键字产生的对象不同,clone出来的副本对象包含了原对象的所有信息(注意深拷贝和浅拷贝),而new出来的对象只有构造方法中的初始化信息。

当我们使用new关键字创建类的一个实例时,构造函数中的所有构造函数都会被自动调用。而如果一个对象实现了Cloneable接口,通过其方法调用其clone()方 法,可以创建一个与原对象独立的副本对象(深拷贝时),这是通过cpp控制虚拟机复制一个新的OOP对象,它不会去调用构造方法。

2 避免对boolean判断

Java里的boolean数据类型被定义为存储8位(1个字节)的数值形式,但只能是true或是false。

有些时候我们出于写代码的习惯,经常容易导致习惯性思维,这里指的习惯性思维是想要对生成的数据进行判别,这样感觉可以在该变量进入业务逻辑之前有一层检查、判定。对于大多数的数据类型来说,这是正确的做法,但是对于boolean变量,我们应该尽量避免不必要的等于判定。如果尝试去掉boolean与true的比较判断代码,大体上来说,我们会有两个好处。

■ 代码执行得更快(生成的字节码少了5个字节);

■ 代码整体显得更加干净

3 多用三元运算符

对去简单的逻辑,尽量使用三元运算符去替代"if (cond);else“

public int abs1(int a) {
return a >= 0 ? a : -a;
}
public int abs2(int a){
if(a>=0)
return a;
else
return -a;
}

这样的顺序判断结构,主要原因还是因为条件操作符更加简捷,代码看起来会少一点。

4 静态方法替代实例方法

在Java中,使用static关键字描述的方法是静态方法。与静态方法相比,实例方法的调用需要消耗更多的系统资源,这是因为实例方法需要维护一张类似C++虚拟函数导向表(java中的虚方法或接口表)的结构,这样可以方便地实现对多态的支持。对于一些常用的工具类方法,它们本身不保存状态,方法不会使用成员变量,它们是无状态的类,我们没有必要对其进行继承重写来支持多态,那么我们可以尝试将它们声明为static,即静态方法,这样有利于加速方法的调用。

5 有条件地使用final关键字

在Java中,final关键字可以被用来修饰类、方法和变量(包括成员变量和局部变 量)。在使用匿名内部类将外部变量传递到构造方法中要用到final关键字,用来修饰类时表明这个类不能被继承,而String就是这种典型的不可变类,防止被子类化造成密码(密码本身是字符串,还可变就可怕了)等安全问题。而修饰方法保证方法不会被重写,修饰变量保证不会引用新的对象(引用数据类型)或赋值(基本数据类型)。这些显式地提醒不能重载变量或重写类方法,否则编译器会报错。另外由final修饰的方法因其不能被重写了,在当前类的层次中不会在放入虚方法表中,就不存在动态绑定了,提高定位实际方法的速度;如果final的方法体代码少、逻辑简单,它会被内联到调用者中,进一步提高性能。

6 避免不需要的instanceof操作

instanceof的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据,即如果左边的对象的静态类型等于右边的,我们使用的instanceof表达式的返回值会返回true 。如“ animal instanceof Object”这种类似的代码尽量不要出现,已经显式地知道了其个对象肯定是某种类型,那么就没有必要再做这种判断。我们要少用instanceof,要使用面向对象的多态机制来保证业务逻辑,java毕竟不是面向过程的编程语言。

7.避免子类中存在父类转换

我们知道Object类是所有Java类的祖先,因此每个类都使用Object作为超类,所有对象(包括数组)都实现这个类的方法。在不明确是否提供了超类的情况下,Java会自动把Object作为被定义类的超类。

我们可以使用类型为Object的变量指向任意类型的对象。同样,所有的子类也都隐含的“等于”其父类。那么,程序代码中就没有必要再把子类对象转换为它的父类了。

class P{

}
class S extends P{
public void cast() {
S s = new S();
P p = (P) s;//没必要的转换
      //......
}
}

8 尽量使用局部变量

调用方法时传递的参数以及在调用中创建的临时变量都被保存在栈(Stack)里面, 因此读写速度较快。其他变量,例如静态变量、实例变量,它们都在堆(heap)中被创建,也被保留在那里,所以读写相对于保存在栈里面的数据来说,它的速度较慢。

Java类的成员变量有两种,一种是被static关键字修饰的变量,叫类变量或者静态变量,另一种没有static修饰,称为实例变量。在语法定义上的区别,静态变量前要加static 关键字,而实例变量前则不加。

静态变量(类变量)被所有对象共有,如果其中一个对象将它的值改变,那么其他对象得到的就是改变后的结果。实例变量属于对象私有,如果某一个对象将其值改变,也不会影响到其他对象。此外,静态变量和实例变量都属全局变量。

程序运行过程当中,实例变量属于某个对象的属性,必须创建实例对象,其中的实例变量才会被分配空间,才能使用这个实例变量。静态变量不属于某个实例对象,而是属于类,所以也称为类变量,只要程序加载了类的字节码,不用创建任何实例对象,静态变量就会被分配空间,静态变量就可以被使用了。总之,实例变量必须创建对象后才可以通过

这个对象来使用,静态变量则可以直接使用类名来引用。

除了速度之外,还要安全性的问题,静态变量和实例变量都是全局的,不只一个方法能访问使用,其他方法也可能使用到,可能会造成线程安全问题。

9 运算效率最高的方式——位运算

在Java语言中的所有运算中,位运算是最为高效的。位运算表达式由操作数和位运算符组成,实现对整数类型的二进制数进行位运算。位运算符可以分为逻辑运算符(包括~、&、|和^)及移位运算符(包括>>、<<和>>>)。因此,可以尝试使用位运算方式代替部分算术运算,来提高系统的运行速度。最典型示例的就是对于整数的乘除运算优化。

10  用一维数组代替二维数组

数组支持下标的随机访问,它能够根据相对偏移量快速定位要查找的元素。二维数组的基本原理是:二维数组的每个元素再去引用一个一维数组。

一维数组和二维数组的访问速度不一样,二维数组的访问速度要优于一维数组,但是,二维数组比一维数组占用更多的内存空间,大概是10倍左右。在性能敏感的系统中要使用二维数组,如果内存不足,尽量将二维数组转化为一维数组再进行处理,以节省内存空间。

11  布尔运算代替位运算(与/或)

虽然位运算的速度远远高于算术运算,但是在条件判断时,使用位运算替代布尔运算是非常非常错误的选择。在条件判断时,Java会对布尔运算做相当充分的优化。假设有表达式a、b、c进行布尔运算“a&&b&&c”,根据逻辑与的特点,只要在整个布尔表达式中有一项返回false,整个表达式就返回false,因此,当表达式a为false时,该表达式将立即返回false,而不会再去计算表达式b和c。若此时,表达式a、b、c需要消耗大量的系统资源,这种处理方式可以节省这些计算资源。同理,当计算表达式“a||b||c”时,只要a、b或c,3个表达式其中任意一个计算结果为true时,整体表达式立即返回true,而不去计算剩余表达式。简单地说,在布尔表达式的计算中,只要表达式的值可以确定,就会立即返 回,而跳过剩余子表达式的计算。如果使用位运算(按位与、按位或)代替逻辑与和逻辑或,虽然位运算本身没有性能问题,但是位运算总是要将所有的子表达式全部计算完成 后,再给出最终结果。因此,从这个角度看,使用位运算替代布尔运算会使系统进行很多无效计算。

12 提取公共表达式

我们知道在程序高速运行过程当中,少量的重复代码并不会对性能构成太大的威胁,但是如果你希望将系统性能发挥到极致,则还是有很多地方可以优化的。

13 不要总是使用取反操作符(!)

取反操作符(!)表示异或操作,使用起来很方便,但是也要注意的是,它降低了程序的可读性,所以建议不要经常使用。

14 初始化变量

默认情况下,调用类的构造函数时,Java会把变量初始化为一个确定的值,例如,所有的对象被设置成Null,整数变量设置成0,float和double变量设置成0.0,逻辑值设置成false。当一个类从另一个类派生时,这一点尤其应该注意,因为用new关键字创建一个对象时,构造函数链中的所有构造函数都会被自动调用。

这里需要注意,当我们给成员变量设置初始值,又需要调用其他方法的时候,最好放在一个方法里面。比如initXXX()中(而且这个方法最好设为private私有的,防止构造方法多态),因为直接调用某方法赋值可能会因为类尚未初始。如果不初始化局部变量,那么当我们直接调用变量的时候,系统会给对象或变量随机赋一个值,这样容易产生不必要的错误。

15 switch语句使用要注意

在Java 7之前,switch语句中的条件表达式的类型只能是与整数类型兼容的类型,包括基本类型char、byte、short和int,与这些基本类型对应的封装类Character、Byte、Short和Integer,还有枚举类型。Java  7放宽了这个限制,额外增加了一种可以在switch的数据类型字符串,即String类型。一定要注意String不能为null(编译后将字符串的哈希值作为switch语句的表达式的值),否则出现空指针异常;另外每个case 分支后面一定要的break关键字,否则可能会继续向下进入其他case分支。

16 使用含分隔符的字面量

如果Java源代码中有一个很长的数值字面量,开发人员在阅读这段代码时需要很费力地分辨数字的位数,以知道其所代表的数值大小。在Java 7中,数值字面量,不管是整数还是浮点数,都允许在数字之间插入任意多个下画线。这些下画线不会对字面量的数值产生影响,其目的主要是分隔数字、方便阅读。

    public void de() {
int a=242_434_343;
float f=-23.343_343_433F;
double d = 342.340_674;
}

17 优化变长参数

J2SE 5.0中引入的一个新特性就是允许在方法声明中使用可变长度的参数。一个方法的最后一个形式参数可以被指定为代表任意多个相同类型的参数。在调用的时候,这些参数是以数组的形式来传递的。在方法体中也可以按照数组的方式来引用这些参数。可变长度的参数在实际开发中可以简化方法的调用方式。但是在Java 7之前,如果可变长度的参数与泛型一起使用后会遇到一个麻烦,就是便一起产生的警告过多,比如下面 Java 6变长参数示例

Java 性能优化:面向对象及基础类型使用优化

为了解决这个问题,Java 7引入了一个新的注解@SafeVarargs。@SafeVarargs注解只能用在参数长度可变的方法或构造方法上,且方法必须声明为static或final,否则会出现编译错误。

    @SafeVarargs
public static <T> List<T> toList(T ...args){
return Arrays.asList(args);
}

一个方法是用@SafeVarargs注解的前提是,开发人员必须确保这个方法的实现中对泛型类型参数的处理不会引发类型安全问题。

18   针对基本数据类型的优化

Java7对基本类型的包装类做了一些更新,以更好地满足日常的开发需求。第一个更新是在基本类型的比较方面,Boolean、Byte、Short、Integer、Long和Character类都添加了一个比较两个基本类型值的静态compare方法,比如Long类的compare方法可以用来比较两个Long类型的值。这个compare方法只能简化进行基本类型数值比较时的代码。在Java7 之前,如果需要对两个int数值x和y进行比较,一般的做法是使用代码“Integer.value(x).compareTo(Integer.value(y))”,而在Java7直接使用“Integer.compare(x,y)”。

字符串内部化(string    interning)技术可以提高字符串比较时的性能,是一种典型的 空间换时间的做法。在Java中包含相同字符的字符串字面量引用的是相同的内部对象。String类也提供了intern方法来返回与当前字符串内容相同的但已经包含在内部缓存中的对象引用。在对被内部缓存的字符串进行比较时,可以直接使用“==”操作符,而不需要用更加耗时的equals方法。

另外Java7还把这种内部化机制扩大到了-128~127的数字。根据Java语言规范,对于-128到127范围内的short类型和int类型,以及\u0000到\u007f范围内的char类型,它们对应的包装类对象始终指向相同的对象,即通过“==”进行判断时的结果为true。为了满足这个要求,Byte、Short、Integer类的valueOf方法对于-128到127范围内的值,以及Character类的valueOf方法对于0到127范围内的值,都会返回内部缓存的对象。如果希望缓存更多的值,可以通过Java虚拟机启动参数“java.lang.Integer.Integer-Cache.high”来进行设置。例如,使用“-Djava.lang.Integer.IntegerCache.high=256”之后,数值缓存的范围就变成了-128 到256。

19  赋空变量

显式地赋空变量是否有助于程序的性能。赋空变量是指简单地将null值显式地赋值给这个变量,相对于让该变量的引用失去其作用域,提醒java的垃圾回收器进行对象回收、释放相应的内存空间。

上一篇:String类型的属性和方法


下一篇:【Java数据结构学习笔记之三】Java数据结构与算法之队列(Queue)实现