【Java】包装类

一、什么是包装类?

包装类:基本数据类型对应的引用数据类型。

说白了,就是把基本数据类型变成了一个对象。


二、如何理解包装类?

从内存的角度来看,其实非常容易理解。

首先,我们先来看基本数据类型,所谓基本数据类型,在变量中记录的是真实的数据值。

例如下图的 int i = 10;,真实的数据值就是 10,那么在内存中变量里面记录的也是 10

image-20240424172450958

而整数 int类型 所对应的包装类名字叫做 Integer

可以认为:在Java中,是用 Integer 这个类去描述整数。

Integer 类中,有一个属性:value,用来记录整数的数据值。

在创建对象的时候,跟以前其实是一样的,也是用 new 关键字去创建对象。

此时在内存中栈里面就有一个变量,假设叫做 n,类型就是 Integer

image-20240424173147265

又因为此时,有 new 关键字了,所以在堆中,需要开辟一块小空间,成员变量 value 就记录数据 10,而左边的栈中,变量 n 记录的就是对象的地址值。

此时,上面的 a 就是基本数据类型,下面的 n 就是引用数据类型,也就是我们平时所说的 包装类

因此,包装类本质上其实就是创建了一个对象,对象中记录对应的数据值。

image-20240424173417011

包装类我们可以这么去理解:用一个对象,把数据给包起来。


三、为什么要学习包装类?

在Java中,万物皆对象,也就是所有的东西,我都可以把它看做是一个对象,整数也不例外。

而且由于多态的存在,所有的对象都可以用 Object 来进行表示。

那如果说没有包装类,此时传递过来一个整数,Object 就接收不了,程序就会有局限性。

而且,在集合中,它是不能存基本数据类型的,只能存对象。

image-20240424173654608

因此,我们要往集合中存储基本数据类型的时候,实际上也要用到包装类。

image-20240424173831971

在Java中,八种基本数据类型都有其对应的包装类。

基本类型对应的包装类(位于java.lang包中)
byteByte
shortShort
charCharacter
intInteger
longLong
floatFloat
doubleDouble
booleanBoolean

在以后我们用的最多的就是 Integer,所以我们以 Integer 进行讲解,其他类都是类似的。


四、获取Integer对象的方式(了解)

1)介绍

但是这个知识点只需要大家了解一下就行了,因为在JDK5的时候,对获取对象的方式进行了优化。

在JDK5以前,如果我们要获取包装类的对象,需要通过构造方法自己 new

或者通过一个静态方法 valueOf() 来获取。

image-20240424174217041


2)利用构造方法获取 Integer 对象

这种方式是JDK5以前的方式

括号中可以传入整数,也可以传入一个字符串

Integer i1 = new Integer(1);
Integer i2 = new Integer("1");
System.out.println(i1);
System.out.println(i2);

然后将它们分别打印,程序运行结果如下图。

可以看见,虽然会报错:"Integer(java.lang.String)"自版本 9 起已弃用,并标记为删除,但是程序是可以正常打印的。

image-20240424174624307

3)利用静态方法获取 Integer 对象

这种方式其实也是JDK5以前的方式。

调用静态方法 valueOf(),在括号中可以传入 整数 / 字符串

Integer i3 = Integer.valueOf(123);
Integer i4 = Integer.valueOf("123");
System.out.println(i3); // 123
System.out.println(i4); // 123

除此之外,我们还可以去指定进制

Integer i5 = Integer.valueOf("123", 8);

然后对它做一个打印。

它是将前面的 123 当做是八进制的,在打印的时候就会把这个八进制的 123 转成 十进制进行打印,所以它的结果就是 83

System.out.println(i5); // 83

4)这两种获取对象的区别(掌握)

这点是需要大家掌握的,因为在一些面试题中会进行出现。

我们先来看一段代码。

首先我们需要知道,==号 比较的是地址值,如果它的结果是 true,就表示:前后是同一个对象。

如果是 false,就表示前后不是同一个对象。

Integer i6 = Integer.valueOf(127);
Integer i7 = Integer.valueOf(127);
System.out.println(i6 == i7); // true

Integer i8 = Integer.valueOf(128);
Integer i9 = Integer.valueOf(128);
System.out.println(i8 == i9); // false

Integer i10 = new Integer(127);
Integer i11 = new Integer(127);
System.out.println(i10 == i11); // false

Integer i12 = new Integer(128);
Integer i13 = new Integer(128);
System.out.println(i12 == i13); // false

为什么结果是上面这样呢?

如果没有看源码,那么下面两段很好理解,因为看到了 new 关键字,在Java中,每一次 new 都是创建了一个新的对象,所以下面的两个对象都是 new 出来的,地址值不一样,因此比较的时候肯定就是 false 了。


但是上面两个,用静态方法 valueOf() 获取出来的,为什么有一个是 true 呢?

此时我们就需要来看一下 valueOf() 的底层源码。选中 valueOf() ctrl + b 跟进。

在源码中,形参叫 i,这个就是我们传递过来的整数。

