【Java常用类】1-2 基本类型包装类
§1-2 基本类型包装类
1-2.1 什么是包装类?
在基础课程中,我们学习了 Java 中的八大基本数据类型:byte, short, int, long, float, double, char, boolean
。
在程序运行的过程中,这些基本数据类型变量存储在栈中,不具备任何的属性或方法。对这些基本数据类型,只能通过 Java 所提供的运算符来操作这些基本数据类型。
为了向这些基本数据类型提供更为强大的功能,JDK 中提供了由这八大基本数据类型所对应的引用数据类型,称为基本数据类型的包装类。此时,这些 “基本数据类型” 就有了配套的属性和方法。
其中,Object
类可统一所有数据,包装类的默认值为 null
(即引用类型的默认值)。
下表列出了基本数据类型所对应的包装类,所有类型都在 java.lang
下:
基本数据类型 | 对应包装类 |
---|---|
byte |
Byte |
short |
Short |
int |
Integer |
long |
Long |
float |
Float |
double |
Double |
char |
Character |
boolean |
Boolean |
上述的包装类都继承自一个抽象类 Number
,该类还有以下已知子类:AtomicInteger
,AtomicLong
,BigDecimal
,BigInteger
。
1-2.2 类型转换与装箱、拆箱
基本类型变量存储在栈中,而对象存储在堆中。此处的类型转换指的是基本数据类型和其对应包装类的类型转换。
装箱:将栈中的基本数据类型以对象的形式存储到堆中,称为装箱。
装箱可使用包装类的构造器(不推荐,已被弃用),或使用 valueOf();
方法返回包装类实例。
拆箱:将堆中的对象以基本数据类型的形式存储到栈中,称为拆箱。
拆箱可通过 Number
类中的 byteValue();
,shortValue();
,intValue();
,longValue();
,floatValue();
,doubleValue();
将引用类型转成基本类型。(通过子类的重写方法)
示例:JDK 1.5 前,装拆箱需要手动进行
public class Test {
public static void main(String[] args) {
//JDK 1.5 前,手动装箱、拆箱
//不建议使用构造器,现已被弃用
//手动装箱
int num1 = 10;
Integer int1 = Integer.valueOf(num1);
System.out.println("手动装箱:");
System.out.println("num1 = " + num1);
System.out.println("int1 = " + int1);
System.out.println();
//手动拆箱
Integer int2 = Integer.valueOf(100);
int num2 = int2.intValue();
System.out.println("手动拆箱:");
System.out.println("int2 = " + int2);
System.out.println("num2 = " + num2);
}
}
运行得到
手动装箱:
num1 = 10
int1 = 10
手动拆箱:
int2 = 100
num2 = 100
JDK 1.5 后,支持自动装拆箱:
public class Test {
public static void main(String[] args) {
//不建议使用构造器,现已被弃用
//JDK 1.5 后,自动装拆箱
//自动装箱
int num3 = 20;
Integer int3 = num3;
System.out.println("自动装箱:");
System.out.println("num3 = " + num3);
System.out.println("int3 = " + int3);
System.out.println();
//自动拆箱
Integer int4 = 200;
int num4 = int4;
System.out.println("自动拆箱:");
System.out.println("int4 = " + int4);
System.out.println("num4 = " + num4);
}
}
运行得到:
自动装箱:
num3 = 20
int3 = 20
自动拆箱:
int4 = 200
num4 = 200
实际上,自动装拆箱的原理是编译器在编译时为我们自动做了装拆箱工作。将自动装拆箱的程序字节码文件反编译,就可以发现,自动装拆箱的实现,实际上就是编译器将赋值语句转换成了方法调用语句。
1-2.3 基本类型与字符串的互相转换
基本类型转换成字符串:可以通过使用包装类中的 toString();
方法实现。以整型为例:
//与字符串的互相转换
//基本类型转成字符串:toString();
int1 = 255;
String str = int1.toString(); //int1 + ""也可以,但更推荐使用方法
System.out.println(str);
str = Integer.toString(int1,16); //使用重载(静态方法),以特定进制输出
System.out.println(str);
str = Integer.toHexString(int1); //使用这个方法也可以实现十六进制转换
System.out.println(str);
得到结果:
255
ff
ff
字符串转换为基本类型:使用静态方法 XXX.parseXXX();
String str = "17e"; //含有非法字符
int num = 0;
boolean accepted = true;
try {
num = Integer.parseInt(str);
} catch (NumberFormatException e) {
accepted = false;
System.out.println("输入中含有非法字符。");
}
if (accepted) {
System.out.println(num);
}
运行得到
输入中含有非法字符。
注意:
- 若字符串中含有非法字符,则会抛出
NumberFormatException
异常; - 字符串转布尔类型:
"true"
则返回true
,非"true"
则返回false
。
1-2.4 Integer
缓冲区
我们发现,自动装箱的过程中,编译器实际上是在调用 valueOf();
方法。
且已知,使用 ==
运算符,对于引用变量而言,比较的实际上是二者的地址是否相同。
测试如下程序:
public class IntegerCacheTest {
public static void main(String[] args) {
//Integer 缓冲区
//不使用构造器
Integer int1 = 100;
Integer int2 = 100;
System.out.println(int1 == int2); //使用 == 比较引用变量地址,使用 equals() 比较内容
Integer int3 = 200;
Integer int4 = 200;
System.out.println(int3 == int4);
}
}
运行得到
true
false
不同的对象实际上是通过构造器构造产生(不允许外部使用,已被弃用),地址不同,比较结果应当都为 false
。而 int1 == int2
结果为 true
意味着,二者在内存当中,共同指向同一片内存地址。
结合子自动装箱原理,查看 valueOf()
方法的源码,不难发现:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
对于方法中的一些变量,有(节选):
//java.lang.Integer.java
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache; //一个 Integer 缓冲数组
static Integer[] archivedCache;
//静态代码块
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h; //即 high = h = 127
// Load IntegerCache.archivedCache from archive, if possible
CDS.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1; //缓冲数组长度(256)
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache; //指向同一个数组
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
}
实际上,Java 预先创建了 256 个常用的整型包装类对象,存放于数组之中,范围为 -128~127。调用 valueOf()
方法,若传入参数范围落入其中,则直接返回预先创建好的对象的地址,否则创建新对象。
在实际应用中,这鞋整数使用比较频繁。这样子可以对一创建的对象进行复用,减少内存消耗。