文章目录
一、什么是包装类?
包装类:基本数据类型对应的引用数据类型。
说白了,就是把基本数据类型变成了一个对象。
二、如何理解包装类?
从内存的角度来看,其实非常容易理解。
首先,我们先来看基本数据类型,所谓基本数据类型,在变量中记录的是真实的数据值。
例如下图的 int i = 10;
,真实的数据值就是 10
,那么在内存中变量里面记录的也是 10
。
![image-20240424172450958](https://img-blog.csdnimg.cn/img_convert/7a61f748e609d7f591ffe843504211ef.png)
而整数 int类型
所对应的包装类名字叫做 Integer
。
可以认为:在Java中,是用 Integer
这个类去描述整数。
在 Integer
类中,有一个属性:value
,用来记录整数的数据值。
在创建对象的时候,跟以前其实是一样的,也是用 new
关键字去创建对象。
此时在内存中栈里面就有一个变量,假设叫做 n
,类型就是 Integer
。
又因为此时,有 new
关键字了,所以在堆中,需要开辟一块小空间,成员变量 value
就记录数据 10
,而左边的栈中,变量 n
记录的就是对象的地址值。
此时,上面的 a
就是基本数据类型,下面的 n
就是引用数据类型,也就是我们平时所说的 包装类
。
因此,包装类本质上其实就是创建了一个对象,对象中记录对应的数据值。
包装类我们可以这么去理解:用一个对象,把数据给包起来。
三、为什么要学习包装类?
在Java中,万物皆对象,也就是所有的东西,我都可以把它看做是一个对象,整数也不例外。
而且由于多态的存在,所有的对象都可以用 Object
来进行表示。
那如果说没有包装类,此时传递过来一个整数,Object
就接收不了,程序就会有局限性。
而且,在集合中,它是不能存基本数据类型的,只能存对象。
![image-20240424173654608](https://img-blog.csdnimg.cn/img_convert/e342976d8ac53c41e31c00115e9bddac.png)
因此,我们要往集合中存储基本数据类型的时候,实际上也要用到包装类。
![image-20240424173831971](https://img-blog.csdnimg.cn/img_convert/6f364712fce8cde2df553cca3de688ad.png)
在Java中,八种基本数据类型都有其对应的包装类。
基本类型 | 对应的包装类(位于java.lang包中) |
---|---|
byte | Byte |
short | Short |
char | Character |
int | Integer |
long | Long |
float | Float |
double | Double |
boolean | Boolean |
在以后我们用的最多的就是 Integer
,所以我们以 Integer
进行讲解,其他类都是类似的。
四、获取Integer对象的方式(了解)
1)介绍
但是这个知识点只需要大家了解一下就行了,因为在JDK5的时候,对获取对象的方式进行了优化。
在JDK5以前,如果我们要获取包装类的对象,需要通过构造方法自己 new
。
或者通过一个静态方法 valueOf()
来获取。
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](https://img-blog.csdnimg.cn/img_convert/676e29a014e368bb8d9d202f4d4b8718.png)
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](https://img-blog.csdnimg.cn/img_convert/b2898b66c0143c8ca326abd5b38eaac4.png)
选中 cache
ctrl +b,此时就能很直观的看见了,它是一个 Integer
类型的数组,数组的名字叫做:cache
![image-20240424183747591](https://img-blog.csdnimg.cn/img_convert/9b1d420d9bc45591b2446d096ebd3670.png)
ctrl + alt + ← 回到上一步。此时我们就知道了,如果传递过来的参数在一个范围中,我们就不会自己 new
,而是从一个数组中去获取 Integer对象
返回。
那么这个范围是多少呢?ctrl +b 跟进 low
。可以发现它的值是 -128
![image-20240424183937269](https://img-blog.csdnimg.cn/img_convert/715abccd0cd55f31dea6c3a07758c405.png)
然后 ctrl + alt + ← ,ctrl +b 跟进 high
,就可以看见 high
是一个成员变量。
下面有一块静态代码块,在静态代码块中,它定义了一个变量 h
,记录的值是 127
。
然后在 1039行
中,它将 127
赋值给了 high
,因此我们知道了,刚刚看到的范围其实是 byte类型
的取值范围:[-128, 127]
![image-20240424184157030](https://img-blog.csdnimg.cn/img_convert/e2341ee00dcd42949b74854cc4d827c4.png)
静态代码块中首先会创建一个 Integer
数组,数组的长度 size
它是这么计算的 (high - low) + 1
,+ 1
是因为在 -128 - 127
之间,它还有一个数据 0
,也就是说在这个范围内,一共有 256
个数据。
那么下面数组长度也是 256
。
![image-20240424184822433](https://img-blog.csdnimg.cn/img_convert/fc07733f9a279bf9f0e312a665364314.png)
在下面有一个数组,然后遍历数组,给数组中的每个值进行赋值,这里就是通过创建对象来赋值的 c[i] = new Integer(j++)
。
j
的初始值是 low
,即 -128
,然后将 -128
创建一个 Integer
的对象,方法数组的 0
索引.
创建完后,j++
变成 -127
,i
也 ++
,变成 1索引
,将 -127
放到了 1索引
中。
![image-20240424185144892](https://img-blog.csdnimg.cn/img_convert/ef63864aa7539da0efe7106a1dc8e53b.png)
这样我们就知道了,Integer
在底层实际上会把 [-128, 127]
之间所有的数据都创建好 Integer
的对象放到 cache
数组中。
当我们用 valueOf()
方法去获取到对象的时候,它会判断你的这个数据是不是在 [-128, 127]
之间,如果在,它不会创建新的,而是从数组中将已经创建好的对象给你返回。
如果不在这个范围内,它才会去 new
。
![image-20240424185400636](https://img-blog.csdnimg.cn/img_convert/7191b3b513dd587bfdab5ab164251ea3.png)
因此在刚刚的代码中我们就知道了,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以后,int
和 Integer
可以看做是同一个东西,因为在内部可以自动转换。
6)总结
1、什么是包装类?
包装类其实就是基本数据类型所对应的对象。
2、JDK5以后对包装类新增了什么特性?
自动装箱(基本数据类型会自动的变成其对应的包装类)、自动拆箱(包装类自动的变成其对象的基本数据类型)
3、我们以后如何获取包装类对象?
获取对象我们以后不需要 new
,也不需要调用方法,直接赋值就行了
Integer i = 10;
如果想要让包装类进行计算,我们也不需要手动拆箱,直接拿对象 i1
和 i2
进行相加就行了,它的底层会先进行拆箱,然后进行计算,最后再进行装箱赋值给 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](https://img-blog.csdnimg.cn/img_convert/8020e710bfff1f3a0e568e9a17103861.png)
然后再找里面的 toString()
方法,这里就用到了 Interger.toHexString()
,它会将对象的地址值转成十六进制的形式给你进行返回。
![image-20240424193247027](https://img-blog.csdnimg.cn/img_convert/35cd8e37a4a01b7957058b6dabadbfdc.png)
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](https://img-blog.csdnimg.cn/img_convert/048647052c1388ba39e0f7cf899fe95e.png)
细节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);