在下面,它会对 i 来做一个判断,如果 i[IntegerCache.low, IntegerCache.high],那么就从这里去获取 Integer 的对象。

如果不在这个范围里面,才自己 new

1080行 的代码中有一个方括号,即数组,所以我就猜,这里的 cache 是不是表示一个数组。

image-20240424183533880

选中 cache ctrl +b,此时就能很直观的看见了,它是一个 Integer 类型的数组,数组的名字叫做:cache

image-20240424183747591

ctrl + alt + ← 回到上一步。此时我们就知道了,如果传递过来的参数在一个范围中,我们就不会自己 new,而是从一个数组中去获取 Integer对象 返回。

那么这个范围是多少呢?ctrl +b 跟进 low。可以发现它的值是 -128

image-20240424183937269

然后 ctrl + alt + ←ctrl +b 跟进 high,就可以看见 high 是一个成员变量。

下面有一块静态代码块,在静态代码块中,它定义了一个变量 h ,记录的值是 127

然后在 1039行 中,它将 127 赋值给了 high,因此我们知道了,刚刚看到的范围其实是 byte类型 的取值范围:[-128, 127]

image-20240424184157030

静态代码块中首先会创建一个 Integer 数组,数组的长度 size 它是这么计算的 (high - low) + 1+ 1 是因为在 -128 - 127 之间,它还有一个数据 0,也就是说在这个范围内,一共有 256 个数据。

那么下面数组长度也是 256

image-20240424184822433

在下面有一个数组,然后遍历数组,给数组中的每个值进行赋值,这里就是通过创建对象来赋值的 c[i] = new Integer(j++)

j 的初始值是 low,即 -128,然后将 -128 创建一个 Integer 的对象,方法数组的 0 索引.

创建完后,j++ 变成 -127i++,变成 1索引,将 -127 放到了 1索引 中。

image-20240424185144892

这样我们就知道了,Integer 在底层实际上会把 [-128, 127] 之间所有的数据都创建好 Integer 的对象放到 cache 数组中。

当我们用 valueOf() 方法去获取到对象的时候,它会判断你的这个数据是不是在 [-128, 127] 之间,如果在,它不会创建新的,而是从数组中将已经创建好的对象给你返回。

如果不在这个范围内,它才会去 new

image-20240424185400636

因此在刚刚的代码中我们就知道了,127 是在范围内,因此获取的都是同一个对象。而 128 超出范围了,那么每次都是 new 的。

那Java为什么这么设计呢?

因为在我们实际开发中, [-128, 127] 之间的数据它用的比较多,如果每次使用都是 new,那么太浪费内存了,因此Java就将 [-128, 127] 范围内的每一个数据都创建好对象,如果要用到了不会创建新的,而是返回已经创建好的对象。


5)包装类如何进行计算?

① 以前的方式

Integer i1 = new Integer(1);
Integer i2 = new Integer(2);

需求:要把两个数据进行相加得到结果 3

对象直接是不能直接进行计算的,在以前,如果我们想要计算的话,步骤如下:

1、把对象进行拆箱(变成基本数据类型),调用 intValue() 方法。

2、相加

int result = i1.intValue() + i2.intValue();

3、把得到的结果再次进行装箱(再变回包装类)

Integer i3 = new Integer(result);

最后再来打印 i3

System.out.println(i3); // 3

上面是以前的写法,写起来太麻烦了。用的不爽,就需要改进。

因此在 JDK5 的时候提出了一个机制:自动装箱和自动拆箱。


② 现在的方式

首先来解释一下 自动装箱和自动拆箱,所谓自动,就是不需要我们额外去写代码,Java底层都帮你做好了。

自动装箱:基本数据类型会自动的变成其对应的包装类

自动拆箱:包装类自动的变成其对象的基本数据类型

因此,现在我们就能这么去写

Integer i1 = 10;

在底层,此时还会去自动调用静态方法 valueof() 得到一个 Integer对象,只不过这个动作不需要我们自己去操作了。

Integer i2 = new Integer(10);,此时可以直接将 i2 赋值给一个基本数据类型 i。这个就是自动拆箱。

int i = i2;

因此,在JDK5以后,intInteger 可以看做是同一个东西,因为在内部可以自动转换。


6)总结

1、什么是包装类?

包装类其实就是基本数据类型所对应的对象。

2、JDK5以后对包装类新增了什么特性?

自动装箱(基本数据类型会自动的变成其对应的包装类)、自动拆箱(包装类自动的变成其对象的基本数据类型)

3、我们以后如何获取包装类对象?

获取对象我们以后不需要 new,也不需要调用方法,直接赋值就行了

Integer i = 10;

如果想要让包装类进行计算,我们也不需要手动拆箱,直接拿对象 i1i2 进行相加就行了,它的底层会先进行拆箱,然后进行计算,最后再进行装箱赋值给 i3,这些操作都是Java底层自动去完成的。

Integer i1 = 10;
Integer i2 = 10;
Integer i3 = i1 + i2;

五、Integer 成员方法

1)总述

常见的成员方法一共有 4个,前面三个都是类似的:将一个整数分别变成二进制、八进制、十六进制。

