前言
正如所有接触过Java 的程序员所知,Java 是一种面向对象的高级编程语言。但Java 中却存在8种数据类型,它们不但不支持面向对象的编程机制,也不具备面向对象的特性(不包含任何field和方法)。这8种数据类型分别为 byte, short, int, long, char, double, float, boolean, 它们被称之为Java 的8种基本数据类型。Java 提供这8种基本数据类型主要是为了照顾C++等程序员的编程习惯。
Java 兼容这8种基本数据类型固然带来了一定的便利,例如可采用8种数据类型进行常规的数据处理。但在某些场景下,采用基本数据类型却带来了一定的约束,例如:当某个方法的形参只接受引用数据类型,但方法调用者只能提供基本数据类型的实参时,基本数据类型就显得无能为力了。为解决这一矛盾,Java 在1.5之后为每个基本数据类型引入了对应的包装类。具体如表1所示:
基本数据类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
通过观察上表可以看出,除了char和int 类型外,其他6种基本数据类型对应的包装类名称均为其首字母大写后的名称。
自动拆装箱
jdk1.5提供的包装类不仅弥补了基本数据类型在面向对象上的不足,而且还可与其对应的基本数据类型之间进行相互转换,这一功能称之为自动拆装箱。即可以将包装类直接赋值给其对应的基本数据类型,也可将一个基本数据类型的值直接赋值给其对应的包装类。如下两个例子在编译时均不会报错:
Integer a=1; // 1
int b=a;// 2
采用反编译工具JD-GUI获取代码块1对应的class码如下:
public class Test
{
public static void main(String[] args) {
int a = 1;
Integer b = Integer.valueOf(a);
int c = b.intValue();
}
}
基于此特性,可将基本数据类型与其对应的包装类直接进行关系比较。其原理为:首先将通过自动拆箱功能获取包装类封装的基本数据类型数据,然后再将两个基本数据类型数据进行比较。当然也可采用相同原理将两个包装类进行关系比较。具体实例如代码块2所示:
public class Test
{
public static void main(String[] args) {
Integer a = Integer.valueOf(12);
Integer c=Integer.valueof(14)
boolean b = (a.intValue() > 13);
boolean d=(a.intValue()>b.intValue())
}
}
下面以int为例介绍自动拆装箱的原理:
- 自动装箱
当将一个int类型的数据赋值给一个Integer类型的数据时(如代码块1中的第1行代码),会调用Integer类的静态方法valeueOf(int a)获取相应的包装类。源码如下代码块3所示:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
注:关于代码块3中的cache将在Integer的特殊性中讲解.
- 自动拆箱
当将一个Integer类型的数据赋值给一个int类型的变量时(如代码块1中的第2行代码),会调用Integer的实例方法intValue()来获取其封装了数据。源码如代码块4所示:
public int intValue() {
return value;
}
Integer的特殊性
Java中对象的创建与回收是一个十分为消耗性能的过程。故为提高性能,Integer在内部采用缓存的方式保存了部分数据,从而避免了对象的创建重复创建。也正因为缓存的存在,导致将Integer对象与基本数据类型之间进行关系比较时会出现一些非常规现象,具体如下:
Integer a=129;
int a1=129;
System.out.println(a==a1);// 1 输入结果:false
Integer b=12;
int b1=12;
System.out.println(b==b1);//输出结果为:true
通过观察代码块3中的代码发现,当自动装箱的基本数据类型大小属于某个特定的范围时,虚拟机将直接从缓存中获取对应的包装类对象返回;当装箱对象超出该范围,虚拟机每次自动装箱时都会创建一个新的对象返回。故,代码块5中,语句1输出false说明129超出了该范围。下面通过观察缓存源码,分析具体的范围大小:
public class Integer{
...
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为Integer的静态内部类,其静态数组cache 就是用来存储一定范围内int类型数据对应的包装类。static 静态代码块中的代码则是对缓存数组cache 的存储的初始化过程。为便于理解,将其转化为流程图如图1所示:
由图1可知,在Integer包装类的内部,保存有[-128,127]范围内int对应的包装类。其下限由代码种low=-128决定,上限由变量high=127决定。基本数数据类型a在缓存中的索引位置k如下:
总结
本文首先分析了Java 保存8中基本数据类型的的原因及其存在的问题,并在此基础上引出包装类的概念。然后以int和Integer之间的转换为例,介绍了自动拆箱和自动装箱的概念,并从源码层面分析了自动拆装箱的实现原理;最后从源码层面上分析了8个包装类中较为特殊的一个包装类:Integer,得出Integer缓存的具体范围,从而在实际应用中能够灵活应对代诸如码块4中遇到的问题。