目录
6.探究 Integer 类的静态内部类:IntegerCache 类
1.什么是包装类
对于基本数据类型 int 、double 、float 等,虽然使用起来非常的方便,但由于它们不是对象,因此无法通过向上转型获取到 Object 类提供的方法,而像 String 类却可以,只因为 String 是一个对象而不是一个类型。基本数据类型由于这样的特性,导致无法参与转型,泛型,反射等过程。为了弥补这个缺陷,java 提供了包装类。
包装类是把基本数据类型用一个类包装起来,并提供了相应基本数据类型的操作方法,这样就定义了一个包装类。
2.包装类的特点
(1)所有包装类都是使用 final 修饰的,不能被继承。其中数值型对应的包装类都是继承自 Number 类。
(2)包装类是不可变类,包装类的对象被创建后,它所包含的基本数据类型值就不能改变。
3. 8 大基本数据类型对应的包装类
基本数据类型 | 包装类 |
int | Integer |
char | Character |
byte | Byte |
short | Short |
long | Long |
double | Double |
float | Float |
boolean | Boolean |
从上表我们注意到,除了 int 和 char 类型之外,其他的包装类名均为基本类型首字母大写。
对于数值型的包装类,我们以Integer类为例进行学习。
4.包装类的继承关系
5.数值型包装类的使用
(1)装箱与拆箱
装箱就是把基本数据类型装换为包装类,拆箱就是把包装类转换成基本数据类型。
其中装箱其实就是调用了包装类的构造方法,底层实现就是通过构造方法给包装类中的属性赋值。
数值型的包装类有两个构造方法,其中一个构造方法参数为对应的基本数据类型,另一个构造方法参数为 String 类型。需要注意的是,第二个构造方法的参数必须为相应基本数据类型格式的字符串,否则由于格式错误无法解析,会在运行时报错。
public static void main(String[] args) {
//参数为int型
Integer integer1 = new Integer(4);
//参数为String型
Integer integer2 = new Integer("11");//参数为String类型
//此时若参数不是整数格式,就会报错
//Integer integer3 = new Integer("123.45");
//Integer integer3 = new Integer("erer122");
System.out.println(integer1);
System.out.println(integer2);
}
结果为:
4
11
而对于拆箱,则需要调用包装类中拆箱后相应类型的 value() 方法。
public static void main(String[] args) {
//参数为int型
Integer integer1 = new Integer(4);
//拆箱
//把数值转为int型
int a = integer1.intValue();
//把数值转为double型
double b = integer1.doubleValue();
System.out.println(a);
System.out.println(b);
}
结果:
4
4.0
其中 value() 方法中实现了对包装类中的成员属性 value 的基本数据类型转换。
public int intValue() {
return value;
}
public long longValue() {
return (long)value;
}
public float floatValue() {
return (float)value;
}
public double doubleValue() {
return (double)value;
}
(2)自动装箱与自动拆箱
在 JDK 1.5 之前,是不能把一个基本类型直接赋值给引用类型的。需要引用类型,就必须使用创建对象的方式。
在 JDK 1.5 以后,JVM 会把基本数据类型和引用数据类型默认自动进行转换,这就有了自动装箱与自动开箱。这一新特性从代码上简化了装箱与拆箱的实现步骤。
下面举例说明自动装箱与自动拆箱
//自动装箱
Integer a = 3;
//装箱
Integer a = new Integer(3);
//自动拆箱
int b = a;
//拆箱
int b = a.intValue();
此处需要注意的是,对于自动装箱和装箱,他们的实现原理是不一样的。自动装箱使用了 Integer 类中的 valueOf() 方法返回了一个新的 Integer 对象;而装箱是直接创建了一个新的 Integer 对象。对于此类情况衍生出的一系列问题问题,会在 6 小节中进行深入的探究。
(3)Integer 类中的 parseInt() 方法
parseInt() 方法是一个静态的方法,其主要作用是把指定进制的字符串解析成十进制带符号整数,字符串默认为十进制;该方法在给定字符串不符合规范的情况下会抛出数字格式异常。通常情况下该方法在 Integer 类带 String 参数的构造方法中被调用,开发者也可直接使用该方法进行字符串到基本数据类型的转换。
public static void main(String[] args) {
//10进制(默认),可省略radix参数
int a = Integer.parseInt("-43354",10);
System.out.println(a);
//16进制
int b = Integer.parseInt("ABC4335",16);
System.out.println(b);
//2进制
int c = Integer.parseInt("1001010101",2);
System.out.println(c);
}
结果为:
-43354
180110133
597
//Integer 类中参数为 String 类型的构造方法
public Integer(String s) throws NumberFormatException {
this.value = parseInt(s, 10);
}
6.探究 Integer 类的静态内部类:IntegerCache 类
在我们使用 Integer 类的时候,常常会遇到以下的问题:
public static void main(String[] args) {
Integer a1 = 100;
Integer a2 = 100;
System.out.println(a1 == a2);
Integer b1 = 200;
Integer b2 = 200;
System.out.println(b1 == b2);
Integer c1 = new Integer(100);
Integer c2 = new Integer(100);
System.out.println(c1 == c2);
}
结果为:
true
false
false
结合前面学过的知识,对于对象的创建,每个新对象都应该有一个新的地址,因此三个结果都为 false,但为什么第一个自动装箱结果却为 true 呢?在这里就涉及到了Integer 类中的静态内部类——IntegerCache。
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() {}
}
从源码中我们可以看出,IntegerCache 定义了上限 high、下限 low 以及一个数组 cache[ ] ,用于存储 Integer 的缓存对象,以提高内存利用率和效率。类似的缓存机制还有8种基本数据类型中除了 Double 和 Float 外的6种。而之所以 Double 和 Float 没有缓存机制,是因为 double 、float 是浮点型的,没有经常使用到的数据,缓存效果没有其它几种类型使用效率高。
在 IntegerCache 首次被使用时会执行其中的静态代码块,low 默认是 -128,high 的值默认127(high 的值也可以通过 JVM 进行修改);然后会根据 low 与 high 的值创建一个包含区间内所有整数的 Integer 数组。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
下面我们再来看 valueOf( ) 方法:该方法首先会判断传入的 int 值是否在 low(-128)与high(默认127)之间。若符合条件则取出返回 cache[ ] 数组中的 Integer 对象;若不符合,则返回一个值为相应整数的新的 Integer 对象。
至此,上面的问题就能够解释的通了——对于自动装箱,在调用 valueOf( ) 方法时,区间 [ - 128 , 127 ] 之间的参数会使用静态内部类 IntegerCache 中的 cache[ ] 数组返回相应的 Integer 元素,因此它的地址就是数组的基址。只有在参数超出这个区间时,才会创建一个新的 Integer 对象。
但需要注意的是,该场景只适用于自动装箱。对于普通的装箱操作,是直接调用 Integer 的构造方法创建一个新的对象。