拆箱装箱都不知道?(从源码带你理解包装类)

众所周知,java 是一门面向对象的高级语言。但是 java 中的基本类型不能作为对象使用,为了解决对象的调用问题,为每个基本类型创造了对应的包装类型。
先来看一道包装类的题目吧

int a = 10;
Integer b = 10;
System.out.println(a == b);        ①
System.out.println(b.equals(a));   ②
Integer c = 100;
Integer d = 100;
System.out.println(c == d);        ③
System.out.println(c.equals(d));   ④
Integer e = new Integer(100);
Integer f = new Integer(100);
System.out.println(e == f);        ⑤
System.out.println(e.equals(f));   ⑥
Integer g = 1000;
Integer h = 1000;
System.out.println(g == h);        ⑦
System.out.println(g.equals(h));

在看知识点前先看一下习题,看看是否存在不了解的点,以便对症下药
这些题考查的都是 java 中很基础的一类概念
包括包装类与基本类型的转换,包括自动拆箱装箱
以及 == 和 equals 的区别

给上答案
①true ②true ③true ④true ⑤false ⑥true ⑦false ⑧true
以下示范都以Integer为例,其他包装类型与之无明显差异,大家看会了 Integer 其他包装类也就都能理解了

为什么需要包装类

  • 基本类的值不属于对象,因此基本类型只存在一个值,没有各种属性,也没有各种方法可以调用。
  • 很多场景需要使用对象,比如 java 中的集合,所以为了能使基本类型的值也能存入集合中,或者用于其他类中,则需要把它们包装成对象。
  • 每个非基本类都继承自基类 Object ,所以天生神力,自带一身方法。但是基本类型没有方法可以调用,用包装类可以继承 Object 方法,也可以添加自身方法。
  • 非基本类型变量不赋值默认为 null,但基本类型没有 null 值,比如 int 默认为 0,Boolean 默认为 false。
  • 在某些场合中,我们可能会希望使用到 null 值,表示某些意义,那基本类型则就做不到了。

包装类与基本类型的转换 (包括自动装拆箱)

JDK自从1.5之后就引入了自动装拆箱的功能,这大大简化了开发人员的代码复杂度,使得对基本类型的代码书写效率大大提高。
但是这也带来了一个问题,很多小白搞不明白基本类型和包装类型,使很多开发人员对此形成误解。

首先,最基本的,用 new 来构造一个 Integer 对象
再用 Integer 的 intValue() 方法返回它的int值

Integer a = new Integer(1);
int b = a.intValue();

但在 java 中一般不提倡 new 一个 Integer 包装类对象
用 Integer.valueOf() 静态方法来获取一个 Integer 对象

a = Integer.valueOf(5);
Integer a = 5; // 也可以写成

而这个方法即是 int --> Integer 的自动装箱方法 (即上面两行代码是等同的)
同理自动拆箱

Integer a = 5;
// int b = a.intValue();
int b = a; // 这行代码与上面一行代码等同

比如之前题目中的①和② 就有许多装拆箱的知识点

int a = 10;
Integer b = 10; // 自动装箱
// 将b自动拆箱与a比较
System.out.println(a == b);
// 将a自动装箱成为对象才能作为equals中的参数
System.out.println(b.equals(a));

但是在自动装箱需要注意一个细节,也就是之前题目中的③④⑦⑧。
在 -128 ~ 127 的数中,也就是 byte 的取值范围,自动装箱时,会从常量池中去取Integer对象,因而包装出的 Integer 对象是同一个对象,不在这个范围内的数字,包装出的 Integer 对象则都是 new 出来的新对象。

Integer c = 100;  // 从常量池中取出Integer对象
Integer h = 1000; // 相当于用new创建出一个新对象

因此在之前题目中值为 100 的 Integer 对象 == 为 true,而值为 1000 的Integer对象则为 false。

基本类型和包装类型与String的转换

由于存在自动装拆箱的功能,所以对于 String 与数字转换时,可以将其转换为基本类型 int,也可以转换为包装类 Integer,通过自动装拆箱即可以正确赋值给变量
String 和 Integer 之间的转换

// String --> Integer
Integer a = new Integer("12345");
// Integer --> String
String s = a.toString();

String 和 int 之间的转换

