【Java入门】之为什么要有包装类 5k字详解

前言

上一篇博客跟各位Java老屁股聊了lang包到底是什么(点击标题阅览文章),其中第一个提到的常用类就是包装类,那这篇博客就来探讨一下《为什么要有包装类》,它能实现什么基本数据类型所实现不了的?接下来就一一说明。

一、包装类是什么

包装类就是把基本数据类型包装成对象(即引用数据类型),使得它们也具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

基本数据类型对应的包装类(引用数据类型)
byteByte
shortShort
intInteger
longLong
charCharacter
floatFloat
doubleDouble
booleanBoolean

继承关系:除了 Character 和 Boolean 之外,其他所有的包装类都继承自 Number 类。这种继承关系使得这些包装类能够共享一些通用的功能和特性,例如数字的比较和转换,这为在不同数值类型之间的操作提供了一致的接口。

查看继承关系的快捷键:

  • Ctrl + h 快捷键可以查看当前类的继承关系

  • 或者在源码界面右键点击“图表”,再点击“显示图”
    如果操作所示:
    图片

  • 以 Integer 为例,继承关系图如下
    图片

二、泛型要求

泛型底层用 Object 来存数据,取出来时再强转成对应的类型。Object是所有类的父类,它只能接收 引用数据类型(对象),不能存 基本数据类型,所以必须用包装类(Integer、Double、Character)把基本数据类型包装成对象。

//ArrayList源码,说明集合底层是个Object[]集合
transient Object[] elementData; // non-private to simplify nested class access

//取出元素时,把Object强转为泛型类型E
E elementData(int index) {
    return (E) elementData[index];
}

三、null 值表示

  • 阿里巴巴开发手册强制规定
    图二

3.1 POJO 属性:用 null 强制提醒

如果一个订单的金额属性是 int,没有给它赋值,会使用默认值 0,变成“零元订单”,这个 Bug 很难发现。
但如果金额属性是 Integer,没有赋值它就是 null。当你后续使用它时,如果没有做非空判断,程序会立刻抛出 NullPointerException(NPE),强制性地提醒你:这里忘记赋值了,必须修复。

3.2 RPC 方法:用 null 表示调用失败

手册中举了一个反例:成交总额涨跌幅 x 如果用基本类型接收,当远程调用失败时,x 会被赋成默认值 0,页面就会显示“0%”,而正确做法应该是显示中划线。
包装类型的 null 恰好能承载“远程调用失败”这个额外信息,让调用方有机会区分“正常返回 0”和“调用出错了”。

3.3 Controller 方法参数:用 null 表示前端没传

  • 错误理解
    有人可能认为:基本数据类型默认值为 0,而包装类默认值为 null。所以在业务场景中,用户没填年龄,如果使用 Integer 就会传默认值 null;使用 int 就会传默认值 0。此时年龄应该用 null 表示,而 0 代表年龄为 0 岁不合适,所以我们要用包装类。
  • 正确理解
    1. 成员变量和局部变量的默认值规则完全不同
    成员变量:int 默认是 0,Integer 默认是 null。
    局部变量:必须显式初始化,没有默认值。
    但 Controller 方法的参数并不是类的成员变量,而是局部变量,需要由 Spring 从请求中绑定数据,没有默认值这一说。所以不能用“int 默认值是 0”来推断“前端不传参就能得到 0”。
    2. Spring 绑定参数
    当前端不传某个参数时,Spring 拿到的值是 null。
    - 如果使用基本类型,Spring 拿到的绑定值是 null,但 int 不是对象,没法接收 null,会直接抛出NPE异常;
    - 而使用包装类,Spring 可以将 null 正常绑定给包装类型,所以 age 为 null。
//报错:int不能存null
int age = null; 

//Integer 可以存 null
Integer age = null; 

3. 配合动态 SQL,安全更新数据库

  • 错误写法
    如果前端没传这个参数,会直接把数据库里的 age 字段更新为 null,覆盖掉原来的值。
UPDATE User
<set>
    age = #{age}
</set>
WHERE id = #{id}
  • 正确写法
    此时,age 为 null 时 if 条件不成立,不会更新年龄字段,完美保留了旧值。
UPDATE User
<set>
    <if test="age != null">
        age = #{age}
    </if>
</set>
WHERE id = #{id}

