龙珠

修炼自己与发现世界

Java:避免创建不必要的对象

为了保证内存的有效利用,应该尽量重用对象而不是在每次需要的时候就创建一个新的对象。

比如,构造器在每次被调用的时候就构建一个新的对象,而静态工厂方法则从来不要求这样做(顺便说一句,如果要求对象只有一个实例,可以使用静态工厂方法;如果对象有多个/不定个参数,可以使用静态Builder方法构建)。

举例来说,新建字符串有两种方法:

法一: String s = new String("This is a string.");
           
法二: String s = "This is a String";

在法一中,使用构造器强制构建了一个新的对象,不管相同的字符串是否已经存在;

而在法二中,则会先查看是否已经存在了该String对象,如果存在,则返回已存在String的引用,而不会再新建String对象。

因此,如果在String已经存在的情况下,法一多创建了一个不必要的String实例。如果这是在一个循环中,则频繁调用会产生成千上万个不必要的String。

再举一个关于Long和long的例子。对于Long而言,相当于上例的法一,每次需要创建新的实例,而如果对象被定义为long,则相当于法二,对象可以重新使用。代码如下:

public static void main(String[] argv){
 
        long startTime = System.currentTimeMillis();
        System.out.println("Integer.MAX_VALUE:"+Integer.MAX_VALUE);
         
        int result = 0;
        for(long i=0;i<Integer.MAX_VALUE;i++){
            result += i;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Int Result:"+result+". Total Cost Time:"+(endTime - startTime));
 
        startTime = endTime;
        long sumlong2 = 0L;
        for(long i=0L;i<Integer.MAX_VALUE;i++){
            sumlong2 += i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("long-long Sum:"+sumlong2+". Total Cost Time:"+(endTime - startTime));
     
        startTime = endTime;
        long sumlong = 0L;
        for(Long i=0L;i<Integer.MAX_VALUE;i++){
            sumlong += i;
        }
        endTime = System.currentTimeMillis();
        System.out.println("long-Long Sum:"+sumlong+". Total Cost Time:"+(endTime - startTime));
 
        startTime = endTime;
        Long sumLong1 = 0L;
        for(long i=0L;i<Integer.MAX_VALUE;i++){
            sumLong1 += i;
        }       
        endTime = System.currentTimeMillis();
        System.out.println("Long-long Sum:"+sumLong1+". Total Cost Time:"+(endTime - startTime));
     
        startTime = endTime;
        Long sumLong = 0L;
        for(Long i=0L;i<Integer.MAX_VALUE;i++){
            sumLong += i;
        }       
        endTime = System.currentTimeMillis();
        System.out.println("Long-Long Sum:"+sumLong+". Total Cost Time:"+(endTime - startTime));
    }

在代码中,分别对sum和i使用Long和long来实现,查看最后耗费时间的区别(单位:ms)。另外,由于最后结果超过int,我们把int结果作为参照。

代码结果如下:

Integer.MAX_VALUE:2147483647
        
Int Result:1073741825. Total Cost Time:8172
        
long-long Sum:2305843005992468481. Total Cost Time:8687
        
long-Long Sum:2305843005992468481. Total Cost Time:59188
        
Long-long Sum:2305843005992468481. Total Cost Time:49000
        
Long-Long Sum:2305843005992468481. Total Cost Time:96500

可以看到,在sum和i均为long的时候,耗费时间是最小的,8600ms左右。i单独为Long比sum单独为Long耗费时间要多一些(为什么?猜测是对i的操作比对sum的操作多一些。不过除去创建耗时之外,消耗时间和操作次数是成正比的吗?)。而毫无争议的,sum和i都为Long的情况最耗时,约为100s。因此可以看出,将变量声明为long而非Long,可以有效的节省时间。

因此,要优先使用基本类型而不是装箱类型,要当心无意识的自动装箱。

参考资料:

  1. Effective Java 中文版