- Java修炼指南:高频源码解析
- 开课吧组编 曹子方 杨富杰 刘常凯等编著
- 2656字
- 2021-04-22 17:10:49
1.3 最常用的引用类——Integer类
本节重点介绍一下Integer。首先介绍Integer类和int基本数据类型的关系,接着再从源码层次详细介绍Integer的实现。在讲解Integer之前,先看如下代码。
可以先思考一下运行结果是什么?
答案是
至于为什么是这个结果,下面逐一介绍。
1.3.1 Integer类简介
首先大致看一下Integer是什么,Integer类在JDK 1. 0的时候就有了,它是一个类,是int基本数据类型的封装类,定义代码如下:
1.3.2 Integer的主要属性
如图1-9所示,int类型在Java中占据4个字节,所以其可以表示大小的范围是 -231~231-1,即-2147483648~2147483647,在用int表示数值时一定不要超出这个范围。
●图1-9 Integer主要属性
1.3.3 Integer类和int的区别
1)Integer是int包装类,int是八大基本数据类型之一(byte、char、short、int、long、float、double、boolean)。
2)Integer是类,默认值为null,int是基本数据类型,默认值为0。
3)Integer表示的是对象,用一个引用指向这个对象,而int是基本数据类型,直接存储数值。
1.3.4 构造方法Integer(int),Integer(String)
对于第一个构造方法Integer(int),源码如下:
对于第二个构造方法Integer(String),就是将输入的字符串数据转换成整型数据。
首先必须要知道能转换成整数的字符串必须分为两个部分:第一位必须是"+"或者"-",剩下的必须是0~9和a~z字符。
1.3.5 toString(),toString(int i)和toString(int i,int radix)
通过toString(),toString(int i)和toString(int i,int radix)这三个方法重载,能返回一个整型数据所表示的字符串形式,其中最后一个方法toString(int,int)中第二个参数表示进制数。
toString(int)方法内部调用了stringSize()和getChars()方法,stringSize()是用来计算参数i的位数,也就是转成字符串之后的字符串的长度,通过内部结合一个已经初始化好的int类型的数组sizeTable来完成计算。
这里实现的形式很巧妙。注意负数包含符号位,所以对于负数的位数是stringSize(-i)+1。
再看getChars方法:
i:被初始化的数字。
index:这个数字的长度(包含了负数的符号“-”)。
buf:字符串的容器-一个char型数组。
第一个if判断,如果i<0,sign记下它的符号“-”,同时将i转成整数。下面所有的操作也就只针对整数了,最后再判断sign,如果不等于零,则将sign的值放在char数组的首位buf [--charPos] = sign。
1.3.6 自动拆箱和装箱
自动拆箱和自动装箱是JDK 1. 5以后才有的功能,也就是Java当中众多的语法糖之一,它的执行是在编译期,会根据代码的语法,在生成class文件的时候,决定是否进行拆箱和装箱动作。
1. 自动装箱
一般创建一个类的对象需要通过new关键字,比如:
但是实际上,对于Integer类,可以直接这样使用:
为什么可以这样,通过反编译工具,可以看到,生成的class文件是:
看看valueOf()方法
其实最后返回的也是通过new Integer()产生的对象,但是这里要注意前面的一段代码,当i的值-128<= i<= 127时返回的是缓存类中的对象,并没有重新创建一个新的对象,这在通过equals进行比较的时候要注意。
这就是基本数据类型的自动装箱,128是基本数据类型,然后被解析成Integer类。
2. 自动拆箱
将Integer类表示的数据赋值给基本数据类型int,就执行了自动拆箱。
反编译生成的class文件,代码如下所示。
简单来讲:自动装箱就是Integer. valueOf(int i);自动拆箱就是i. intValue();。
1.3.7 回顾本节开篇的问题
本节开篇时的代码如下:
使用反编译工具,得到的代码如下:
打印结果为:
首先,直接声明Integer i = 10,自动装箱变为Integer. valueOf(10);自动拆箱变为i. intValue()。
1)第一个打印结果为true。对于i = = j,这是两个Integer类,它们比较应该用equals,这里用==比较的是地址,那么结果肯定为false,但是实际上结果为true,这是为什么?
进入Integer类的valueOf()方法,代码如下所示。
分析源码可以知道在i>= -128并且i<= 127的时候,第一次声明会将i的值放入缓存中,第二次直接取缓存里面的数据,而不是重新创建一个Ingeter对象。那么第一个打印结果因为i = 10在缓存表示范围内,所以为true。
2)第二个打印结果为false。从上面的分析可以知道,128是不在-128到127之间的,所以第一次创建对象的时候没有缓存,第二次创建了一个新的Integer对象。故打印结果为false。
3)第三个打印结果为true。Integer的自动拆箱功能,也就是比较两个基本数据类型,结果为true。
4)第四个打印结果为true。解释和第三个一样。int和integer比都为true,因为会把Integer自动拆箱为int再去比较。
5)第五个打印结果为false。因为这个虽然值为10,但是都是通过new关键字来创建的两个对象,是不存在缓存的概念的。两个用new关键字创建的对象用 == 进行比较,结果为false。
1.3.8 进行测试
测试代码如下:
反编译结果为:
打印结果为:
分析:第一个和第二个结果容易获得,它们是Integer类在-128到127的缓存问题。
第三个结果:由于a+b包含了算术运算,因此会触发自动拆箱过程(会调用intValue方法),==比较符又将左边的自动拆箱,因此它们比较的是数值是否相等。
第四个结果:对于c. equals(a+b),会先触发自动拆箱过程,再触发自动装箱过程,也就是说,a+b会先各自调用intValue方法,得到了加法运算后的数值之后,再调用Integer. valueOf方法,进行equals比较。
第五个结果:对于g ==(a+b),首先计算a+b,也是先调用各自的intValue方法,得到数值之后,由于前面的g是long类型的,也会自动拆箱为long,==运算符能将隐含的小范围的数据类型转换为大范围的数据类型,也就是int会被转换成long类型,然后再将两个long类型的数值进行比较。
第六个结果:对于g. equals(a+b),同理a+b会先自动拆箱,然后将结果自动装箱,需要说明的是,equals运算符不会进行类型转换。所以是Long. equals(Integer),结果当然是false。
第七个结果:对于g. equals(a+h),运算符+会进行类型转换,a+h各自拆箱之后是int+long,结果是long,然后long进行自动装箱为Long,两个Long进行equals判断。
1.3.9 equals()方法
该方法先通过instanceof关键字判断两个比较对象的关系,然后将对象强转为Integer,再通过自动拆箱,转换成两个基本数据类int,然后通过 == 比较,代码如下所示。
1.3.10 String类的定义
和Integer类一样,这也是一个用final声明的常量类,不能被任何类所继承,而且一旦一个String对象被创建,包含在这个对象中的字符序列是不可改变的,包括该类后续的所有方法都不能修改该对象,直至该对象被销毁,这是需要特别注意的(该类的一些方法看似改变了字符串,其实内部都是创建一个新的字符串,下面讲解方法时会介绍)。接着实现了Serializable接口,这是一个序列化标志接口;还实现了Comparable接口,用于比较两个字符串的大小(按顺序比较单个字符的ASCII码),后面会有具体方法实现;最后实现了CharSequence接口,表示是一个有序字符的集合,相应的方法后面也会介绍。
1.3.11 hashCode()方法
Integer类的hashCode方法也比较简单,直接返回其int类型的数据,代码如下所示。
1.3.12 parseInt(String s)和parseInt(String s,int radix)方法
前面通过toString(int i)可以将整型数据转换成字符串类型输出,这里通过parseInt(String s)能将字符串转换成整型输出。
这两个方法在介绍构造函数Integer(String s)时已经详细讲解了。
1.3.13 compareTo(Integer anotherInteger)和compare(int x,int y)方法
这两个方法可以比较两个数的大小。
compareTo方法可以在内部直接调用compare方法,代码如下所示。
如果x<y则返回 -1。
如果x == y则返回0。
如果x>y则返回1。