【面试准备】Java基础之浅谈自动装箱与拆箱

大家好,我是被白菜拱的猪。

一个热爱学习废寝忘食头悬梁锥刺股,痴迷于girl的潇洒从容淡然coding handsome boy。

假如你喜欢我的文字,欢迎关注公众号“放开这颗白菜让我来”。

自动装箱与拆箱

前言

最近在为日后的实习面试以及秋招做准备,在复习的过程中发现自己有很多东西没有掌握,尤其是基础的地方,很多概念还有细节上的东西没有去深入理解,再加上自己的懒惰拖延心中一直有此症结,其实很长时间之前就说要复习,结果一个月过去了还是没有行动,内心还无比的焦虑,每天愁啊愁啊,好菜,真的好菜,找不到工作可怎么办呢。

当然这都是没有用的,让我们告别焦虑,告别恐慌,一步一个脚印,接下来通过每日更新的方式打破现状,像银行螺丝钉看齐,每日搞定一个小知识点。

不积跬步,无以至千里;不积小流,无以成江海。告别曾经焦虑的自己,开始行动吧!

今天就来谈谈 Java 中的自动装箱与拆箱。

我将通过what、why、how三个方面也就是它是什么,为什么要这样做以及如何做进行介绍。学会知其然也要知其所以然。

what

什么是自动装箱,什么又是自动拆箱?曾经有个帅哥在编码的时候有这么一个疑问,int用着用着怎么突然冒出一个Integer。最近在学 JVM 的时候才逐渐解开心中的疑惑,像 char、byte、short、int、long、float、double、boolean称之为基本数据类型。像Character、Byte、Short、Integer、Long、Float、Double、Boolean以及像我们创建的类如Person ,他们都是一个类。要说什么数据类型,他们都是引用数据类型,前者我们又称作包装器类型,如Integer是int的包装器类型。

我们心中要把这个概念搞清,尤其是要分清楚int他们是一个类型,而Integer是一个类。类中呢则包含了属性、方法等内容。在很多地方他们都是要分别对待的。如基本数据类型在 JVM 中是存储在栈上的,而对象实例则是存储在堆上,栈只是存储这个对象的引用(reference)。又比如 == ,对于基本数据类型,比较的是他们的值,对于引用数据类型,则是比较对象存储的内存地址。

说了那么多,主要还是想把知识点进行融合,学会融会贯通,举一反三,更不要死记硬背。铺垫那么多,进入主题。

自动装箱就是自动将基本数据类型转化为包装器类型;

自动拆箱就是自动将包装器类型转化为基本数据类型。

why

我们知道在 Java 中,万事万物皆对象。我们操作更多的是对象,包装成一个类的目的是为了让我们更好的去操作他,我们看jdk的文档怎么说

Integer 类在对象中包装了一个基本类型 int 的值。Integer 类型的对象包含一个 int 类型的字段。

此外,该类提供了多个方法,能在 int 类型和 String 类型之间互相转换,还提供了处理 int 类型时非常有用的其他一些常量和方法。

说白了就是里面有很多方法,方便我们的操作。像ArrayList他存储的都是对象,ArrayList< int >这样写则是错的。

how

他们是如何实现自动装箱和拆箱的呢?正好正在学习 JVM,针对下面代码,我们使用 javap 反编译一下看一下字节码文件的内容。

public class AutoBoxing {
    
    
    public static void main(String[] args) {
    
    
        Integer a = 1000;
        int b = a + 1;
    }
}

在这里插入图片描述

我们看到装箱拆箱的操作时编译期间自动完成的,装箱是调用了Integer.valueOf()方法,拆箱则是调用了Integer.intValue()方法。

假如我们int b = a + 1;换成Integer b = a + 1;再使用 javap命令反编译一下

在这里插入图片描述

我们看两张截图的差别,发现在进行算术运算之前,先进行了拆箱,运算了之后又进行了装箱。这说明最后进行运算的时候是要转化为基本数据类型的。没想到就这么一小行代码一个加号背后竟然有这么多东西。

下面我们就通过一个例子去了解valueOf。

