Java–包装类的学习

本文介绍了Java中的包装类– 什么是包装类 ,包装类和基本数据类型之间的关系转换 –装箱与拆箱,包装类的valueOf和XXValue以及构造方法的总结 , 自动装箱和拆箱以及触发的情况 , 包装类内里的缓存机制, 包装类一些相关练习题和面试题

包装类的学习

  • 一.包装类
    • 1. 什么是包装类?
    • 2.基本数据类型对应的包装类
    • 3.基本数据类型和包装类类型之间的转换
      • ①.装箱和拆箱
      • ②.各个包装类的valueOf和XXValue方法和构造方法
      • ③.自动装箱和自动拆箱
      • ④.触发自动装箱/拆箱的情况
  • 二.包装类的缓存机制
  • 三.包装类练习题&面试题

一.包装类

在java中有基本数据类型 –四类八种 有引用数据类型
相比基本数据类型来说,它可以用来定义数据的基本类型,也可以用来定义存放对应基本数据的空间类型

而java是一种纯面向对象的编程语言, 一切皆对象, 显然基本数据类型不符合这一设定,因为它仅仅是用来描述一个数据的类型. 因此,包装类由此诞生…

1. 什么是包装类?

包装类是一种特殊的类,它是针对现有的基本数据类型延伸拓展的类类型
包装类即是对应着各种基本数据类型进行包装后产生的引用数据类型 ,
是基本数据类型的plus版本

通过类来描述基本数据, 将数据和操作数据的方法进行有机结合, 不仅可以描述一个基本数据类型,还能描述这个类型的一些相关属性(最大值,最小值…) 以及对应这个基本数据类型相关的方法(valueOf 将字符串类型转换成此基本数据类型)

拿Integer为例 , int对应的包装类是Integer ,它内部的value存放的就是对应的基本数据 ,而还额外有很多静态属性 比如 int的最大值 最小值 Size:int占多少bit位等…

2.基本数据类型对应的包装类

基本数据类型有四类 8种, 对应包装类也有8种

基本数据类型包装类
boolean (未定义)Boolean
byte(1字节)Byte
char (2字节)Charcter
short(2字节)Short
int(4字节)Integer
long(8字节)Long
float(4字节)Float
double(8字节)Double

除了 Integer 和 Character, 其余基本类型的包装类都是首字母大写。

3.基本数据类型和包装类类型之间的转换

每个基本数据类型都有其对应的包装类, java中用来表示更具体完善的类型会用包装类,但是在一些运算 ,比较或者简单存储时会用到基本数据类型,而二者间可以进行相应的转换

①.装箱和拆箱

装箱:将基本数据类型转换为对应包装类
拆箱:将包装类转换为对应基本数据类型

int i = 10; // 基本数据类型 --int 定义的整形空间i 存放整形常量10

// 装箱操作,新建一个 Integer 类型对象,将 i 的值放入对象的某个属性中

//装箱操作: 将一个基本类型的数据(int) 放到对应的包装类对象(Integer)中
Integer ii = Integer.valueOf(i);//调用Integer.valueOf方法传基本数据类型的数据(常量或者变量) 将其转换成Integer对象
Integer ij = new Integer(i);// 创建Integer对象 构造时提供对应的基本数据

// 拆箱操作:将 包装类对象(Integer)对象中的值取出放到一个基本数据类型(int) 变量中
int j = ii.intValue();// ii是Integer对象 内有成员方法intValue 即返回此对象内存储的基本数据

int和Integer就好像大古和迪迦奥特曼
大古是一个普通人,它能做的事相对较少, 而迪迦是奥特曼拥有更多的能力

装箱即是大古变身为迪迦…
迪迦内部仍然是大古,但是它具备了很多属性和行为
(大古是普通人,因为其能力小,一些大型工作就难以胜任 某些特殊场景:打怪兽 等 需要将大古变身为迪迦)
用包装类适用于某些较大的复杂的工作

拆箱即是迪迦变回大古…
变回大古后,便是一个普通人,不再具备那些额外的属性和能力
在日常生活中 迪迦相比大古各方面都比较”重”,占据的资源也大,
用基本数据类型更完成适合日常简单的一些工作

②.各个包装类的valueOf和XXValue方法和构造方法

