• Java中自动装箱与拆箱


    一、什么是封装类?

      Java中存在基础数据类型,但是在某些情况下,我们要对基础数据类型进行对象的操作,例如,集合中只能存在对象,而不能存在基础数据类型,于是便出现了包装器类。包装器类型就是对基本数据类型进行了封装,使之成为一个对象,每一个基本数据类型都对应一种包装器类型。

    二、什么是装箱与拆箱

      将基本数据类型变为包装器类,便是装箱,将包装器类转为基本数据类型就是拆箱。相面以Integer为例:

    public static void main(String[] args){
            //自动装箱
            Integer a = 100;
            //自动拆箱
            int b = a;
        }

    实际上,Integer在装箱过程中调用了Integer中的valueOf()方法,拆箱为int时调用了Integer中的intValue()方法,源码如下:

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

    这便是装箱与拆箱的底层执行过程,其它包装器类类似。

    三、自动装箱与拆箱中的一些问题

    问题1:

    public class Main {
        public static void main(String[] args) {
    
            Integer i1 = 100;
            Integer i2 = 100;
            Integer i3 = 200;
            Integer i4 = 200;
    
            System.out.println(i1==i2);  //true
            System.out.println(i3==i4);  //false
        }
    }

    通过分析valueOf()方法我们知道,当值为-128到127之间时,返回return IntegerCache.cache[i + (-IntegerCache.low)];当不在这个区间时,返回return new Integer(i);那么IntegerCache.cache[i + (-IntegerCache.low)]又是什么呢?继续追踪源码:

    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) {
                    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);
                }
                high = h;
    
                cache = new Integer[(high - low) + 1];
                int j = low;
                for(int k = 0; k < cache.length; k++)
                    cache[k] = new Integer(j++);
            }
    
            private IntegerCache() {}
        }

    从上面代码可以看出Integer类中提前就初始化了一个Integer数组,范围为-128到127。回到上面的题目:i1==i2=100返回true是因为范围属于-128到127,直接返回cache[]数组中的对象,属于同一个对象,==运算符比较对象,从而返回true,而200超过了这个范围,直接返回new Integer(),因此属于两个不同的对象,结果为false。

    问题2:

    public class Main {
        public static void main(String[] args) {
    
            Double i1 = 100.0;
            Double i2 = 100.0;
            Double i3 = 200.0;
            Double i4 = 200.0;
    
            System.out.println(i1==i2); //false
            System.out.println(i3==i4); //false
        }
    }

    上面的代码中的结果与Integer类型的结果大相径庭,原因还得从源码中找。

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

    因此返回false理所当然。

    总结一下:

    Integer派别:Integer、Short、Byte、Character、Long这几个类的valueOf方法的实现是类似的。 
    Double派别:Double、Float的valueOf方法的实现是类似的。每次都返回不同的对象。

    Integer派别的范围:

    问题3:

    public class Main {
        public static void main(String[] args) {
    
            Boolean i1 = false;
            Boolean i2 = false;
            Boolean i3 = true;
            Boolean i4 = true;
    
            System.out.println(i1==i2);//true
            System.out.println(i3==i4);//true
        }
    }

    以上代码又是林外一种情况,继续追踪源码

    public static Boolean valueOf(boolean b) {
            return (b ? TRUE : FALSE);
        }
    /**
         * The {@code Boolean} object corresponding to the primitive
         * value {@code true}.
         */
        public static final Boolean TRUE = new Boolean(true);
    
        /**
         * The {@code Boolean} object corresponding to the primitive
         * value {@code false}.
         */
        public static final Boolean FALSE = new Boolean(false);

    分析可知,TRUE和FALSE是两个final对象,因此i1、i2和i3、i4始终指向同一个对象的地址,因此返回true。

     问题4:

    public static void main(String[] args) {
    
                Integer num1 = 400;
                int num2 = 400;
                
                System.out.println(num1 == num2); //true
                System.out.println(num1.equals(num2));  //true
            }

    可见num1 == num2进行了拆箱操作。equals源码如下:

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

    我们知道equal比较的是内容本身,并且我们也可以看到equal的参数是一个Object对象,我们传入的是一个int类型,所以首先会进行装箱,然后比较,之所以返回true,是由于它比较的是对象里面的value值。

    问题5:

    public static void main(String[] args) {
    
                Integer num1 = 100;
                int num2 = 100;
                Long num3 = 200l;
                System.out.println(num1 + num2);  //200
                System.out.println(num3 == (num1 + num2));  //true
                System.out.println(num3.equals(num1 + num2));  //false
            }

    当一个基础数据类型与封装类进行==、+、-、*、/运算时,会将封装类进行拆箱,对基础数据类型进行运算。 
    对于num3.equals(num1 + num2)为false的原因很简单,我们还是根据代码实现来说明:

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

    它必须满足两个条件才为true: 
    1、类型相同 
    2、内容相同 
    上面返回false的原因就是类型不同。

    问题6:

    public static void main(String[] args) {
    
                int num1 = 100;
                int num2 = 200;
                long mum3 = 300;
                System.out.println(mum3 == (num1 + num2)); //true
            }

    当 “==”运算符的两个操作数都是包装器类型的引用,则是比较指向的是否是同一个对象,而如果其中有一个操作数是表达式(即包含算术运算)则比较的是数值(即会触发自动拆箱的过程)。

    问题7:

    public static void main(String[] args) {
    
                Integer integer100=null;
                int int100=integer100;
            }

    这两行代码是完全合法的,完全能够通过编译的,但是在运行时,就会抛出空指针异常。其中,integer100为Integer类型的对象,它当然可以指向null。但在第二行时,就会对integer100进行拆箱,也就是对一个null对象执行intValue()方法,当然会抛出空指针异常。所以,有拆箱操作时一定要特别注意封装类对象是否为null。

  • 相关阅读:
    RDIFramework.NET ━ .NET快速信息化系统开发框架4.4 员工(职员)管理
    (八大方法、逐层深入,有你一定没见过的)使用INSERT语句向表中插入数据
    国土档案管理信息系统【档案著录】地籍类档案著录
    RDIFramework.NET ━ .NET快速信息化系统开发框架4.3 平台主界面
    [推荐][提供下载](Excel):常用函数公式及操作技巧系列文章【共十篇】
    RDIFramework.NET ━ .NET快速信息化系统开发框架4.6 角色管理模块
    CSS长度单位参考
    Ajax 和 XML: 五种常见 Ajax 模式
    Web2.0岁月:使用AJAX技术的十大理由
    AJAX的安全性及AJAX安全隐患
  • 原文地址:https://www.cnblogs.com/jxxblogs/p/10700104.html
Copyright © 2020-2023  润新知