小白学Java:包装类

学习了许久的Java,我们知道Java是一种面向对象的语言,万物皆对象。但是我们之前在说到Java基本数据类型的时候,由于处理对象需要额外的系统开销,于是出于对性能的考虑,基本数据类型并不做为对象使用

既然是面向对象的,在Java中许多方法需要把对象作为参数,但是基本类型变量身上没有任何方法和属性,于是Java提供了一个简单的方法,就是为每一个基本数据类型类型都配套提供一个包装类型,我们便可以在两者之间来回反复地横跳。

包装类的继承关系

先看一波包装类型的继承图:

数值类型都直接继承于父类Number类,非数值类型CharacterBoolean直接继承于Object类

除此之外,包装类型的名字也非常好记,除了int->Integerchar->Character两个比较特殊之外,其他都是基本数据类型的首字母改为大写即可,如:byte->Byte

通过查看官方文档,我们可以发现,数值类型继承的Number类其实是一个抽象类,那么可想而知,该类中的抽象方法已经在这几个数值类型中得到实现,看一波:

很明显,除了最后一个serialVersionUID(这个以后再总结),其他的方法在数值型包装类中都存在,可以通过这些方法将对象“转换”为基本类型的数值。

创建包装类实例

我们再来看看包装类型的构造器,我们再查看所有包装类之后,发现:

  • 所有的包装类型都不存在无参构造器
  • 所有包装类的实例都是不可变的。
  • 一旦创建对象后,它们的内部值就不能进行改变。

    在JDK1.5之前,我们可以这样把基本数据类型转换为包装类对象,这个过程也叫做装箱,当然反向的过程叫做拆箱:
Integer i1 = new Integer(5);//5
Integer i2 = new Integer("5");//5
  • 第一句调用的是传入int类型参数的构造器,this.value = value,一目了然。
  • 第二句调用的是传入String类型参数的构造器,其实又是调用了静态方法parseInt(String s,int radix):
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}

深究一下,parse(String s,int radix)中的radix其实代表着进制信息,而我们的构造器默认让radix为10,代表着输出字符串s在十进制下的数,所以除了数字0-9之外,字符串中不能有其他的玩意儿,否则会抛出NumberFormatException的异常。

自动装箱与拆箱

我们在上面说过,基本数据类型和包装类型之间的转换涉及到装箱与拆箱的操作,为了简化代码,在JDK1.5之后,Java允许基本类型和包装类型之间可以自动转换。

自动装箱

将基本类型直接赋值给对应的引用类型,编译器在底层自动调用对应的valueOf方法。

就像下面这样:

int i = 5;
Integer in = i;

我们利用debug调试工具设上断点,发现在执行Integer in = i; 时,将会自动调用下面的方法:

继续深究其底层实现,我们发现IntegerCache其实是Integer包装类的一个内部类,我们进入IntegerCache一探究竟:

我们会发现所有的整数类型的(包括Character)包装类里都有类似的玩意儿,所以大致运行的规则应该大致相同,在这里就总结几点不太一样的:

  • 只有Integer包装类才可以更改缓存大小。
  • Character容量只有128

    浮点数类型包装类并不存在缓存机制,是因为在一定的范围内,该类型的数值并不是有限的。

    看到这,我们大致就可以得出结论,整数数值类型在自动装箱的时候会进行判断数值的范围,如果正好在缓存区,那么就不必创建新的对象,它们将会指向同一地址。Java中另一个例子就是我们说的字符串常量池。

    所以下面很火的几条语句,结果就很明显了:
int num = 100;
Integer i1 = num;
Integer i2 = num;
System.out.println(i1==i2);//true
//num改为200,结果为false
Integer i1 = 100;
Integer i2 = new Integer(100);
System.out.println(i1 == i2);//false

自动拆箱

将引用类型字节赋值给对应的基本类型,编译器在底层自动调用对应的xxxvalue方法(如intValue)。