因为基本数据类型和包装类有8种,故每个一个包装类里都有其自己的valueOf方法和XXValue方法可以将对应的基本数据类型和包装类之间进行转换…
XX表示基本数据类型中的某种

Integer:

Integer的valueOf主要有两个
一个是将字符串数据转换成对应的包装类数据(方便将字符串内容转换成指定类型数据)
一个是将对应的基本数据类型转换成对应的包装类 (如Integer包装类的valueOf提供的是将int转换成Integer)

而XXValue方法除了BooleanValue和CharValue 其他的基本数据类型转换都有, 表示的含义就是将Integer包装类对象里的int数据拆箱成对应XX类型的数据
Character:

提供的valueOf方法只有 对char进行操作的一个方法, 因为其对应的只是一个字符,字符串是有n个字符的集合,不存在n个字符转变成一个字符没有对String进行操作的

XXvalue也只有一个将Character转换成char的 charValue方法,因为char是字符类型数据,和其他的基本类型数据不同,它是一种由整数特殊对应的字符数据
它可以表示英文字符 中文字符

英文字符用Ascll码(-128~127的整数)表示 ,中文字符根据不同的字符编码表示(有的是两个字节对应一个中文字符 有的是三个), 字符类型本意就是表示字符的, 故只有转换成对应的char类型的XXValue方法 ,如果需要转成整数可以进行强转

Boolean:

Boolean是boolean布尔类型对应的包装类 .它表示的只有true和false两种情况, valueOf提供的将boolean进行转换 和将String类型数据进行转换(字符串转换时 字符串的值只能是”true”or”false”)

XXValue只有booleanValue ,因为布尔类型数据是特殊的,和其他类型数据没有关联

Byte&Short&Integer&Long&Float&Double:

以Short为例, Byte Short Integer Long Float Double这六个包装类里提供的valueOf都是两个 ,一个是将字符串转换成对应的包装类 , 一个是将对应的基本数据类型转换成包装类

XXValue都是6个 就是将此包装类进行转换成这六个包装类对应的基本数据类型

关于八个包装类的构造方法 ,Character 只有一种char类型的构造方法
Float 有 float double String三种构造方法 ,其他的包装类都提供的是对应基本数据类型 和String 两种构造方法 在手动装箱的时候要注意一下…

③.自动装箱和自动拆箱

上述装箱拆箱的操作过于繁琐,本质上就是想实现基本数据类型和包装类类型之间的快速转换
装箱和拆箱带来不少的代码量,所以为了减少开发者的负担,java 提供了自动机制。

int i = 10; // 基本数据

Integer ii = i; // 自动装箱
Integer ij = (Integer)i; // 自动装箱

int j = ii; // 自动拆箱
int k = (int)ii; // 自动拆箱
float f=(float) ii;

上述代码即实现了自动装箱和自动拆箱

主要通过 = 赋值运算符 直接将基本数据赋值给包装类引用 (自动装箱)
将包装类对象赋值给基本数据类型定义的空间(自动拆箱)

注意:自动装箱和自动拆箱 两边的类型要对应 (不能出现不符合内部设计出现类型完全不匹配的情况)

自动装箱和自动拆箱是怎么完成的呢?
在命令提示符中 进入找到编译运行的字节码文件所在的目录 ,输入java -c 字节码文件 后进行反汇编

通过反汇编可以看到:

自动装箱操作在底层实际是调用了valueOf方法,在底层帮你进行的装箱, 将基本数据类型转换成了对应的包装类 ,不同的包装类调用的valueOf方法的参数是当前包装类对应的基本数据类型
如int装箱 调用的valueOf(int)方法

自动拆箱操作也是在底层调用了intValue方法,将Integer包装类转换成了int基本数据类型的数据, 不同的包装类进行拆箱调用的是不同的XXValue方法
如Integer拆箱 为int 调用 IntValue方法

关于自动装箱自动拆箱的类型检测:
在自动装箱时会进行类型是否匹配的检测 , 包装类类型和基本数据类型之间进行赋值 必须得类型对应 如Integer i=1; Float=1.0L; Long l =1L;两边的类型要对应,否则就会编译报错…
Long l=1; // 此时 整形1对应包装类Long是类型不对应的 是会编译报错的,除非强转