我们要注意的是:方法的返回值是 String

但是为什么返回值是字符串呢?而不是 int / long 类型的呢?

  • 二进制有可能是 010101 这种形式,如果是一个整数类型,0 是不能开头的。

  • int / long 它们是有长度的限制的,以 int类型 为例,它最多可以取到 21个亿,也就是说 int类型 最多只能有十位。

    但是二进制有可能是20多位、40多位…因此 int / long 有可能装不下,只有 String 才可以。

方法名说明
static string tobinarystring(int i)得到二进制
static string tooctalstring(int i)得到八进制
static string toHexstring(int i)得到十六进制

再来看最后一个方法:parseInt(),利用这个方法,我们就可以做类型的转换。

static int parseInt(string s)将字符串类型的整数转成int类型的整数

常见的这四个方法都是静态的,所以我们在调用的时候,就可以用类名直接调用。


2)把整数转为二进制、八进制、十六进制

//1.把整数转成二进制,十六进制
String str1 = Integer.toBinaryString(100);
System.out.println(str1);//1100100

//2.把整数转成八进制
String str2 = Integer.toOctalString(100);
System.out.println(str2);//144

//3.把整数转成十六进制
String str3 = Integer.toHexString(100);
System.out.println(str3);//64

这三个方法我们在看源码的时候就会经常看见,我们之前在看 Object源码 的时候就看见过,我们可以再来看一下。

ctrl + N 搜索 java.lang包 下的 Object

image-20240424193032115

然后再找里面的 toString() 方法,这里就用到了 Interger.toHexString(),它会将对象的地址值转成十六进制的形式给你进行返回。

image-20240424193247027

3)将字符串参数转换为对应的基本类型

这个方法是非常非常非常重要的,在以后我们会大量去用到。

因为Java是一种强类型语言。

强类型语言:每种数据在Java中都有各自的数据类型。

在计算的时候,如果不是同一种数据类型,是无法直接计算的,需要转成一样的才可以。

例如现在有个字符串类型的 "123" 和 整数类型的 123 相加,结果我想得到 246,这该怎么办呢?

如果字符串和整数直接相加,它是不会直接进行计算的,而是做一个拼接

"123" + 123 // 123123

但如果你要将前面的字符串当成整数,你就必须得先将字符串变成整数才能计算。

因此此时我们需要使用 Integer.parseInt() 进行字符串的类型转换。

int i = Integer.parseInt("123");
System.out.println(i); // 123
// 为了判断 i 是否真的转成了整数123,在这里打印 123 + 1,如果它真的转成了整数123,那么打印结果应该为124
System.out.println(i + 1);//124

String str = "true";
boolean b = Boolean.parseBoolean(str);
System.out.println(b);

细节1:在类型转换的时候,括号中的参数只能是数字不能是其他,否则代码会报错 java.lang.NumberFormatException(数字格式化异常):For input string: “abc”(对于输入字符串:“abc”)

image-20240424193958879

细节2:8种包装类当中除了Character类之外,其他所有包装类都具有parseXxx静态方法可以将字符串参数转换为对应的基本类型

  • public static byte parseByte(String s):将字符串参数转换为对应的byte基本类型。
  • public static short parseShort(String s):将字符串参数转换为对应的short基本类型。
  • public static int parseInt(String s):将字符串参数转换为对应的int基本类型。
  • public static long parseLong(String s):将字符串参数转换为对应的long基本类型。
  • public static float parseFloat(String s):将字符串参数转换为对应的float基本类型。
  • public static double parseDouble(String s):将字符串参数转换为对应的double基本类型。
  • public static boolean parseBoolean(String s):将字符串参数转换为对应的boolean基本类型。

注意:如果字符串参数的内容无法正确转换为对应的基本类型,则会抛出 java.lang.NumberFormatException 异常。

有了这个类型转换之后,在以后我们就要改写键盘录入的代码了。


4)改写键盘录入

在正式学习之前,先回顾一下之前讲的键盘录入。

System.out.println("请输入一个整数"); 
int i = sc.nextInt();
System.out.println(i);

如果我要接收一个字符串,方法就要变成 next()

Scanner sc = new Scanner(System.in);
System.out.println("请输入一个字符串");
String str = sc.next();
System.out.println(str);

但是这种输入方式其实是有一些小小的弊端的。

当我们在使用 next,nextInt,nextDouble 在接收数据的时候,遇到空格,回车,制表符的时候就停止了。

如果键盘录入的是 123 123 那么此时只能接收到空格前面的数据,但这不是我想要的,我想要的是接收一整行数据。

因此在这我们跟大家来做一个约定:以后我们如果想要键盘录入,不管什么类型,统一使用 nextLine(),这个方法会接受一整行数据,将空格、制表符全部接收完毕,它的特点是:遇到回车才停止

String line = sc.nextLine(); // 输入:123 123
System.out.println(line); // 123 123

但如果想要整数 / 小数,拿到字符串后再去进行数据转换

int i = Integer.parseInt(line);
System.out.println(i);

double v = Double.parseDouble(line);
System.out.println(v);
  • 41
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值