Java面试题之包装类缓存机制

Java包装类缓存机制

引言

在我人生中第一次找工作时(大四实习),曾经出现了这样一道笔试题,求其结果。

Integer a = 100;
Integer b = 100;
Integer c = new Integer(100);
Integer d = new Integer(100);
Integer e = 1000;
Integer f = 1000;
System.out.println(a==b);
System.out.println(a==c);
System.out.println(c==d);
System.out.println(e==f);

我记得我当时答案是:false,false,false,false。哈哈,全false,当时脑子想的是好像有个缓存,valueOf相关,这儿没有用,都是对象,全错。
然后…………光荣OUT。
正确答案是:true,false,false,false

一言不合,先反编译

Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
Integer c = new Integer(100);
Integer d = new Integer(100);
Integer e = Integer.valueOf(1000);
Integer f = Integer.valueOf(1000);
System.out.println(a == b);
System.out.println(a == c);
System.out.println(c == d);
System.out.println(e == f);

对,你没有看错,Integer a = 100 ,变成了Integer a = Integer.valueOf(100)
这涉及到了Java中的装箱转换。
在《Java语言规范》第三版中5.1.7节有一下一段话。

装箱转换将把基本类型的值转换为相应的引用类型的值
……
……
如果被装箱的值是true、false, —个byte、一个在\u0000~\u007f之间的char。
或者一个在-128~127之间的int或short数字,设r1和是r2的任何两个装箱转换的结果,则始终有r1=r2

话是这样说,怎么实现呢,我们通过Integer代码说明(我使用的是JDK9)

Integer缓存机制实现

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

上面这段话的意思就是,如果传入的值在[IntegerCache.low,IntegerCache.high]之间,则返回IntegerCache中的cache数组中对应的值。(比如 i = 100,low = -128,high = 127,则返回cache[228])
那这个IntegerCache又是什么东西呢,它是Integer中的一个私有静态内部类

private static class IntegerCache {
    static final int low = -128;//##cache的最低值为-128
    static final int high;//##最大值未定义,此时默认为0
    static final Integer cache[];//##定义一个Integer数组,存放Integers

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            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);//##最小值-127不变,h就是i和Integer.MAX_VALUE-128中的最小值。总不能超过int能表示的数
            } 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++)//##遍历赋值从-128,-127...-->cache.length
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        //##根据Java语言规范   -128, 127之间必须被缓存  当最大值小于127时程序将会异常终止java.lang.AssertError
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

上述代码中//##是我所备注注释。

设置IntegerCache.high

-Djava.lang.Integer.IntegerCache.high=xxx

xxx为你想要设置的数字大小 [-128,xxx]范围之内将会被缓存
比如:下图所示 [-128,300]范围之内将会被缓存
这里写图片描述

包装类缓存机制原因

一个工程中,像这种数字对象,时不时就要创建一个,如果有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存中已有的对象,避免重新创建。
于是,我们的Java采用了享元模式来设计这些包装类(String也是)。

享元模式(Flyweight Pattern)
主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。

引用自菜鸟教程,它对其进行了详细的介绍。

其他包装类缓存机制源码

Boolean缓存机制实现

public static Boolean valueOf(boolean b) {
    return (b ? TRUE : FALSE);
}

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));
    }
}
public static Byte valueOf(byte b) {
    final int offset = 128;
    return ByteCache.cache[(int)b + offset];
}

Short缓存机制实现

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));
    }
}
public static Short valueOf(short s) {
    final int offset = 128;
    int sAsInt = s;//##先转为int
    if (sAsInt >= -128 && sAsInt <= 127) { // must cache
        return ShortCache.cache[sAsInt + offset];
    }
    return new Short(s);
}

Long缓存机制实现

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);
    }
}
public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

Float和Double未使用缓存

public static Float valueOf(float f) {
  return new Float(f);
}
public static Double valueOf(double d) {
    return new Double(d);
}

既然说了自动装箱,就可以提一下自动拆箱

#自动拆箱

《Java语言规范》第三版中5.1.8节

拆箱转换把引用类型的值转换成相应的基本类型的值。

Integer a = 100;
int b = a;

反编译之后。

Integer a = Integer.valueOf(100);;
int b = a.intValue();

其他包装类一样,都是通过intValue()获取被包装的值。

#总结

可以看的出,这些包装类中(除了浮点数和双精度数)都使用了缓存机制,他确确实实减少创建对象的数量,减少了内存的占用和提高了性能。但是开发者有时不会注意到这些细节,可能会引起一些程序逻辑上的问题,所以一定要注意这些使用了缓存的包装类之间的对比。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值