**在自动拆箱时,类型匹配会适当放宽, 自动拆箱时会转换为包装类对应的基本数据类型,此时赋值给某个变量 ,这个变量不需要类型完全匹配, 精度大于等于赋值的数据都可以,编译器会进行隐式类型转换,如果精度不匹配则需要显示强转 **
因此可以看到 int k=(int)ii; 这个语句时,(int)编译器会提示:Casting ‘ii’ to ‘int’ is redundant (将ii 强转为int 是多余的)
在拆箱时,本身ii就是int类型 不用再强转 , 如果是k是byte类型的 则要byte k=(buye)ii; 或者long k =ii ; 都是可以的
包装类类型是不能直接强转为基本数据类型的,基本数据类型也不能直接强转为包装类类型

包装类之间进行赋值操作必须得类型匹配不会存在隐式类型转换 也不能强转 如 Integer i= new Byte(“1”); // 类型不匹配 编译报错

基本数据类型之间的赋值操作存在隐式类型转换和强转…

④.触发自动装箱/拆箱的情况

在java中引入自动装箱/拆箱本质是为了书写代码的方便,但是其中会有一些小细节,不注意就会导致一些错误~

以下是博主整理的一些情况

自动装箱:
1.在使用直接赋值操作时 包装类类型 变量 = 基本数据类型;
2.在进行方法传参时 形参是包装类类型 实参是基本数据类型
3.在方法返回值时 返回类型是包装类类型 返回的值是基本数据类型

注意: Object类型的引用变量 接收基本数剧类型 发生自动装箱, 在方法传参和返回值处 同样适用
因为Object本身是所有类的父类包括包装类 Object obj =1L时, 编译器会进行检测, 最后会将1L包装成Long对象 赋给obj

自动拆箱:
1.两个包装类或者基本数据进行算数运算操作时 (+ – * / % | & ^ …)
2.两个包装类或者包装类数据和基本数据类型进行比较运算操作时 ( > < …) ==有特殊情况
3.包装类对象进行强转操作会拆箱为基本数据类型再强转

注意:
1.进行上述拆箱操作时需要类型对应, 可以Integer 数据和Long类型数据进行运算,但是要注意产生的结果会隐式转换为高精度类型(需要根据实际情况进行强转)但是不能类型不对应 比如: Integer数据和Boolean数据(因为boolean没有确切的数字 只是表示真假 )
但是Integer 和Character进行运算是可以的, 因为对应的char类型数据本质上就是一个整数…
一个包装类和一个基本数据进行操作和上述同样…

2.包装类可以通过强转来拆箱成基本数据类型, 但是基本数据类型不能通过强转包装类进行装箱

3.当两个包装类类型进行比较运算时, 如果判断是否相等则不能使用==等号, 因为编译器会默认当做是两个对象进行比较(比较的是对象的地址)!需要比较是否相等需要调用包装类重写的equals方法
比较大于小于等操作会自动拆箱,并且也提供了比较大小的方法CompareTo
(当一个包装类和一个基本数据类型等号号比较 包装类会进行拆箱)

4.一个包装类和基本数据类型进行比较相等 或者大小时,包装类会进行自动拆箱
通过包装类的.equals 或者compareTo方法也同样可以传基本数据类型 (会将基本数据类型装箱,最后通过一些转换进行比较) ,compareTo本质是调用Integer的compare方法拿两个value进行比较大小
为了避免混淆: 如果比较大小时有存在包装类数据 那么最好就包装类对象用其equals方法或者compareTo方法进行比较



二.包装类的缓存机制

了解缓存机制前先来看一段代码…

public static void main(String[] args) {
        Integer i=1;
        Integer ii=1;
        Integer j=128;
        Integer jj=128;
        Integer k=new Integer(1);
        Integer kk=new Integer(1);

        System.out.println(i==ii);
        System.out.println(j==jj);
        System.out.println(k==kk);
        //输出结果是什么?
    }

按上面分析, 两个包装类对象进行== 比较大小比较的是对象的地址, 此处是四个对象,本应该输出的是三个个false ,但是结果确是 true false false

为什么会出现上述的情况?
拿Integer包装类为例:
首先,自动装箱会调用Integer.valueOf方法, 通过观察源码发现它并不是直接进行实例化对象

首先 在valueOf方法里 拿到要装箱的整形 i 进行判断大小 ,如果大于等于low 小于等于high时则会返回IntegerCache的cache数组里的一个对象

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

