Integer 的 valueOf 方法 与 常量池(对 String Pool 的部分理解)

时间:2023-03-09 09:59:23
Integer 的 valueOf 方法 与 常量池(对 String Pool 的部分理解)

举例:

public class Test {

    @org.junit.Test
public void intTest() {
Integer t1 = 128;
Integer t2 = 127;
} }

使用 javap -c 查看字节码

public void intTest();
Code:
0: sipush 128
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: bipush 127
9: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
12: astore_2
13: return

说明:

造成两种区别对待的等价方式,在于 valueOf 方法的实现:(low 与 high 分别是 -128 与 127),底层原理:IntegerCache 本质是编译期常量 static final Integer cache[], 一个 Integer 数组。

public static Integer valueOf ( int i){
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}

测试:

Integer t1 = new Integer(128);

Integer t2 = 128;

Integer t3 = 128;

int t4 = 128;

Integer t5 = 127;

Integer t6 = 127;

Integer t7 = new Integer(127);

结果:

System.out.println(t1 == t2); // false  因为 t2/t3 等价于 new Integer(128),所以 t1、t2、t3 互不相等。

System.out.println(t1 == t4); // true  因为与 int 运算时,会进行自动拆箱。所以 t1、t2、t3 与 t4 相等。

System.out.println(t5 == t6); // true 因为 t5/t6 其值默认指向常量池中的 127 常量。

System.out.println(t5 == t7); //false 因为 t5 是常量,而 t7 是Object实例。

拓展:

与 Integer 类似,String 有两种创建方式。对于使用字面量赋值方式。JVM为了节省空间,会首先查找JVM中是否有对应的字符串常量。如果已经存在,则直接返回该常量或字符串实例对象的地址引用,而无需重新创建对象。对象new创建方式,JVM将添加字面量常量和创建字符串实例并返回实例引用。

注意:

1>String 与 Integer 包装类都是不可变的:

private final int value; // Integer.java

private final char value[]; // String.java

2>常量池的实现有多种,String 与 Integer 的实现方式有区别。 Integer 的常量池直接是 Integer 数组,而 String 在 Java7后移动到堆空间(共享),底层是由 C++ 中的StringTable(类似固定容量的 HashMap)实现,每个 bucket 存储 相同 hash 对应的字符串 列表,效率更高(参考)。

测试1

String a = "we";
String a2 = new String("we");
String b = "we";
String c = "we";
System.out.println(a ==a2.intern());

结果

true

测试2

String a = "1";
String b = "1";
int aHashCode = System.identityHashCode(a);
int bHashCode = System.identityHashCode(b);
System.out.println("\na:" + a + "\nb:" + b);
System.out.print("\naHashCode:" + aHashCode + "\nbHashCode:" + bHashCode); char[] valueChar = new char[0];
try {
Field value = String.class.getDeclaredField("value");
value.setAccessible(true);
valueChar = (char[]) value.get(b);
} catch (Exception e) {
e.printStackTrace();
}
valueChar[0] = '2';
String c = "1";
String d = "2";
int cHashCode = System.identityHashCode(c);
int dHashCode = System.identityHashCode(d);
System.out.print("\na:" + a + "\nb:" + b + "\nc:" + c + "\nd:" + d);
System.out.print("\naHashCode:" + aHashCode + "\nbHashCode:" + bHashCode + "\ncHashCode:" + cHashCode + "\ndHashCode:" + dHashCode);

结果

a:1
b:1 aHashCode:476800120
bHashCode:476800120
a:2
b:2
c:2
d:2
aHashCode:476800120
bHashCode:476800120
cHashCode:476800120
dHashCode:1254526270

注意:

Object对象 的 hashCode() 方法与 System.identityHashCode(o) 方法返回结果是一致的。返回相应的 hash码。但 String 类重写了 hashCode() 方法,它是根据以下公式计算:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

导致的 String 对象的 hashCode 与 System.identityHashCode 返回的原值不相等。

为什么要重写 hashCode 方法呢?

1.String Pool 常量池的实现,底层类似 HashMap,一个 hash 对应多个相同字符串。

2.作为HashSet、HashMap等容器的 key 被使用。这些容器依赖于 hashCode 方法的实现(先使用 hashcode 比较,再使用 equals 比较)。

3.根据规范,如果根据 equals(Object) 方法,两个对象是相等(不是相同)的,那么对这两个对象中的每个对象调用 hashCode 方法都必须生成相同的整数结果。所以,根据字符串的 equals 方法的实现(根据字符串值判断相等),hashCode 也需要根据字符串值来返回哈希,以确保对于两个相等的值返回相同的 hashCode。此外,也说明 hashCode 不能是随机的数字,一定要按照相应的实现确立。

不可变的引用类型

在不使用常量池的情况下,new Integer() 返回的是一个引用类型,但是对这个引用实例的修改却并没有改变原来的引用绑定的值,原因是每次修改其实是在重新创建对象并重新绑定。这就是 primitive 类型的实现方式之一。