Short对象的比较

一.背景

今天和同事一起分析线下问题,发现代码中有个笔误,对两个Short类型的对象使用==来比较,结果是比较表达式一直为false,导致一个bug的产生。

二.分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class Test {

    public static void main(String[] args) {
        Short s = 100;
        Short s1 = 100;
        // true
        System.out.println(s1 == s);

        Short t = 129;
        Short t1 = 129;
        // false
        System.out.println(t1 == t);
        
        Short m = new Short("100");
        Short m1 = new Short("100");
        // false
        System.out.println(m1 == m);
    }
}

写了一小段代码,模拟今天遇到的问题。我们看到第一个输出为true,第二个输出为false,第三个输出为false。为什么呢?我们来看一下生成的字节码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
Code:
   // Stack表示操作数栈的最大深度
   // Locals表示局部变量表所需的存储空间,单位是Slot,JVM为局部变量分配内存所使用的最小单位,double和long占用了两个Slot
   // Args_size 表示方法参数个数,main函数只有一个参数,当然size为1了
   // 明明main方法中只有6个局部变量,为啥空间大小是7,别忘了第一个存储的是调用方法的实例,如果是静态方法存储的是类实例,如果是非静态方法存储的是this引用。
   Stack=3, Locals=7, Args_size=1

   0:	bipush	100
   // 这里调用了Short类的静态方法valueOf,待会分析valueOf的实现
   2:	invokestatic	#16; //Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
   5:	astore_1
   6:	bipush	100
   8:	invokestatic	#16; //Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
   11:	astore_2
   12:	getstatic	#22; //Field java/lang/System.out:Ljava/io/PrintStream;
   15:	aload_2
   16:	aload_1
   17:	if_acmpne	24
   20:	iconst_1
   21:	goto	25
   24:	iconst_0
   25:	invokevirtual	#28; //Method java/io/PrintStream.println:(Z)V
   28:	sipush	129
   31:	invokestatic	#16; //Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
   34:	astore_3
   35:	sipush	129
   38:	invokestatic	#16; //Method java/lang/Short.valueOf:(S)Ljava/lang/Short;
   41:	astore	4
   43:	getstatic	#22; //Field java/lang/System.out:Ljava/io/PrintStream;
   46:	aload	4
   48:	aload_3
   49:	if_acmpne	56
   52:	iconst_1
   53:	goto	57
   56:	iconst_0
   57:	invokevirtual	#28; //Method java/io/PrintStream.println:(Z)V
   60:	new	#17; //class java/lang/Short
   63:	dup
   64:	ldc	#34; //String 100
   // 这里直接调用了Short的构造函数
   66:	invokespecial	#36; //Method java/lang/Short."<init>":(Ljava/lang/String;)V
   69:	astore	5
   71:	new	#17; //class java/lang/Short
   74:	dup
   75:	ldc	#34; //String 100
   // 这里直接调用了Short的构造函数
   77:	invokespecial	#36; //Method java/lang/Short."<init>":(Ljava/lang/String;)V
   80:	astore	6
   82:	getstatic	#22; //Field java/lang/System.out:Ljava/io/PrintStream;
   85:	aload	6
   87:	aload	5
   89:	if_acmpne	96
   92:	iconst_1
   93:	goto	97
   96:	iconst_0
   97:	invokevirtual	#28; //Method java/io/PrintStream.println:(Z)V
   100:	return

我们在字节码中看到有调用Short的valueOf方法,那么接下来我们看看valueOf方法的实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
private static class ShortCache {
	private ShortCache(){}

	static final Short cache[] = new Short[-(-128) + 127 + 1];

	static {
	    // 缓存[-127,128]之间的Short对象
	    for(int i = 0; i < cache.length; i++)
		cache[i] = new Short((short)(i - 128));
	}
}
...
public static Short valueOf(short s) {
	final int offset = 128;
	int sAsInt = s;
	// 走缓存
	if (sAsInt >= -128 && sAsInt <= 127) { // must cache 
	    return ShortCache.cache[sAsInt + offset];
	}
        return new Short(s);
}
...

看完valueOf的实现,你就明白这一切了,首先使用=赋值的时候会调用valueOf(S)这个方法,S标识short,在这方法中如果要赋予的值在[-127,128]这个区间内,那么直接取缓存中的值,缓存中缓存了[-127,128]这个区间内的所有Short对象,如果Short的值在这个区间内,你使用=赋值,直接取缓存,至此上面的问题就不言而喻了。

三.总结

1.写代码的时候最好不要使用==来比较任何类型的引用,除非是基本类型,不然很容易出问题,导致某些场景下面没问题,某些场景下面有问题。
2.Long类型也有类似的机制。