通过观察源码发现, Integer包装类里有一个静态内部类 IntegerCache , 在加载外部类Integer时会加载静态内部类, 此时已经确定low的值为-128 , high在 static修饰的静态代码块里最后获得的值是127

IntegerCache里还有个最重要的cache缓存数组引用 , 它最后会指向一个有256个Integer的引用,
且最后每个Integer都会指向一个Integer对象, 从第一个Integer对象开始存放的是-128 直到第256个对象存放的是value值为127的Integer对象

一句话来讲就是:
在装箱时, 会调用Integer的valueOf方法, 会加载静态内部类IntegerCache , 最后其内部会存在一个缓存数组(里面存放了-128~127 共256个Integer对象) , 当调用valueOf方法时对应的整形数据是在[-128,127] 之间时,会直接使用缓存数组里提前创建好的Integer实例, 如果不在这个范围则会自己实例化一个Integr对象

缓存数组也可以看成是一个对象池,它是一种资源预申请, 在加载时就被创建出来, 当在这个要使用对象时,会在缓存数组里寻找是否已经存在, 如果有则使用缓存数组里的, 没有则自己创建…能够在一定程度上减少创建对象的频率 提高效率 (不是必然的,因为本质上加载会消耗资源,当创建对象的频率较低且value值不在区间里时,这个缓存数组的优势便体现不出来)

注意: 只有调用valueOf方法会出现上述情况, 直接实例化是不会在缓存数组里找的

各种包装类缓存机制:

不存在缓存机制的包装类: Boolean Float Double
Boolean只有True和False两种情况,使用此包装类也不频繁
Float和Double因为都是浮点数在内存中存储会存在精度损失问题,不易创建缓存区

存在缓存机制的包装类:Byte Short Character Integer Long
Byte: -128~127 (正好是byte的范围)
Short: -128~127
Character: 0~127 (char不存在负数)
Integer: -128~127
Long:-128~127
对应的基本数据类型调用valueOf方法进行转换成对应包装类对象时,其数值在这些范围内则会使用加载时生成的缓存数组里的对象, 如果不在范围则自己创建对象

为什么多数缓存区域在-128~127
因为缓存本质上是资源预申请, 它也是创建对象,会消耗资源, 如果缓存区域比较大的情况,那么在加载的时候会更消耗资源,在使用时也不一定频繁用到缓存区域 , 而-128~127这个区间是比较小的预先申请不会占据太多资源,这个区间也是一些常用的数值分布区域,进行一些小数值计算就可以不用频繁的创建对象

三.包装类练习题&面试题

1.写出下面代码输出结果

 public static void main(String[] args) {

        Integer i1 = 1;
        Integer i2 = 2;
        Integer i3 = 3;
        Integer i4 = 3;
        Integer i5 = 321;
        Integer i6 = 321;
        Long l1 = 3L;
        Long l2 = 2L;
        Double d1 = 100.0;
        Double d2 = 100.0;
        System.out.println(i3==i4);
        System.out.println(i5==i6);
        System.out.println(d1==d2);
        System.out.println(i3==(i1+i2));
        System.out.println(l1==(i1+i2));
        System.out.println(l1.equals(i1+i2));
        System.out.println(l1.equals(i1+l2));
        System.out.println(i3.equals(i1+i2));

        //输出结果是什么?
    }

涉及到的知识点在上面基本都介绍到:缓存区 里的同一个对象地址是一样的 ,装箱 拆箱 两个包装类用等号比较的是对象地址, 一个包装类一个基本数据则会拆箱比较 , equals则会装箱为包装类进行比较

需要注意的是: 使用equals方法形参是object类型接受 , 接受的基本数据类型会对应装箱为包装类 , 而进行比较时 必须比较的两个包装类类型匹配,如果不匹配哪怕value值相等也是false…

例如第五题: 哪怕Long和Integer里的value值相等,但是 类型不匹配…

2.谈谈Integer i = new Integer(xxx)和Integer i =xxx;这两种方式的区别。

答:
第一种创建方法不会触发自动装箱 ,第二种创建方法会触发自动装箱

在一般情况下,第二种方法比第一种方法执行效率更高,资源占用更低, 因为可能会从缓存数组中拿已经事先创建好的对象使用 … (但是并不是绝对的)

上面知识点也涉及到一些面试知识点, 更多的题后续还会收集补充… 共勉

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2023年7月29日
下一篇 2023年7月29日

相关推荐