Integer in = 5;
int i = in;

自动拆箱相对来说就稍微简单一点了,我们还是利用debug工具,发现上面的代码将会自动调用下面的方法

包装类型的比较

"=="比较

int num = 100;
Integer i1 = num;
Integer i2 = num;
//都是包装器类型的引用时,比较是否指向同一对象。
System.out.println(i1==i2);//true Integer i1 = 128;
int i2 = 128;
//如果包含算数运算符,则底层自动拆箱,即比较数值。
System.out.println(i1 == i2);//true
Integer i3 = 1;
Integer i4 = 129;
System.out.println(i4 == i1+i3);//true

equals比较

equals比较的是同一包装类型,即比较两者数值是否相等

Integer i1 = 5;
Integer i2 = 5;
Integer i3 = 10;
//同一包装类型,比较数值是否相等
System.out.println(i1.equals(i2));//true
System.out.println(i3.equals(i1+i2));//true Long l1 = 5L;
Long l2 = 10L;
//Long与Integer比较,不是同一类型,false
System.out.println(l1.equals(i1));//false
//先自动拆箱,i1先转为int,l转为long,int自动类型提升转为long,最后相等
System.out.println(l2.equals(l1+i1));//true

自动装箱与拆箱引发的弊端

自动装箱弊端

Integer sum = 0;
for(int i = 500;i<5000;i++){
//先自动拆箱,而后自动装箱
sum+=i;
}

在拆箱装箱操作之后,由于sum数值超过缓存范围,所以会new出4500个毫无用处的实例对象,大大影响了程序的性能。所以在循环语句之前,务必声明正确的变量类型。

自动拆箱引起的空指针

private static Integer sum;
public static void setSum(Integer num,boolean flag){
sum = (flag)?num:-1;
}

上面的代码,当num传入为null时,即会引发空指针异常,因为包装类在进行算术运算时(上述是三目运算),如果数据类型不一致,将会先自动拆箱转换成基本类型进行运算,而null如果调用了intValue()方法就会形成空指针。

改进方案:

public static void setSum(Integer num,boolean flag){
//这样类型一致,便不会自动拆箱了
sum = (flag)?num:Integer.valueOf(-1); }

参考链接:

Java 自动装箱与拆箱的实现原理

Java的自动装箱、拆箱

Integer缓存池(IntegerCache)及整型缓存池

Java中的自动装箱与拆箱

