《Effective Java》第6条:避免创建不必要的对象

非常简单易懂的理念,应该重用现有对象的时候就不要创建新的对象,因为这会影响程序的性能

第一个反面例子就是字符串的创建,非常经典,可以拿出来拓展一番

String s = new String("bikini");

“该语句每次被执行的时候都创建一个新的String实例,但是这些创建对象的动作都是不必要的。”

String s = "bikini";

"这个版本只用了一个String实例,而不是每次执行的时候都创建一个新的实例。“

为什么?

探寻原因之前,请思考:我们知道,字符串是引用数据类型,那么"bikini"是什么呢?它是如何创建的?什么时候创建的?

写一个测试类:

public class Test {
    public void test() {
        String a = "Effective Java";
    }
}

 首先讲常量池的概念,查看Test.class字节码文件

 Constant pool,常量池,用于存放编译期生成的字面量和符号引用

字面量:

  1. 文本字符串
  2. 声明为 final 的常量值
  3. 基本数据类型值

符号引用:

  1. 类和接口的全限定名,如 #2=Class
  2. 方法的名称和描述符,如 #1=Methodref
  3. 字段的名称和描述符

运行时常量池和常量池是两个不同的概念,JVM在完成类装载操作时,会将常量池中的内容载入内存,并保存在方法区中的运行时常量池,如果是字符串的话,会先在堆中创建字符串对象实例,然后将该对象的引用放到堆中的字符串常量池中,最后将运行时常量池里的符号引用替换成直接引用

篇幅有限,个人表述能力有限,可以看一下:

  1. The Structure of the Java Virtual Machine
  2. 理解JVM运行时数据区(五)方法区​​​​​​

ldc 就是在运行时常量池中载入项并压入操作数栈,可以看到载入的是 #7 "Effective Java"  然后 astore_1 将引用存储到局部变量中,也就是说直接复用了字符串常量池中的字符串对象

如果使用的是 String a = new String("Effective Java"),会在堆中额外创建一个新的对象,不一一讲解了,感兴趣的可以看一下The Java Virtual Machine Instruction Set

想要继续探究的可以思考一下我下面给出的代码运行结果是什么样的?为什么?

public class Test {
    public static void main(String[] args) {
        String a = "Effective";
        String b = "Java";
        String c = "EffectiveJava";
        String d = "Effective" + "Java";
        String e = a + b;
        System.out.println(c == d);
        System.out.println(c == e);
    }
}

再介绍一下基本数据类型包装类的自动装箱拆箱机制, 先写一个测试类,在这段代码中我们没有 显式地调用构造方法或者转换方法

public class Test {
    public void test() {
        Integer a = 1;
        int b = a;
    }
}

反汇编后可以发现自动调用了 Integer 类的 valueOf 和 intValue 两个方法

//Returns an Integer instance representing the specified int value. 
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

//Returns the value of this Integer as an int.
public int intValue() {
    return value;
}

因此这段代码实际上相当于:

public class Test {
    public void test() {
        Integer a = Integer.valueOf(1);
        int b = a.intValue();
    }
}

引人注目的是 valueOf 方法里的 IntegerCache.cache ,这是基本数据类型的缓存池,如果调用 valueOf 方法时传入的参数范围在-128~127间,会直接返回常量池中数据的引用,否则创建新对象,不多展开,感兴趣的可以看一下源码

public class Test {
    public static void main(String[] args) {
        Integer a = 1;
        Integer b = 1;
        Integer c = 128;
        Integer d = 128;
        System.out.println(a == b);
        System.out.println(c == d);
    }
}
//output:
//true
//false
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值