// String --> int
int a = Integer.parseInt("12345");
// String --> Integer --> int
a = new Integer("12345");
// int --> Integer --> String
String s = a + ""; //自动装箱调用toString()
s = (Integer.valueOf(a)).toString();

包装类基础

基本类包装类继承基本类类信息包装类对应类信息
byteByteNumberbyte.classByte.TYPE
shortShortNumbershort.classShort.TYPE
intIntegerNumberint.classInteger.TYPE
longLongNumberlong.classLong.TYPE
floatFloatNumberfloat.classFloat.TYPE
doubleDoubleNumberdouble.classDouble.TYPE
charCharacterObjectchar.classCharacter.TYPE
booleanBooleanObjectboolean.classBoolean.TYPE

一些关键源码(解释写在注释之中)

首先是 Integer 的范围,与 int 相同,都为 -2的31次方~2的31次方-1。
其中0x表示16进制,但是仔细一看,会发现最小值比最大值大一,这是因为在二进制中负数是用补码表示的,最高位是符号位,0是正,1位负,所以正数最大值没有问题,而负数最小值则根据首位1推出为负数。
对于补码不细讲,只需记住
正数的补码 = 原码
负数的补码 = 原码符号位不变 + 数值位按位取反后+1
或者 = 原码符号位不变 + 数值位从右边数第一个1及其右边的0保持不变,左边安位取反

@Native public static final int   MIN_VALUE = 0x80000000;
@Native public static final int   MAX_VALUE = 0x7fffffff;

构造方法很简单

public Integer(int value) {
    this.value = value;
} // 给指定值创建对象
public Integer(String s) throws NumberFormatException {
    this.value = parseInt(s, 10);
} // 通过字符串解析出值创建对象

装箱方法

public static Integer valueOf(int i) {
    // 如果值在low和high之间 low为-128 high一般默认为127
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        // 则直接返回常量池中的对象
        return IntegerCache.cache[i + (-IntegerCache.low)];
    // 否则用new新建一个Integer对象返回
    return new Integer(i);
}

在源码中我们发现了一个静态嵌套类IntegerCache,它拥有两个 final 值 low 和 high,代表缓存的 Integer 的对象的范围,这样在装箱时可以直接获取,而不用再创建对象。

private static class IntegerCache {
    static final int low = -128;  // 缓存最小值
    static final int high;        // 缓存最大值
    // low到high的Integer对象都被保存在这里 装箱时取
    static final Integer cache[];
    // 根据类加载机制 以下方法块在类初始化时执行
    // 会率先将low到high的Integer保存在cache数组中
    static {
        int h = 127;
        /* 此处省略一些无关代码  */
        high = h;
        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);
    }
    // 私有构造方法 保证类无法创建对象
    // 因为该类仅作为事先缓存Integer对象
    private IntegerCache() {}
}

再看toString()方法

public static String toString(int i) {
    if (i == Integer.MIN_VALUE) //为最小值则直接return
        return "-2147483648";
    // 计算数字的长度
    int size = (i < 0) ? stringSize(-i) + 1 : stringSize(i);
    // 根据长度创建字符数组
    char[] buf = new char[size];
    // 不断获取字符放入数组中
    getChars(i, size, buf);
    // 根据字符数组返回新字符串
    return new String(buf, true);
}

里面的 stringSize 方法很有趣,很多情况一般会用循环除10来计算长度,不众所周知,JDK源码是非常讲究效率的,它直接保存了在 MAX 范围内的所有每个长度最大值,直接依次比较,十分高效。

final static int [] sizeTable = { 9, 99, 999, 9999, 
    99999, 999999, 9999999, 
    99999999, 999999999, Integer.MAX_VALUE };
static int stringSize(int x) {
    for (int i=0; ; i++)
        if (x <= sizeTable[i])
            return i+1;
}

总之对于我们来说,java 中的每一个基础类我们都应把它学习透彻,理解它的执行原理,尽量对源码中的关键方法都能掌握,这样在我们的编程生涯中,才能做到游刃有余,不犯一些低级错误。
同时,jdk 的源码都是十分优秀,高效的,在 jdk 版本不断迭代的过程中,很多关键类的代码都会进行优化和调整,确保程序的高效执行。

文章到这里就结束啦,我还是大学生哦,喜欢学习的小伙伴可以评论交流,或者加关注,一起学习更轻松。
素质三连。。。

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值