Integer类型的值比较大小

Integer,int类型的值作比较

Posted by HuangCanCan on June 23, 2019

前言

这个星期在写代码的时候发现一个问题,这个问题以前我都没有注意过,就是关于Integer类型或int类型的值的比较。

Integer大小比较问题

描述一

下面演示,我出现的问题:

public class DemoApplication {

    public static void main(String[] args) {
        Integer code = 530000;
        City city = City.init();
        Integer c_code = city.getCode();
        if (c_code == code) {
            System.out.println("结果:true");
        } else {
            System.out.println("结果:false");
        }
    }

    static class City {
        private Integer id;
        private Integer code;
        private String name;
        private Integer pcode;
        private Integer level;

        public static City init() {
            City city = new City();
            city.setId(1);
            city.setCode(530000);
            city.setName("云南省");
            city.setPcode(0);
            city.setLevel(1);
            return city;
        }

        public Integer getId() {
            return id;
        }

        public void setId(Integer id) {
            this.id = id;
        }

        public Integer getCode() {
            return code;
        }

        public void setCode(Integer code) {
            this.code = code;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getPcode() {
            return pcode;
        }

        public void setPcode(Integer pcode) {
            this.pcode = pcode;
        }

        public Integer getLevel() {
            return level;
        }

        public void setLevel(Integer level) {
            this.level = level;
        }
    }
}

输出 结果:false 原因是:两个Integer类型的值,它们的地址值是不相等的。用”==”判断的是它们的地址值是否相等。所以结果输出false。

可以使用System.identityHashCode()方法查看对应变量的内存地址
System.out.println(System.identityHashCode(code));
System.out.println(System.identityHashCode(c_code));

最后,用Integer里面的equals方法进行比较值是否相等。

if (c_code.equals(code)) {
    System.out.println("结果:true");
} else {
    System.out.println("结果:false");
} `输出 结果:true`

结论:两个Integer类型对象,如果判断值相等要用equals方法,而不能用”==”。equals方法判断的是值是否相等,==判断的是地址值是否相等。

接下来,我们看一下Integer的equals方法的源码:

public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

由上面的Integer的equals方法的源码可知: 首先判断比较的这个值obj是否是Integer类型,如果是则把obj强制转换为Integer类型,然后再次拆箱(Integer转为int)为int类型,再比较它们的值是否相等。


到此,忽然想到一个问题,请往下看。

我把code这个变量类型改为int类型,原来是Integer类型,请问输出是多少?

int code = 530000;
City city = City.init();
Integer c_code = city.getCode();
if (c_code == code) {
    System.out.println("结果:true");
} else {
    System.out.println("结果:false");
}

if (c_code.equals(code)) {
    System.out.println("结果:true");
} else {
    System.out.println("结果:false");
}

两个if输出结果都是,结果:true

一个是Integer类型的,一个是int类型的值的比较,用==比较为什么输出的是true?原因是,上面c_code是Integer类型,因为java存在自动装拆箱的机制,c_code自动拆箱为int类型,相当于if(c_code.intValue() == code),所以它们就可以用==比较值是否相等了。

结论:一个Integer类型的值和int类型的值比较,使用”==”或者Integer的equals方法都可以比较出它们是否相等。

总结:

如果两个Integer类型对象,判断值相等要用equals方法,而不能用”==”。equals方法判断的是值是否相等,==判断的是地址值是否相等。

如果一个Integer类型的值和int类型的值比较,使用”==”或者Integer的equals方法都可以比较出它们是否相等。

如果两个int类型的值做比较,则使用”==”。

描述二

下面演示问题:

Integer a = 10;
Integer b = 10;
System.out.println("a==b : " + String.valueOf(a==b));
System.out.println("a.equals(b) : " + String.valueOf(a.equals(b)));

输出结果: a==b : true      a.equals(b) : true

变换一下a,b的值:

Integer a = 1000;
Integer b = 1000;
System.out.println("a==b : " + String.valueOf(a==b));
System.out.println("a.equals(b) : " + String.valueOf(a.equals(b)));

输出结果: a==b : false      a.equals(b) : true

这是什么原因呢? 我们在使用类似 Integer a = 1000 时,Integer这个类是调用的public static Integer valueOf(int i)这个方法完成自动装箱,把int类型自动转换为Integer类型。

自动装箱涉及到的方法是Integer.valueOf(),看看其源代码如下:

/**
    * Returns an {@code Integer} instance representing the  specified
    * {@code int} value.  If a new {@code Integer} instance is not
    * required, this method should generally be used in preference to
    * the constructor {@link #Integer(int)}, as this method is likely
    * to yield significantly better space and time performance by
    * caching frequently requested values.
    *
    * This method will always cache values in the range -128 to 127,
    * inclusive, and may cache other values outside of this range.
    *
    * @param  i an {@code int} value.
    * @return an {@code Integer} instance representing {@code i}.
    * @since  1.5
    */
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

上面源代码的注释里就直接说明了-128到127之间的值都是直接从缓存中取出的。实现:如果int型参数i在IntegerCache.low和IntegerCache.high范围内,则直接由IntegerCache返回;否则new一个新的对象返回。IntegerCache.low是-128,IntegerCache.high是127。

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 {@code -XX:AutoBoxCacheMax=<size>} 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.
    */
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() {}
}

在其static块中就一次性生成了-128到127直接的Integer类型变量存储在cache[]中,对于-128到127之间的int类型,返回的都是同一个Integer类型对象。

Integer.class在装载(Java虚拟机启动)时,其内部类型IntegerCache的static块即开始执行,实例化并暂存数值在-128到127之间的Integer类型对象。当自动装箱int型值在-128到127之间时,即直接返回IntegerCache中暂存的Integer类型对象。

为什么Java这么设计?应该是出于效率,性能的考虑,因为自动装箱经常遇到,尤其是小数值的自动装箱;而如果每次自动装箱都触发new,在堆中分配内存,就显得太慢了;所以不如预先将那些常用的值提前生成好,自动装箱时直接拿出来返回。哪些值是常用的?就是-128到127了。

不仅int,Java中的另外7中基本类型都可以自动装箱和自动拆箱,其中也有用到缓存。见下表:

基本类型 装箱类型 取值范围 是否缓存 缓存范围
byte Byte -128 ~ 127 -128 ~ 127
short Short -2^15 ~ (2^15 - 1) -128 ~ 127
int Integer -2^31 ~ (2^31 - 1) -128 ~ 127
long Long -2^63 ~ (2^63 - 1) -128 ~ 127
float Float
double Double
boolean Boolean true, false true, false
char Character \u0000 ~ \uffff \u0000 ~ \u007f