四、缓存机制

  • 作用
    包装类的缓存机制是为了优化性能和节省内存而设计的。
    它为整型(Byte、Short、Integer、Long)、字符型(Character)和布尔型(Boolean)的包装类都提供了缓存,创建这个范围的整数时,不会新建对象,而是直接返回缓存池里的同一个对象。
    而对于浮点数类型的包装类(Float、Double),则没有这种缓存机制,意味着每次都需要创建新的对象。

  • 使用条件
    该缓存池适用于调用了valueOf()方法产生的包装类对象,以及自动装箱的包装类对象;而不适用于new关键字创建的包装类对象。

  • 缓存范围
    Integer:-128 到 127
    Byte:-128 到 127
    Short:-128 到 127
    Character:0 到 127
    Boolean:true 和 false

Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2);//true
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4);//false
//注意:用 equals 比较的是数值本身,无法体现是否是同一个对象
//而双等号== 比较的是地址,可以体现是同一个对象
System.out.println(i3.equals(i4));//true

拓展
自定义缓存的大小: 通过 JVM 参数 -XX:AutoBoxCacheMax=size 可以设置 Integer 缓存的最大值,最小缓存值固定为 -128,不可修改。

  1. 在当前文件下点击”编辑配置“
    图片

  2. 左侧选中你要运行的主类或Spring Boot 启动类。
    图片

  3. 点击修改选项,再点击添加虚拟机选项
    图片

  4. 名称中填类名,主类填完整类名(包括包名),虚拟机选项填-XX:AutoBoxCacheMax=最大值
    图片

  • 最后效果
Integer i1 = 128;  
Integer i2 = 128;  
System.out.println(i1 == i2);//true  
Short s1 = 128;  
Short s2 = 128;  
System.out.println(s1==s2);//false  
//AutoBoxCacheMax 只对 Integer 生效

五、类型转换

  • 把字符串数值转换成对应的基本数据类型
    八种包装类当中,除了 Character 都有对应的parseXxx的方法,进行类型转换。
    注意:字符串的内容必须是数字,如果是其它类型比如字母"abcd",则会报错。
String s1 = "666";  
int i1 = Integer.parseInt(s1);  
int i2 = Integer.valueOf(s1);
System.out.println(i1);//666  
System.out.println(i2);//666

String s2 = "555.5";  
double i3 = Double.parseDouble(s2);  
System.out.println(i3);//555.5
  • 把基本类型的数据转换成字符串
int i = 3;  
String s = Integer.toString(i);//"3"  
System.out.println(s+1);//"31"

//拓展:转换成字符串的其它方法
int i = 3;
//1.
String s = i + "";
//2.
String s = String.valueOf(i);

六、Character 类的方法

Character 没有 parseXxx,如果想将字符串类型的数据转换成 char 类型的数据,可以通过 String 类中的 toCharArray() 方法和 charAt() 方法实现。
除此之外,它还提供了一系列静态方法来判断字符类型和做大小写转换。

方法名说明
isDigit(char ch)是否是数字
isLetter(char ch)是否是字母
isLetterOrDigit(char ch)是否是字母或数字
isWhitespace(char ch)是否是空白字符(空格、tab、换行)
isUpperCase(char ch)是否是大写字母
isLowerCase(char ch)是否是小写字母
toUpperCase(char ch)转为大写字母
toLowerCase(char ch)转为小写字母
Character.isDigit('5');      // true
Character.isLetter('a');     // true
Character.isUpperCase('A');  // true
Character.toLowerCase('Q');  // 'q'

七、Integer 类的方法

方法名说明
public static String toBinaryString(int i)返回无符号二进制字符串表示
public static String toOctalString(int i)返回无符号八进制字符串表示
public static String toHexString(int i)返回无符号十六进制字符串表示

八、数值常量

MAX_VALUE / MIN_VALUE

System.out.println(Integer.MAX_VALUE);   // 2147483647
System.out.println(Integer.MIN_VALUE);   // -2147483648
System.out.println(Double.MAX_VALUE);    // 1.7976931348623157E308

九、包装类的注意事项

1. == 和 .equals():== 比较的是对象引用,.equals() 比较的是值。

2. 空指针异常(NullPointerException)

Integer num = null;
int value = num; //抛出 NullPointerException

3. 性能开销: 装箱和拆箱涉及对象创建和方法调用,有性能开销。在性能敏感的循环中,应尽量避免不必要的装箱拆箱。

4. 内存占用:一个 Integer 对象比一个 int 占用多得多的内存(约 24 字节 vs 4 字节)。

总结

简单来说,对外输入输出的地方用包装类型,内部临时变量用基本类型。 有什么问题欢迎在评论区指出!!! 要是能点个赞我就更开心了^_^

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值