0%

面试题之-Java中判断200==200为false但127==127为true的疑问

Java中判断200==200为false但127==127为true的疑问

1. 问题的引出

1.1 200==200为false吗?

代码:

package com.niewj.nio;

/**
 * Created by niewj on 2020/9/20 11:24
 */
public class Test {
    public static void main(String[] args) {
        Integer i1 = 127;
        Integer i2 = 127;
        System.out.println("包装类 127 == 127: "+ (i1 == i2));

        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println("包装类 127 == 127: "+ (i3 == i4));

        int i5 = 200;
        int i6 = 200;
        System.out.println("非包装int型 200 == 200: " + (i5 == i6));
    }
}

控制台输出:

包装类 127 == 127: true
包装类 127 == 127: false
非包装int型 200 == 200: true

看现象得结论:

1). 原生类型的200==200为true

2). 包装类Integer的200==200为false

3). 包装类Integer的127==127为true

看结论得疑惑? why??

对象类型的==比较的不是 对象的内存地址吗? 怎么这两个对象难道是一个?? 下来我们找找原因:

1.2 为什么包装类127==127为true

翻开随身携带的记事本, 写着许多事, 都是关于你….你讨厌被冷漠….习惯被守候…

跑题了, 我们不唱歌!

翻开jdk的源码, Integer:829行:

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

看代码能开出些许动过刀的嫌疑, 没错, 方法的文档注释里也说了:

1). -128到127的值被缓存

此方法将始终缓存范围为-128到127(包括在内)的值,并可能缓存此范围之外的其他值。

没错了, 这就是问题的原因, 辣么, 咋个实的现?

2. 问题的原因探究

Integer类内部有个static的内部类IntegerCache, 就是实现的原因:

2.1 IntegerCache的实现:

private static class IntegerCache {
	static final int low = -128;
	static final int high;
	static final Integer cache[];

	static {
		// high value may be configured by property
		int h = 127;
		String integerCacheHighPropValue =
			sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
		if (integerCacheHighPropValue != null) {
			try {
				int i = parseInt(integerCacheHighPropValue);
				i = Math.max(i, 127);
				// Maximum array size is Integer.MAX_VALUE
				h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
			} catch( NumberFormatException nfe) {
				// If the property cannot be parsed into an int, ignore it.
			}
		}
		high = h;

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

		// range [-128, 127] must be interned (JLS7 5.1.7)
		assert IntegerCache.high >= 127;
	}

	private IntegerCache() {}
}

类的文档注释:

Cache to support the object identity semantics of autoboxing for values between -128 and 127 (inclusive) as required by JLS. The cache is initialized on first usage. The size of the cache may be controlled by the -XX:AutoBoxCacheMax= option. During VM initialization, java.lang.Integer.IntegerCache.high property may be set and saved in the private system properties in the sun.misc.VM class.

机器人翻译:

缓存以支持JLS要求的-128和127(包括)之间值自动装箱的对象标识语义。缓存在第一次使用时初始化。缓存的大小可以由-XX:AutoBoxCacheMax=选项控制。在VM初始化期间,java.lang.Integer.IntegerCache。高属性可以设置和保存在私人系统的阳光属性。VM类。

2.2 IntegerCache类实现简析:

1). IntegerCache类特点:

  1. static class : 只随着类的加载, 初始化一次, 跟后续的不管多少个Integer的实例无关;
  2. static Integer cache[] : 所有Integer实例都可以共享的 cache;
  3. static int low=-128: 最小值为 -128;
  4. static int high; 最大值(static里有逻辑, 默认是127)
  5. static{}块中初始化逻辑

2). 着重分析下static{}块中初始化逻辑

sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high")

代码中读取了这个属性, 然后用它跟127比, 取大的, 作为最大缓存值;

关于这个属性的意义和操作空间, 我们在最后一部分详述:问题的逆转?! 部分

现在只要知道, 默认的话, 就是:

  1. 初始化了一个 static的Integer数组, 容量是256个, 存放的内容就是-128到127之间的值
  2. 我们在声明和初始化一个Integer变量的时候, 如果值的区间在 -128到127之间, 就会从这个cache数组中取;
  3. 由于这个cache数组是静态的, 所以所有的Integer的实例可以共享
  4. 所以, 包装类型的Integer 127==127 实际上都是这个cache中的第一次初始化的127的Integer对象, 必然是同一个, 就true了;
  5. 但是200就不一样了, cache的区间只有 -128到127, 200的并没有缓存, 所以会每次初始化新的Integer对象实例

3). Integer的-128到127之间的值被缓存, 非此区间的每次都是新实例对象;

3. 问题的扩展:Byte/Short/Long/Character

原因已经找到了, 我们该结束了? 不不不, 这可是一道面试题, 你不发挥发挥? 你不发挥可能就被挥发了….危….

Integer有缓存了, 那么其他的整数类型呢?

Byte/Short/Character/Long 会不会也是共犯?

