文章目录

一. 八大基本数据类型二. 使用基本数据类型有什么好处三. 整数类型的取值范围四. 八大包装类型五. 自动拆箱与自动装箱六. 自动装箱/自动拆箱原理七. 分析自动拆装箱使用场景八. 自动拆装箱与缓存九.使用包装类弊端

一. 八大基本数据类型

基本类型,或者叫做内置类型,是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)

整型的这几个类型中

类型字节占用范围默认值byte1个字节范围为 -128 (-2^7)到127 (2^7-1)在变量初始化的时候,byte类型的默认值为0。short2个字节范围为 -32,768 (-2^15)到32,767 (2^15-1)在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0int4个字节范围为 -2,147,483,648 (-2^31)到 2,147,483,647 (2^31-1)在变量初始化的时候,int类型的默认值为0long8个字节范围为 -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包, 它将基本类型进行一层封装,并类中定义了许多方法操作该数据。如:转换字符串, 转换成基本类型

基本类型包装类型所属分类byteByte整数类型shortShort整数类型intInteger整数类型longLong整数类型doubleDouble浮点数类型floatFloat浮点数类型booleanBoolean布尔类型charCharacter字符类型

为什么需要包装类型

因为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 li = new ArrayList<>();
for (int i = 1; i < 50; i ++){
li.add(i);
}

反编译结果

List 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=} 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对象。所有的整数类型的类都有类似的缓存机制。

包装类对应的缓存内部类ByteByteCache用于缓存Byte对象ShortShortCache用于缓存Short对象LongLongCache用于缓存Long对象CharacterCharacterCache用于缓存Character对象

Byte, Short, Long有固定范围: -128 到 127。对于Character, 范围是0 到 127, 并且他们的范围都不能改变

九.使用包装类弊端

包装对象的数值比较,不能简单的使用==,虽然-128到127之间的数字可以,但是这个范围之外还是需要使用equals比较。(包装类重写了equals方法)  有些场景由于自动拆箱,如果包装类对象为null,那么自动拆箱时就有可能抛出NPE(空指针异常)  容易生成无用对象,因为自动装箱会隐式地创建对象,在一个循环体中,会创建无用的中间对象,这样会增加GC压力,拉低程序的性能。 所以在写循环时一定要注意代码,避免引入不必要的自动装箱操作.