java自动装箱、拆箱以及全等规则

java是从1.5 才引入的基础数据类型的自动拆装箱操作。本文主要基于java1.8的测试结果及源码跟踪来对一些知识点进行说明。

jvm规则

在《深入理解java虚拟机》中,有提到几点关于jvm对于自动拆箱的说明:

  1. 全等运算符==在遇到算术运算符的情况下会自动拆箱,否则,不会自动拆箱
  2. 包装类的equals方法基本都有重写,有严格的类型校验要求

全等规则

  1. 全等运算符==

     如果是基本数据类型,则比较值大小是否相等;
     如果是引用类型,则比较引用对象的内存地址是否相同
    
  2. equals方法

     如果重写,则以重写的逻辑为准;
     如果不重写(不考虑中间父类重写),则以Object对象的equals为准
     Object的equals方法采用的逻辑是全等运算符。
    

特殊情况

数据缓存机制

先看一个测试Case

    Integer a = 2;
    Integer b = 2;
 
    Integer x = 128;
    Integer y = 128;
    
    Float f1 = 127f;
    Float f2 = 127f;

    System.out.println("a==b ? " + (a==b));
    System.out.println("x==y ? " + (x==y));
    System.out.println("f1==f2 ? " + (f1 == f2));

运行该段代码,输出结果为:

	a==b ? true
	x==y ? false
	f1==f2 ? false

之所以会出现这种结果,主要是跟包装类自身的实现有关。

短整型(int)和长整形(long)有全局缓存,而浮点型则没有,该缓存大小为1个字节,针对有符号整数,也就是说[-128,127]范围内的整形包装类都指向同一个缓存实例对象,这从源码中也可以看出。

Integer缓存逻辑

/**
     * 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() {}
    }

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);
        }
    }

所以才有前面的测试代码的输出结果。

自动拆箱

对于有算术运算符(加减乘除等)参与的==逻辑,包装类会进行自动拆箱,转换成对应的基础数据类型。对比以下测试Case

    Integer a = 1;
    Integer x = 128;
    Integer y = 129;
    System.out.println("(x + a) == y ? " + ((x + a) == y));

运行该段代码,输出结果为:

	(x + a)== y ? true

可以看出,这里就对Integer进行了自动拆箱为int,比较时,是比较的值。

方法区常量池

java中的字符串类String是一个比较核心的类。使用频率非常高。jvm对字符串也做了一些优化,主要是常量池的的相关实现(除了String类型常量,还有基础数据类型和类元数据也会存储在方法区)。

jvm虚拟机会将java(仅考虑java语言)代码中的字面量(字符串常量、基础数据类型,包括成员变量以及局部变量)缓存到方法区的常量池中。如果通过直接赋值来获取对象实例时,会先在常量池中查找是否已经存在对应的值对象,如果存在,则将常量池中对应的值对象的引用返回。

特殊的,String类的intern方法。该方法会在即使显示的调用了new关键字来分配新的内存时,仍然会先在常量池中进行值查找。

下面通过一个测试Case来验证一下。

    String ss1 = "this is str";
    String ss2 = "this is str";
    String ss3 = new String("this is str");
    String ss4 = new String("this is str").intern();

    System.out.println("ss2 == ss1 ? " + (ss2 == ss1));
    System.out.println("ss3 == ss1 ? " + (ss3 == ss1));
    System.out.println("ss4 == ss1 ? " + (ss4 == ss1));

运行该段代码,输出结果为:

	ss2 == ss1 ? true
	ss3 == ss1 ? false
	ss4 == ss1 ? true

我们也可以看下String. intern()的源码说明

 /**
     * Returns a canonical representation for the string object.
     * <p>
     * A pool of strings, initially empty, is maintained privately by the
     * class {@code String}.
     * <p>
     * When the intern method is invoked, if the pool already contains a
     * string equal to this {@code String} object as determined by
     * the {@link #equals(Object)} method, then the string from the pool is
     * returned. Otherwise, this {@code String} object is added to the
     * pool and a reference to this {@code String} object is returned.
     * <p>
     * It follows that for any two strings {@code s} and {@code t},
     * {@code s.intern() == t.intern()} is {@code true}
     * if and only if {@code s.equals(t)} is {@code true}.
     * <p>
     * All literal strings and string-valued constant expressions are
     * interned. String literals are defined in section 3.10.5 of the
     * <cite>The Java&trade; Language Specification</cite>.
     *
     * @return  a string that has the same contents as this string, but is
     *          guaranteed to be from a pool of unique strings.
     */
    public native String intern();

从中还可以看到对intern后的字符进行比较时,如果结果为true,则两者通过equals比较的结果也必定为true

小结

虽然本文是对这些jvm以及源码中的优化手段进行了分析,证明了测试case的的正确性,但是在实际开发中,仍然不建议开发者进行这样的编码,不但会降低代码的可读性,还会增加逻辑上的迷惑性。
最后,遇到比较疑惑的问题时,不妨从源码以及jvm虚拟机规范中查找解决方案。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值