我们揪出源码审问一下:

3.1 Byte的缓存-ByteCache

Byte类还没打就招了:

private static class ByteCache {
    private ByteCache(){}

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

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Byte((byte)(i - 128));
    }
}

由于Byte本身体弱能力有限, 看到刑拘就招认了; 我本身就只占一个字节, 也没什么花花肠子, 我坦白吧: 我甚至都不用什么属性配置类, 我的实现也很简单粗暴, 够用就行!

1). ByteChache实现了Byte中的-128到127的缓存

3.2 Short的缓存-ShortCache

Short占2个字节, 它的实现也简单粗暴, 够用就行, 不耍花花绕:

private static class ShortCache {
    private ShortCache(){}

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

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Short((short)(i - 128));
    }
}

1). ShortCache实现了Short中的-128到127的缓存

3.3 Long的缓存-LongCache:

Long可是占8个字节的, 壮的一匹! 但是实现也是简单粗暴, 纯情的壮汉一枚!

private static class LongCache {
    private LongCache(){}

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

    static {
        for(int i = 0; i < cache.length; i++)
            cache[i] = new Long(i - 128);
    }
}

1). LongCache实现了Long中的-128到127的缓存

3.4 Character的缓存CharacterCache:

Character表示, 我只有2个字节, 本身是要表示字符的, 表示数字就是兼职交个朋友, 我也很纯情:

private static class CharacterCache {
    private CharacterCache(){}

    static final Character cache[] = new Character[127 + 1];

    static {
        for (int i = 0; i < cache.length; i++)
            cache[i] = new Character((char)i);
    }
}

1). CharacterCache实现了Character中的-128到127的缓存

4. 问题有逆转?!

再看个程序:

package com.niewj.nio;

/**
 * -XX:AutoBoxCacheMax=256
 * System.out.println(sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"));
 * Created by niewj on 2020/9/19 0:24
 */
public class Test {
    public static void main(String[] args) {
        Integer i3 = 256;
        Integer i4 = 256;
        Integer i5 = 257;
        Integer i6 = 257;
        System.out.println("包装类 256 == 256: "+ (i3 == i4));
        System.out.println("包装类 257 == 257: "+ (i5 == i6));
    }
}

程序输出:

包装类 256 == 256: true
包装类 257 == 257: false

什么??!! 包装类Integer啊! 256==256怎么是true? 257==257怎么又是false?

鬼火?! what the shark! 啥情况?

没错, 我们在main方法的注释里已经标明了, 是在启动时, 加了JVM参数, 设置了:

-XX:AutoBoxCacheMax=256

所以此时, Integer缓存的范围就不是 -128到127了, 而是-128到256;

JVM给了Integer类一个操作空间, 可以扩大缓存的范围! 我们通过JVM参数扩大到256了, 所以256比较是同一个对象, 但是257就否了!

4.1 -XX:AutoBoxCacheMax此JVM参数可以扩大Integer里cache的上限值

1). 设置jvm参数: -XX:AutoBoxCacheMax=256

package com.niewj.nio;

public class Test {
    public static void main(String[] args) {
        Integer i3 = 256;
        Integer i4 = 256;
        Integer i5 = 257;
        Integer i6 = 257;
        System.out.println("包装类 256 == 256: "+ (i3 == i4));
        System.out.println("包装类 257 == 257: "+ (i5 == i6));

        System.out.println("java.lang.Integer.IntegerCache.high=\t" + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"));
    }
}

控制台输出:

包装类 256 == 256: true
包装类 257 == 257: false
java.lang.Integer.IntegerCache.high=	256

可以看到, 这个参数已经被改了: java.lang.Integer.IntegerCache.high

2). -XX:AutoBoxCacheMax参数改的就是java.lang.Integer.IntegerCache.high的值

4.2 那么, Byte/Short/Long/Character这些类呢? 能扩容么

package com.niewj.nio;

public class Test {
    public static void main(String[] args) {
        Integer i1 = 256;
        Integer i2 = 256;
        System.out.println("Integer包装类 256 == 256: "+ (i1 == i2));

        Short s1 = 256;
        Short s2 = 256;
        System.out.println("Short包装类 256 == 256: "+ (s1 == s2));

        Long l1 = 256L;
        Long l2 = 256L;
        System.out.println("Long包装类 256 == 256: "+ (l1 == l2));

        Character c1 = 256;
        Character c2 = 256;
        System.out.println("Character包装类 256 == 256: "+ (c1 == c2));

        System.out.println("java.lang.Integer.IntegerCache.high=\t" + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"));
    }
}

控制台输出:

Integer包装类 256 == 256: true
Short包装类 256 == 256: false
Long包装类 256 == 256: false
Character包装类 256 == 256: false
java.lang.Integer.IntegerCache.high=	256

其实从上面的代码分析我们也可以看出, 除了Integer, 其他的类都没有做参数读取; 都只用了127这个固定值;

1). 只有Integer的缓存127最大值可以参数修改