小白学Java:包装类的更多相关文章

  1. 小白学Java:老师!泛型我懂了!

    目录 小白学Java:老师!泛型我懂了! 泛型概述 定义泛型 泛型类的定义 泛型方法的定义 类型变量的限定 原生类型与向后兼容 通配泛型 非受限通配 受限通配 下限通配 泛型的擦除和限制 类型擦除 类 ...

  2. 小白学Java:迭代器原来是这么回事

    目录 小白学Java:迭代器原来是这么回事 迭代器概述 迭代器设计模式 Iterator定义的方法 迭代器:统一方式 Iterator的总结 小白学Java:迭代器原来是这么回事 前文传送门:Enum ...

  3. 小白学Java:奇怪的RandomAccess

    目录 小白学Java:奇怪的RandomAccess RandomAccess是个啥 forLoop与Iterator的区别 判断是否为RandomAccess 小白学Java:奇怪的RandomAc ...

  4. 小白学Java:内部类

    目录 小白学Java:内部类 内部类的分类 成员内部类 局部内部类 静态内部类 匿名内部类 内部类的继承 内部类有啥用 小白学Java:内部类 内部类是封装的一种形式,是定义在类或接口中的类. 内部类 ...

  5. 小白学Java:File类

    目录 小白学Java:File类 不同风格的分隔符 绝对与相对路径 File类常用方法 常用构造器 创建方法 判断方法 获取方法 命名方法 删除方法 小白学Java:File类 我们可以知道,存储在程 ...

  6. 小白学Java:I/O流

    目录 小白学Java:I/O流 基本分类 发展史 文件字符流 输出的基本结构 流中的异常处理 异常处理新方式 读取的基本结构 运用输入与输出 文件字节流 缓冲流 字符缓冲流 装饰设计模式 转换流(适配 ...

  7. 小白学Java:RandomAccessFile

    目录 小白学Java:RandomAccessFile 概述 继承与实现 构造器 模式设置 文件指针 操作数据 读取数据 read(byte b[])与read() 追加数据 插入数据 小白学Java ...

  8. 【aliyun】学java,看这里,不迷茫!1460道Java热门问题

    阿里极客公益活动: 或许你挑灯夜战只为一道难题 或许你百思不解只求一个答案 或许你绞尽脑汁只因一种未知 那么他们来了,阿里系技术专家来云栖问答为你解答技术难题了 他们用户自己手中的技术来帮助用户成长 ...

  9. 学Java必看!零基础小白再也不用退缩了

    程序员们!请往这儿看 对于JAVA的学习,可能你还会有许多的顾虑 不要担心 接着往下看吧 学Java前 一.数学差,英语也不好是不是学不好Java? 答案是:是~ 因为你在问这个问题的时候说明你对自己 ...

随机推荐

  1. 2、Dapper的使用

    1.表结构介绍: 1)课程表 2)成绩表 3)学生表  2.获取数据库连接的工厂类 需要添加System.Configuration和MySql.Data.MySqlClient引用 namespac ...

  2. HDU 1879 还是prim最小生成树、

    #include<stdio.h> #include<math.h> #include<string.h> +,MAX=1e7; int vis[qq]; int ...

  3. 条件随机场(CRF) - 4 - 学习方法和预测算法(维特比算法)

    声明: 1,本篇为个人对<2012.李航.统计学习方法.pdf>的学习总结,不得用作商用,欢迎转载,但请注明出处(即:本帖地址). 2,由于本人在学习初始时有很多数学知识都已忘记,所以为了 ...

  4. springboot 实现 aop

    pom.xml 导入 springboot aop 依赖 <dependency> <groupId>org.springframework.boot</groupId& ...

  5. 析构函数 p157

    析构函数 确保对象的各部分被正确的清除,及做一些用户指定的其他清理工作. 当对象超出它的作用域时,编译器将自动调用析构函数:手动用new在堆上分配的对象空间,需要调用'delete 对象地址'进行手动 ...

  6. 【codeforces 789B】Masha and geometric depression

    [题目链接]:http://codeforces.com/contest/789/problem/B [题意] 让你一个一个地写出等比数列的每一项 (注意是一个一个地写出); 有m个数字不能写; 且数 ...

  7. Python3 dir() 函数

    Python dir() 函数 描述 dir() 函数不带参数时,返回当前范围内的变量.方法和定义的类型列表:带参数时,返回参数的属性.方法列表.如果参数包含方法__dir__(),该方法将被调用.如 ...

  8. 判断是否是ie浏览器或者edge浏览器,引入特定的css

    判断是否是ie浏览器或者edge浏览器,引入特定的css 我本来要用ie浏览器专有的条件注释语句来引入,但是发现都没有效果,网上有说ie10之后的浏览器取消了条件语句,反正我是只要是IE都没有试出效果 ...

  9. video视频标签一些设置,包括封面、播放结束后的封面、视频占满屏幕的方式、视频播放暂停、展示控制栏、触发全屏播放事件

    video视频标签一些设置,包括封面.播放结束后的封面.视频占满屏幕的方式.视频链接.视频播放暂停.展示控制栏.触发全屏播放事件 <video id="video" auto ...

  10. vue-learning:34 - component - 内置组件 - 缓存组件keep-alive

    vue内置缓存组件keep-alive <keep-alive>标签内包裹的组件切换时会缓存组件实例,而不是销毁它们.避免多次加载相应的组件,减少性能消耗.并且当组件在 <keep- ...