【Java基础】分析java数据类型与自动装箱/自动拆箱机制原理
文章目录
- 一. 八大基本数据类型
- 二. 使用基本数据类型有什么好处
- 三. 整数类型的取值范围
- 四. 八大包装类型
- 五. 自动拆箱与自动装箱
- 六. 自动装箱/自动拆箱原理
- 七. 分析自动拆装箱使用场景
- 八. 自动拆装箱与缓存
- 九.使用包装类弊端
一. 八大基本数据类型
-
基本类型,或者叫做
内置类型
,是Java中不同于类(Class)的特殊类型。它们是使用最频繁的类型。 -
Java是一种强类型语言,第一次
申明变量
必须声明数据类型,第一次变量赋值
称为变量的初始化。
Java基本类型共有八种,基本类型可以分为三类:
分类 | 类型 |
---|---|
字符类型 | char(8位) |
整数类型 | byte(8位)、short(16位)、int(32位)、long(64位) |
布尔类型 | boolean(1位) |
浮点数类型 | float(32位)、double(64位) |
- boolean 只有两个值:true、false,可以使用
1 bit
来存储,但是具体大小没有明确规定。 - JVM 会在
编译时期
将 boolean 类型的数据转换为int
,使用1 来表示 true,0 表示 false
。 - JVM 支持 boolean 数组,但是是通过读写 byte 数组来实现的。
- 实际上,Java中还存在另外一种
基本类型void
,它也有对应的包装类java.lang.Void
,不过我们无法直接操作
。
二. 使用基本数据类型有什么好处
-
在java中, new一个对象是存储在堆里的,我们通过栈中的引用来使用这些对象,创建对象本身来说是比较消耗资源的。
-
对于经常用到的类型,如int,double等,如果每次使用的时候都需要new一个Java对象的话,就会比较笨重。所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要使用new创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。
三. 整数类型的取值范围
Java中的整型主要包含byte、short、int和long这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。
1字节=8位(bit) java中的整型属于有符号数(可以带正负符号)
8bit可以表示的数字:
(以byte类型为例)
最小值:10000000(-128)(-2^7)
最大值:01111111(127)(2^7-1)
整型的这几个类型中
类型 | 字节占用 | 范围 | 默认值 |
---|---|---|---|
byte | 1个字节 |
范围为 -128 (-2^7)到127 (2^7-1) |
在变量初始化的时候,byte类型的默认值为0。 |
short | 2个字节 |
范围为 -32,768 (-2^15)到32,767 (2^15-1) |
在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0 |
int | 4个字节 |
范围为 -2,147,483,648 (-2^31)到 2,147,483,647 (2^31-1) |
在变量初始化的时候,int类型的默认值为0 |
long | 8个字节 |
范围为 -9,223,372,036,854,775,808 (-2^63)到 9,223,372,036, 854,775,807 (2^63-1) |
在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为 0 |
超出取值范围怎么办
每个整型类型都有一定取值范围,当超出取值范围,即溢出。如以下代码:
@Test public void testOverFlow() { int i = Integer.MAX_VALUE; int j = Integer.MAX_VALUE; int k = i + j; System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")"); //i (2147483647) + j (2147483647) = k (-2) }
结果为: i (2147483647) + j (2147483647) = k (-2) 这就是发生了溢出,
溢出的时候并不会抛异常,
也没有任何提示。在程序中,使用同种类型的数据进行运算的时候,一定要注意
数据溢出的问题。
四. 八大包装类型
包装类(Wrapper Class)
,均位于java.lang
包, 它将基本类型进行一层封装,并类中定义了许多方法操作该数据。如:转换字符串, 转换成基本类型
基本类型 | 包装类型 | 所属分类 |
---|---|---|
byte | Byte | 整数类型 |
short | Short | 整数类型 |
int | Integer | 整数类型 |
long | Long | 整数类型 |
double | Double | 浮点数类型 |
float | Float | 浮点数类型 |
boolean | Boolean | 布尔类型 |
char | Character | 字符类型 |
为什么需要包装类型
- 因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型。比如集合类List,Map中,我们无法将
int 、double
等类型保存进去的。因为集合的容器要求元素是Object类型
。 - 为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具有了
对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作
。
五. 自动拆箱与自动装箱
那么,有了基本数据类型和包装类,肯定有些时候要在他们之间进行转换。比如把基本数据类型的int转换成一个包装类型的Integer对象。把包装类型Integer对象转换成基本类型int
因此
基本类型
转换成包装类型
的过程就是装箱
,英文对应于boxing包装类型
转换成基本类型
的过程就是拆箱
,英文对应于unboxing
Java 1.5以前我们需要
手动进行转换才行
/Java 1.5以前 Integer iObject = Integer.valueOf(3);// 装箱 int iPrimitive = iObject.intValue()// 拆箱
Java 1.5以后提供了自动装箱的功能,直接使用
Integer integer = 10;语法就可以完成装箱,所有的转换都是由
编译器来完成,编译器会判断是否进行自动装箱动作。
自动装箱只适用于八大基本类型
- 自动装箱: 就是将基本类型
自动
转换成对应的包装类型。 - 自动拆箱:就是将包装类型
自动
转换成对应的基本类型。
//Java 1.5之后 Integer iObject = 3; //自动装箱 - primitive to wrapper conversion int iPrimitive = iObject; //拆箱 - object to primitive conversion
六. 自动装箱/自动拆箱原理
public static void main(String[] args) { Integer integerNum = 1; //自动装箱 int intNum = integerNum; //自动拆箱 }
反编译上面代码
public static void main(String[] args) { Integer integerNum = Integer.valueOf(1); int intNum = integerNum.intValue(); }
从上面反编译后的代码可以看出,
int的
自动装箱都是通过
Integer.valueOf()方法来实现的,
Integer的
自动拆箱都是通过
integer.intValue来实现的。
可以试着将八种类型都反编译一遍 ,你会发现以下规律:
自动装箱都是通过包装类的valueOf()方法来实现的,自动拆箱都是通过包装类对象的xxxValue()来实现的。
七. 分析自动拆装箱使用场景
- 场景一:将基本数据类型放入集合类
List<Integer> li = new ArrayList<>(); for (int i = 1; i < 50; i ++){ li.add(i); }
反编译结果
List<Integer> li = new ArrayList<>(); for (int i = 1; i < 50; i += 2){ li.add(Integer.valueOf(i)); }
结论: Java中的集合类只能保存对象类型,基本数据类型放入集合类中的时候,会进行自动装箱成包装类对象
- 场景二:包装类型和基本类型的大小比较
Integer a = 1; System.out.println(a == 1 ? "等于" : "不等于"); Boolean bool = false; System.out.println(bool == true ? "真" : "假");
反编译结果
Integer a = Integer.valueOf(1); System.out.println(a.intValue() == 1 ? "等于" : "不等于"); Boolean bool = Boolean.valueOf(false); System.out.println(bool.booleanValue() == true ? "真" : "假");
结论: 包装类与基本类型比较时,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
- 场景三:包装类型的运算
Integer i = 10; Integer j = 20; System.out.println(i + j);
反编译结果
Integer i = Integer.valueOf(10); Integer j = Integer.valueOf(20); System.out.println(i.intValue() + j.intValue());
结论: 两个包装类型之间的运算,会被自动拆箱成基本类型进行。
- 场景四:三目运算符的使用
Integer i = 0; int j = 1; boolean flag = true; int k = flag ? i : j;
反编译结果
Integer i = Integer.valueOf(0); int j = 1; boolean flag = true; int k = flag ? i.intValue() : j;
结论: 这是三目运算符的语法规范:当
第二,第三位操作数分别为
基本类型和
包装类型时,其中的
包装类型就会自动拆箱为基本类型进行操作。
例子中,
flag ? i : j;片段中,第二段的
i是一个包装类型的对象,而第三段的
j是一个基本类型,所以会对包装类进行自动拆箱。如果这个时候
i 的值为null,那么久会发生NPE。(自动拆箱导致空指针异常)
- 场景五:方法形参与返回值
//自动拆箱 public int getInt(Integer num) { return num; } //自动装箱 public Integer getInteger(int num) { return num; }
反编译结果
public int getInt(Integer num) { return num.intValue(); } public Integer getInteger(int num) { return Integer.valueOf(num); }
结论:
八. 自动拆装箱与缓存
Java的编译器把
基本类型自动 转换成
包装类对象的过程叫做
自动装箱,相当于使用
valueOf()方法:
代码
Integer integer1 = 3; Integer integer2 = 3; if (integer1 == integer2) { System.out.println("integer1 == integer2"); } else { System.out.println("integer1 != integer2"); } Integer integer3 = 300; Integer integer4 = 300; if (integer3 == integer4) { System.out.println("integer3 == integer4"); } else { System.out.println("integer3 != integer4"); }
执行结果:
- 上面的两个都是用
==
判断包装类对象是否相等。而==
如果左右两边比较的是对象,那比较的是内存地址是否相同, 所以在这个例子中,不同的对象有不同内存,那个么应该返回的都是false才对,但实际上却是一个true,一个false
那为什么返回的结果不一致呢?
- 原因和Integer中的
缓存机制
有关。在Java1.5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过使用相同的对象引用
实现了缓存和重用
。 integer整数值取值范围 在-128 至 +127`直接从缓存中获取,不创建新的对象 - 只适用于自动装箱。使用构造函数
new Integer()
创建对象不适用。
下面是
JDK 1.8中 Integer的
valueOf()方法的实现:
/** * Returns an {@code Integer} instance representing the specified * {@code int} value. If a new {@code Integer} instance is not * required, this method should generally be used in preference to * the constructor {@link #Integer(int)}, as this method is likely * to yield significantly better space and time performance by * caching frequently requested values. * * This method will always cache values in the range -128 to 127, * inclusive, and may cache other values outside of this range. * * @param i an {@code int} value. * @return an {@code Integer} instance representing {@code i}. * @since 1.5 */ public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
在创建对象之前首先 数值
i取值范围是否在
IntegerCache和
IntegerCache.high之间, 如果在就 从
IntegerCache.cache中寻找。如果找到就使用
缓存里的Integer对象, 如果没找到就
new 一个新的Integer对象
IntegerCache 类
IntegerCache 是
Integer类中的一个
私有的静态内部类,使用了
享元模式
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. The size of the cache * may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option. * During VM initialization, java.lang.Integer.IntegerCache.high property * may be set and saved in the private system properties in the * sun.misc.VM class. */ private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
其中的
源码注释详细的说明了缓存支持
-128到127之间的自动装箱过程。
-
最大值127
可以通过-XX:AutoBoxCacheMax=size
修改。这个功能在Java1.5中引入的时候,范围是固定的-128 至 +127
。后来在Java1.6中,可以通过java.lang.Integer.IntegerCache.high
设置最大值。 -
缓存通过一个
for循环
实现。从低到高并创建尽可能多的整数并存储在一个整数数组中。 -
这个缓存会在Integer类
第一次使用
的时候被初始化出来。以后,就可以使用缓存中包含的实例对象,而不是创建一个新的实例(在自动装箱的情况下)。
实例
Integer a = 12; Integer b = 12; System.out.println(a == b);// true Integer c = 1234; Integer d = 1234; System.out.println(c == d);// false Integer e = new Integer(13); Integer f = new Integer(13); System.out.println(e == f); //false Integer g = new Integer(1200); Integer h = new Integer(1200); System.out.println(g == h); //false
- 通过Java源代码可以看到
Integer.valueOf()
中有个内部类IntegerCache
(类似于一个常量数组,也叫对象池),它维护了一个Integer数组cache,长度为(128+127+1)=256 。 - Integer类中还有一个
Static Block(静态块)
。从这个静态块可以看出,Integer已经默认创建了数值【-128-127】的Integer缓存数据
。所以使用Integer a=10时,JVM会直接在该在对象池找到该值的引用。也就是说这种方式声明一个Integer对象时,JVM首先会在Integer对象的缓存池
中查找有没有10的对象,如果有直接返回该对象的引用
;如果没有,则使用new一个对象
,并返回该对象的引用地址
。 - 因为Java中
“==”
比较的是两个对象的引用(即内存地址),a、b为同一个对象,所以结果为true,因为300超过了Integer缓存数据范围
,使用new方式创建:c = new Integer(1234); d=new Integer(40);
虽然他们值相等,但是属于不同的对象,不会被放到对象池中,所以他们不是同一个引用,返回false。
用一句简短通俗的话话概括享元模式:
- 如果有很多个小的对象,它们有很多属性相同,那可以把它们变成一个对象,那些不同的属性把它们变成方法的参数,称之为外部状态,相同的属性称之为内部状态,这种机制即为享元模式。
到底是什么原因选择这个-128到127范围呢?
- 因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。
在Boxing Conversion部分的Java语言规范(JLS)规定如下:
- 如果一个变量p的值是:
-128至127之间的整数
或者true 和 false的布尔值
或者‘\u0000’至 ‘\u007f’之间的字符
范围内的时,将p包装成a和b两个对象时,可以直接使用a==b
判断a和b的值是否相等。否则就要使用equals来判断
其他整型对象有没有缓存机制
这种缓存行为不仅适用于Integer对象。
所有的整数类型的类都有类似的缓存机制。
包装类 | 对应的缓存内部类 |
---|---|
Byte | ByteCache用于缓存Byte对象 |
Short | ShortCache用于缓存Short对象 |
Long | LongCache用于缓存Long对象 |
Character | CharacterCache用于缓存Character对象 |
Byte, Short, Long
有固定
范围:-128 到 127
。对于Character
, 范围是0 到 127
, 并且他们的范围都不能改变
九.使用包装类弊端
-
包装对象的数值比较,不能简单的使用==,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals比较。
(包装类重写了equals方法)
-
有些场景由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE(空指针异常)
-
容易生成无用对象,因为自动装箱会
隐式地创建对象
,在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。
所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作.
- 点赞 2
- 收藏
- 分享
- 文章举报
- Java基础加强:静态导入及可变参数和增强for循环 ,基本数据类型的自动拆箱和装箱
- java语言基础(59)——jdk5自动装箱和拆箱(基本数据类型与包装类之间的转换)
- Java基础学习总结(122)——Java八种基本数据类型的包装类及其装箱拆箱详解
- JAVA1.5新特性----基本数据类型的自动拆箱与装箱
- Java深入(高新技术)(二):开发环境、静态导入、可变参数、增强for循环、基本数据类型的自动拆箱与装箱、享元模式
- java基本数据类型与字符串之间的转换(基本数据类型、对象封装类、自动装箱、自动拆箱)
- Java中基本数据类型的自动拆箱和装箱
- java第十三天---Arrays类、基本类型包装类、Integer类、自动装箱与拆箱、集合Collection、迭代器、集合List、数据结构之栈和队列和数组和链表、List的三个子类、
- Java中基本数据类型的自动拆箱和装箱
- JAVA进阶之旅(一)——增强for循环,基本数据类型的自动拆箱与装箱,享元设计模式,枚举的概述,枚举的应用,枚举的构造方法,枚举的抽象方法
- Java基础(13):Java的自动装箱/拆箱机制、整型包装类缓存机制
- Java 自动装箱、拆箱机制及部分源码分析
- Java基本数据类型的大小,他们的封装类以及自动拆箱和装箱
- Java基础数据类型的包装类(装箱与拆箱)
- Java基础类型的自动装箱拆箱
- 对java基础数据类型在运算过程中的自动转换的一些分析
- 【JAVA学习】java基本数据类型与字符串之间的转换(基本数据类型、对象封装类、自动装箱、自动拆箱)
- JAVA基础--可变参,自动装箱与拆箱,类型转换
- 黑马程序员--张孝祥Java高新技术-JDK1.5新特性(二)【基本数据类型的自动拆箱与装箱,枚举】以及享元设计模式
- java 自动装箱拆箱及 数据对象的缓存机制详解