public class AutoBoxing {
    
    
    public static void main(String[] args) {
    
    
//        Integer a = 1000;
//        Integer b = a + 1;
        Integer a = 100;
        Integer b = 100;

        Integer c = 200;
        Integer d = 200;

        System.out.println(a == b);
        System.out.println(c == d);

        System.out.println(a.equals(b));
        System.out.println(c.equals(d));

    }
}

刚开始,咦,200不就是等于200嘛,怎么运行结果是false呢?那为什么100等于100成立呢?200不等于200,那下面equals怎么又成立了呢?小伙伴是否也有这么多问号。

接下来我们就深入的去探究其中的本质,我们点开源码看看Integer中的ValueOf方法。

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

我们发现在转换的过程中,不是直接创建一个Integer对象,而是return了一个缓存类中的数组中对应下标的元素。那我们点来 IntegerCache 这个类。

在这里插入图片描述

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

我们通过阅读源码发现,他实际上在 -128-127 这个范围内已经提前帮我们创好了对象,127 这个数值我们可以通过指令进行修改,默认是127,也就是说在这个范围内,我们多次创建,拿到的实际上是同一个对象。就跟线程池一样,起到复用的效果,提高了效率。所以想想为什么叫IntegerCache也有原因的。

好,那我们也就明白了,为什么 100 == 100 就是true, 200 == 200就是false了。 前面讲了 == 对于基础数据类型比较的是数值本身,对于引用类型比较的是对象的内存地址。在 -128 到127这个范围内使用的是同一个对象,所以内存地址一样,而不在这个范围内每次都是重新创建一个对象,那么对象的内存地址肯定不一样啦。

那为什么下面 equals 都是true呢。我们点开 equals 方法

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

首先判断的是是不是这个 Integer这个类型,不是直接返回false。然后在调用intValue方法进行拆箱,我们看 intValue(),很简单,直接返回这个数值。

    public int intValue() {
    
    
        return value;
    }

拆箱过后也就是基本数据类型,用 =比较他们的值,200 == 200,那肯定相等啦。

小伙伴们,听我这么一番解释,是不是豁然开朗啦?

其他类型详解

Integer 我们了解的差不多了,那其他类型呢?

我们通过阅读源码可知:

  • Byte、Short、Long是跟 Integer 一样的,默认范围是 -128 到 127。

  • Character则是 0 到 127。因为它本身就没有负数。

  • Float、Double是没有这种缓存机制的,因为你想一想小说他的个数是不确定的,0-1中间就有很多很多很多数字。

  • Boolean则直接用两个对象表示,值为True,值为False。
    在这里插入图片描述

最终杀技

最后我们通过一道题来判断自己是否完全掌握这个概念。

 public static void main(String[] args) {
    
    
        Integer a = 1;
        Integer b = 2;
        Integer c = 3;
        Integer d = 3;
        Integer e = 321;
        Integer f = 321;
        Long g = 3L;
        Long h = 2L;

        System.out.println(c==d); //true
        System.out.println(e==f); //false
        System.out.println(c==(a+b)); //true
        System.out.println(c.equals(a+b)); //true
        System.out.println(g==(a+b)); //true
        System.out.println(g.equals(a+b)); //false
        System.out.println(g.equals(a+h)); //true

    }

我想看完这篇文章应该很轻松解决这些问题了吧。难的也应该是倒数第一个和倒数第二个,拆箱又装箱。把我一个原则,进行运算的是基础数据类型,equals是对象中的方法,所以两边都要是对象。

拿倒数第一个距离,a + h,首先拆箱,这时候要注意一个类型转换,a 是 int,h是 long,所以最后类型是 long,最后装箱的结果是 Long。 而倒数第二个自动装箱的类型是 Integer,Integer当然与Long类型的不相等啦。

总结

在学习的过程中,要学会抓住事物的本质,以不变应万变。天下武功林林总总,我最喜欢独孤九剑,无招胜有招。重剑无锋,大巧不工。

猜你喜欢

转载自blog.csdn.net/weixin_44226263/article/details/112426289