1 基本数据类型的包装类
我们前面学习的八种基本数据类型并不是对象,为了将基本类型数据和对象之间实现互 相转化,JDK 为每一个基本数据类型提供了相应的包装类。
1.1 包装类基本知识
Java 是面向对象的语言,但并不是“纯面向对象”的,因为我们经常用到的基本数据 类型就不是对象。但是我们在实际应用中经常需要将基本数据转化成对象,以便于操作。 比如:将基本数据类型存储到 Object[ ]数组或集合中的操作等等。
为了解决这个不足,Java 在设计类时为每个基本数据类型设计了一个对应的类进行代 表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。
包装类均位于 java.lang 包,八种包装类和基本数据类型的对应关系如表所示:
在这八个类名中,除了 Integer 和 Character 类以外,其它六个类的类名和基本数据 类型一致,只是类名的第一个字母大写而已。
在这八个类中,除了 Character 和 Boolean 以外,其他的都是“数字型”,“数字 型”都是 java.lang.Number 的子类。
Number 类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number 类提供了 抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数 字型”包装类都可以互相转型。
下面我们通过一个简单的示例认识一下包装类。
初识包装类,代码如下:
package com.txw.test;
/**
* 初识包装类
* @author Adair
* @email 1578533828@qq.com
*/
public class WrapperClassTest {
public static void main(String[] args) {
Integer i = new Integer(10); // 从java9开始被废弃
Integer j = new Integer(50);
}
}
的内存分析,如图所示:
1.2 包装类的用途
对于包装类来说,这些类的用途主要包含两种:
1.作为和基本数据类型对应的类型存在,方便涉及到对象的操作,如 Object[ ]、集合等 的操作。
2.包含每种基本数据类型的相关属性如最大值、最小值等,以及相关的操作方法(这些操 作方法的作用是在基本数据类型、包装类对象、字符串之间提供相互之间的转化!)
包装类的使用的代码如下:
package com.txw.test1;
/**
* 包装类的使用
* @author Adair
* @email 1578533828@qq.com
*/
public class Test {
/** 测试Integer的用法,其他包装类与Integer类似 */
void testInteger() {
// 基本类型转化成Integer对象
Integer int1 = new Integer(10); // 已经被废弃,不推荐使用
Integer int2 = Integer.valueOf(20); // 官方推荐这种写法
// Integer对象转化成int
int a = int1.intValue();
// 字符串转化成Integer对象
Integer int3 = Integer.parseInt("334");
Integer int4 = new Integer("999");
// Integer对象转化成字符串
String str1 = int3.toString();
// 一些常见int类型相关的常量
System.out.println("int 能表示的最大整数:" + Integer.MAX_VALUE);
}
public static void main(String[] args) {
Test test = new Test();
test.testInteger();
}
}
执行的结果如图所示:
1.3 自动装箱和拆箱
自动装箱和拆箱就是将基本数据类型和包装类之间进行自动的互相转换。JDK1.5 后, Java 引入了自动装箱(autoboxing)/拆箱(unboxing)。
自动装箱:
基本类型的数据处于需要对象的环境中时,会自动转为“对象”。 我们以 Integer 为例:
在 JDK1.5 以前,这样的代码 Integer i = 5 是错误的,必须要通过 Integer i = new Integer(5) 这样的语句来实现基本数据类型转换成包装类的过程;
而在 JDK1.5 以后,Java 提供了自动装箱的功能,因此只需 Integer i = 5 这样的语句 就能 实现基 本数据 类型转 换成包 装类, 这是因 为 JVM 为我 们执行 了 Integer i = Integer.valueOf(5)这样的操作,这就是 Java 的自动装箱。
自动拆箱:
每当需要一个值时,对象会自动转成基本数据类型,没必要再去显式调用 intValue()、 doubleValue()等转型方法。
如 Integer i = 5;int j = i; 这样的过程就是自动拆箱。
我们可以用一句话总结自动装箱/拆箱:
自动装箱过程是通过调用包装类的 valueOf()方法实现的,而自动拆箱过程是通过调用 包装类的 xxxValue()方法实现的(xxx 代表对应的基本数据类型,如 intValue()、 doubleValue()等)。
自动装箱与拆箱的功能事实上是编译器来帮的忙,编译器在编译时依据您所编写的语 法,决定是否进行装箱或拆箱动作。
自动装箱的代码如下:
Integer i = 100; // 自动装箱
// 相当于编译器自动为您作以下的语法编译:
Integer i = Integer.valueOf(100); // 调用的是valueOf(100),而不是 new Integer(100)
自动拆箱的代码如下:
Integer i = 100;
int j = i; // 自动拆箱
// 相当于编译器自动为您作以下的语法编译:
int j = i.intValue();
所以自动装箱与拆箱的功能是所谓的“编译器蜜糖(Compiler Sugar)”,虽然使用这 个功能很方便,但在程序运行阶段您得了解 Java 的语义。例如示例如下所示的程序是可以 通过编译的:
package com.txw.test1;
/**
* 包装类空指针异常问题
* @author Adair
* @email 1578533828@qq.com
*/
public class Test {
public static void main(String[] args) {
Integer i = null;
int j = i;
}
}
执行的结果为如图所示:
示例代码的运行结果之所以会出现空指针异常,是因为示例中的代码相当于如下代码:
package com.txw.test1;
/**
* 包装类空指针异常问题
* @author Adair
* @email 1578533828@qq.com
*/
public class Test {
public static void main(String[] args) {
Integer i = null;
int j = i.intValue();
}
}
null 表示 i 没有指向任何对象的实体,但作为对象名称是合法的(不管这个对象名称存 是否指向了某个对象的实体)。由于实际上 i 并没有指向任何对象的实体,所以也就不可能 操作 intValue()方法,这样上面的写法在运行时就会出现 NullPointerException 错误。
自动装箱与拆箱,代码如下:
package com.txw.test1;
/**
* 自动装箱与拆箱
* @author Adair
* @email 1578533828@qq.com
*/
public class Test2 {
/*** 测试自动装箱和拆箱 结论:虽然很方便,但是如果不熟悉特殊情况,可能会出错! */
public static void main(String[ ] args) {
Integer b = 23; // 自动装箱
int a = new Integer(20); //自动拆箱
// 下面的问题我们需要注意:
Integer c = null;
int d = c; // 此处其实就是:c.intValue(),因此抛空指针异常。
}
}
1.4 包装类的缓存问题
整型、char类型所对应的包装类,在自动装箱时,对于-128~127之间的值会进行缓存 处理,其目的是提高效率。
缓存处理的原理为:如果数据在-128~127这个区间,那么在类加载时就已经为该区间 的每个数值创建了对象,并将这256个对象存放到一个名为cache的数组中。每当自动装箱 过程发生时(或者手动调用valueOf()时),就会先判断数据是否在该区间,如果在则直接 获取数组中对应的包装类对象的引用,如果不在该区间,则会通过new调用包装类的构造方法来创建对象。
下面我们以Integer类为例,看一看Java为我们提供的源码,加深对缓存技术的理解,代码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
这段代码中我们需要解释下面几个问题:
1.IntegerCache类为Integer类的一个静态内部类,仅供Integer类使用。
2. 一般情况下 IntegerCache.low为-128,IntegerCache.high为127。
IntegerCache.cache为内部类的一个静态属性,代码如下:
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() {
} }
由上面的源码我们可以看到,静态代码块的目的就是初始化数组cache的,这个过程会 在类加载时完成。
包装类的缓存测试代码如下:
package com.txw.test1;
/**
* 包装类的缓存测试
* @author Adair
* @email 1578533828@qq.com
*/
public class Test3 {
public static void main(String[] args) {
Integer in1 = -128;
Integer in2 = -128;
System.out.println(in1 == in2); // true 因为123在缓存范围内
System.out.println(in1.equals(in2)); // true
Integer in3 = 1234;
Integer in4 = 1234;
System.out.println(in3 == in4); // false 因为 1234 不在缓存范围内
System.out.println(in3.equals(in4)); // true
}
}
执行的结果如图所示:
内存分析如图所示:
注意:
1.JDK1.5 以后,增加了自动装箱与拆箱功能,如: Integer i = 100; int j = new Integer(100);
2.自动装箱调用的是 valueOf()方法,而不是 new Integer()方法。