【Java入门】之为什么要有包装类 5k字详解
文章目录
- 前言
- 一、包装类是什么
- 二、泛型要求
- 三、null 值表示
- 3.1 POJO 属性:用 null 强制提醒
- 3.2 RPC 方法:用 null 表示调用失败
- 3.3 Controller 方法参数:用 null 表示前端没传
- 四、缓存机制
- 五、类型转换
- 六、Character 类的方法
- 七、Integer 类的方法
- 八、数值常量
- 九、包装类的注意事项
- 总结
前言
上一篇博客跟各位Java老屁股聊了lang包到底是什么(点击标题阅览文章),其中第一个提到的常用类就是包装类,那这篇博客就来探讨一下《为什么要有包装类》,它能实现什么基本数据类型所实现不了的?接下来就一一说明。
一、包装类是什么
包装类就是把基本数据类型包装成对象(即引用数据类型),使得它们也具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。
| 基本数据类型 | 对应的包装类(引用数据类型) |
|---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
继承关系:除了 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><iftest="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,不可修改。
在当前文件下点击”编辑配置“
左侧选中你要运行的主类或Spring Boot 启动类。
点击修改选项,再点击添加虚拟机选项
名称中填类名,主类填完整类名(包括包名),虚拟机选项填-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; //抛出 NullPointerException3. 性能开销: 装箱和拆箱涉及对象创建和方法调用,有性能开销。在性能敏感的循环中,应尽量避免不必要的装箱拆箱。
4. 内存占用:一个 Integer 对象比一个 int 占用多得多的内存(约 24 字节 vs 4 字节)。
总结
简单来说,对外输入输出的地方用包装类型,内部临时变量用基本类型。 有什么问题欢迎在评论区指出!!! 要是能点个赞我就更开心了^_^
