神奇的常量池和intern方法

以下程序编译环境为JDK1.8,且是对于复合数据类型而言,不针对原始数据类型如byte\int\short\float\double等

一、引入

我们也许会被灌输一个观念,就是复合数据类型比较要用equals,不能用==,否则会出现值相等和地址不相等导致错误。
让我们思考网上的两个有趣的例子

/*****   exmple1 ******/
Integer i1 = 1; 
Integer i2 = 1;
System.out.println(i1 == i2);//true

Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);//true 

Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6);//true

Integer i5n = new Integer(127);
Integer i6n = new Integer(127);
System.out.println(i5n == i6n);//false

Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//false

Integer i9 = 1000;
Integer i10 = 1000;
System.out.println(i9 == i10);//false


/*****   exmple2 比较上下两段代码******/
//  String str1 = new String("1") + new String("1");        
//  System.out.println(str1.intern() == str1);  //true   

String str1 = new String("1") ;        
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "1");  //false  

这里用了==比较,但也许疑惑,为什么的1、100、127会有==,而new、128、1000会不==,这是偶然还是必然;为什么example2的上下两段代码的运行结果会不一样,难道受”+”影响了?intern()返回值是什么……

二、intern()和常量池

intern()

对于JDK7以上的string调用intern方法,会string的值与常量池比对,若不存在则创建常量池变量,无论成功与否都会返回常量池的引用。

常量池

常量池,顾名思义,就是将对象放入池中,使用时通过对象引用的方式。
好处是避免频繁的创建和销毁对象而影响系统性能,其实现了对象的共享。
那我们在编程中如何使用常量池呢?

  • public final static
    我们也许用过这个声明过全局静态变量

  • 对象创建方式

  String str1 = "abcd";
  String str2 = new String("abcd");
  System.out.println(str1==str2);//false

第一种是,在常量池中寻找 “abcd”,若有则返回引用,若无则创建常量池对象并返回引用;第二种方式是直接在堆内存空间创建一个新的对象。所以str1和str2会指向不同的地址
若改为下面

  String str1 = "abcd";
  String str2 = "abcd";
  System.out.println(str1==str2);//true

因为str2在常量池中会找到”abcd”

  • 连接表达式 +
String str1 = "cunteng";
String str2 = "008";

String str3 = "cunteng" + "008";
String str4 = str1 + str2;
System.out.println(str3 == str4);//false

String str5 = "cunteng008";
System.out.println(str3 == str5);//true

原因是常量和常量相加为常量,二变量str1+str2在编译时期是无法确定的,所以str4不会到常量池检查是否罕有该常量
注意特例: public static final 创建的常量与文本创建的
如”cunteng”是一样的

public class Main {
    final static String str1 = "cunteng";
    final static String str2 = "008";
    public static void main(String[] args) throws Exception {  
        String str3 = "cunteng" + "008";
        String str4 = (str1 + str2);
        System.out.println(str3 == str4);//true

        String str5 = "cunteng008";
        System.out.println(str3 == str5);//true

    }

}
  • String s1 = new String(“cunteng008”);发生了什么??
    每次new String(“cunteng008”),都会产生一个新的对象;且将”cunteng008”常量池的对象比较,若不存在,则在常量池创建新对象,我觉得它相当于暗中进行了一次”cunteng008”.intern();
    看一个有趣的例子
/** example3 **/
String str1 = new String("cunteng") + new String("008");      
System.out.println(str1.intern() == str1);  //true 

/** example4 **/
String str1 = new String("cunteng") ;      
System.out.println(str1.intern() == str1);  //false

对于example3,new String(“cunteng”)和new String(“008”)都会分别产生一个对象,并将”cunteng”和”008”放入常量池中;但 new String(“cunteng”)整体并不是一个常量,两个这样的对象会产生一个新的对象;当str1.intern()时,常量池中只有”cunteng”和”008”,并没有”cunteng008”,所以常量池会新增”cunteng008”,但不会新创建对象,而是直接引用str1;
new String(“cunteng”)后,常量池已经有了”cunteng”,是常量池新创建的,所以与str1指向自然不同。

  • 使用了常量池的基本类型包装类
    • Integer
      -128 ~ 127 装箱时使用了常量池,不会产生新的对象,二超范围的会。
Integer i = A; //A为常量
等价于
Integer i = Integer.valueOf(A);
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
    return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
private static class IntegerCache {
   static final int high;
   static final Integer cache[];

      static {
           final int low = -128;

           // high value may be configured by property
           int h = 127;
           if (integerCacheHighPropValue != null) {
     // Use Long.decode here to avoid invoking methods that
    // require Integer's autoboxing cache to be initialized
               int i = Long.decode(integerCacheHighPropValue).intValue();
               i = Math.max(i, 127);
               // Maximum array size is Integer.MAX_VALUE
               h = Math.min(i, Integer.MAX_VALUE - -low);
           }
           high = h;

           cache = new Integer[(high - low) + 1];
           int j = low;
           for(int k = 0; k < cache.length; k++)
               cache[k] = new Integer(j++);
       }

       private IntegerCache() {}
   }
  • Bool
public static Boolean valueOf(boolean b) {
        return (b ? TRUE : FALSE);
    }
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code true}.
     */
    public static final Boolean TRUE = new Boolean(true);
    /**
     * The {@code Boolean} object corresponding to the primitive
     * value {@code false}.
     */
    public static final Boolean FALSE = new Boolean(false);

三、总结

  • intern返回的是常量池对象引用
  • new会生成一个新的对象,分配新的内存;但第一次new会指向常量池的引用
  • 常量相加等于常量,会加入到常量池;
  • 已经创建了的对象,在调用intern时,常量池不会创建新的对象,而是直接指向已创建的对象;
  • final static 为常量;
  • 基本类的包装类有部分使用常量池技术

在看看引入的问题,就简单了许多

/*****   exmple1 ******/
/*
1/100/127在[-128,127]范围内,引用常量池,故==;128/1000超出会分别创建新的对象,故不==;new对象一定会创建新对象,故不==。
*/
Integer i1 = 1; 
Integer i2 = 1;
System.out.println(i1 == i2);//true

Integer i3 = 100;
Integer i4 = 100;
System.out.println(i3 == i4);//true 

Integer i5 = 127;
Integer i6 = 127;
System.out.println(i5 == i6);//true

Integer i5n = new Integer(127);
Integer i6n = new Integer(127);
System.out.println(i5n == i6n);//false

Integer i7 = 128;
Integer i8 = 128;
System.out.println(i7 == i8);//false

Integer i9 = 1000;
Integer i10 = 1000;
System.out.println(i9 == i10);//false


/*****   exmple2 比较上下两段代码******/
/*
str1不会进入常量池,当str1.intern时,因为str1已创建对象,故常量池直接指向str1;
new String("1")后常量池创建新对象并存下"1",常量池的对象与str1不同,故调用intern时返回的常量池指向的对象与str1不==;
*/
//  String str1 = new String("1") + new String("1");        
//  System.out.println(str1.intern() == str1);  //true   

String str1 = new String("1") ;        
System.out.println(str1.intern() == str1); //false
System.out.println(str1 == "1");  //false  

new会生成一个新的对象,分配新的内存;但有时候会让它与一个基本类型比较,这时Integer会先拆箱取出值再比较,这时就是值比较了。
下面例子


int i=0;
Integer j = new Integer(0);
System.out.println(i==j); //true

参考
Java常量池理解与总结
Java面试——从JVM角度比较equals和==的区别
Java技术——你真的了解String类的intern()方法吗
IntegerCache in Java

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值