Java是一种面向对象语言,Java中的类把方法与数据连接在一起,构成了自包含式的处理单元。但是Java中不能定义基本类型对象,在对基本类型数据进行操作时便会出现大量重复的自定义方法。为了能将基本类型视为对象进行处理,并连接相关的方法,Java为所有的基本类型都提供一个与之对应的包装类(wrapper)。Java中所有的包装类都存在于java.lang包中。
对于包装类,有以下两点需要注意:
- 包装类为
final类,不允许扩展,因此不能定义它们的子类。 - 包装类中包装的基本类型的数据为常量,一旦构造了包装类,就不允许更改包装在其中的值。
| 基本类型 | byte | short | int | long | float | double | char | boolean |
|---|---|---|---|---|---|---|---|---|
| 对应的包装类 | Byte | Short | Integer | Long | Float | Double | Character | Boolean |
其中,六种数值类型(四个整型,两个浮点型)对应的包装类都派生于公共的超类java.lang.Number。Number为抽象类,没有具体的方法实现。
另外,除了以上基本类型对应的包装类。Java还提供BigInteger、BigDecimal这两个包装类,它们没有对相对应的基本类型,主要应用于高精度的运算,BigInteger支持任意精度的整数,BigDecimal支持任意精度带小数点的运算。
装箱和拆箱
基本类型数据转换成对应包装类对象称为装箱;
包装类对象转换成对应基本类型数据称为拆箱。
下面通过int类型和对应包装类Integer演示装箱、拆箱操作:
-
装箱
- 自动装箱
int a=3; Integer t=a;- 手动装箱
int a=3; Integer t=new Integer(a); -
拆箱
- 自动拆箱
Integer b=new Integer(4); int t=b;- 手动拆箱
Integer b=new Integer(4); int t=b.intValue();对自动装箱和自动拆箱操作进行反编译,得出下图结果:
{% asset_img 反编译结果.png %}
由此可知,自动拆箱操作其实是隐式地执行
int t=b.intValue();自动装箱操作其实是隐式地执行Integer t=Integer.valueOf(a)。Integer.valueOf()方法的源码如下:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
其中,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() {}
}
注意:为了避免频繁的创建和销毁对象而影响系统性能,自动装箱规范要求boolean、byte、char<=127,介于[-128,127]之间的short和int被包装到固定的对象中,存储在一个缓存空间(称为对象常量池)中,从而实现了对象的共享。故当参数介于[-128,127]之间时会直接引用对象常量池中的对象。
Integer three=100;
System.out.println("three==100的结果:"+(three==100));
Integer four=100;
System.out.println("three==four的结果:"+(three=four));
运行结果如下:
{% asset_img 运行结果.png %}
第一个结果true是因为包装类对象three进行了自动拆箱,从而变成了两个基本类型数据100和100之间的比较。
第二个true是因为Java对象常量池的实现,从而使three和four引用了对象常量池中的同一个对象。
注意:八种基本数据类型中,只有float和double对应的包装类没有应用对象常量池这个概念。Java的这种实现提高了效率,但是可能会让==得出我们不希望的结果,所以在对于两个包装类对象的比较时应调用equals方法。如果两个或其中一个包装类对象都是通过手动装箱(显式使用new关键字)创建的,那它们的存储地址肯定不一样,==比较的结果也为false;因为引用类型,==比较的是两个变量存储的对象引用是否相等,则实例对象的存储地址是否一致。
String中的==和equals的区别
public class StringDemo {
public static void main(String[] args) {
// TODO Auto-generated method stub
String a = "abc";
String b = "abc";
String c = new String("abc");
System.out.println("a和b的内容是否相同:" + a.equals(b));
System.out.println("a和c的内容是否相同:" + b.equals(c));
System.out.println("a和b的地址是否相同:" + (a == b));
System.out.println("a和c的地址是否相同:" + (b == c));
}
}
这令我不能不想到包装类的对象常量池概念,但是字符串那么多组合方式感觉又不大可能。
第一种方式是在常量池中拿对象,如果找不到就在常量池中创建一个字符串;第二种方式是直接在堆内存空间创建一个新的对象。目前获得的信息暂时是这样子,进一步了解之后再来更新。
基本数据类型和字符串之间的装换
-
基本数据类型转换成字符串。采用静态方法toString()
int t1=2; String t2=Integer.toString(2); -
字符串转换为基本数据类型。
-
采用静态方法parseInt()
int t3=Integer.parseInt(t2); -
采用静态方法valueOf()
int t4=Integer.valueOf(t2);
-
包装类与基本数据类型的区别
主要可以从以下几个方面来说明:
显而易见的区别:
Java中只有八种基本类型不是对象,其余都是对象(包括包装类)。
声明方式的不同:
- 基本类型无需通过
new关键字来创建。 - 包装类对象需要通过
new关键字来创建;即便是自动装箱操作,其底层原理也是通过new关键字创建的对象。
作为成员变量时的默认初始值:
-
基本数据类型
-
数值类型
- 整型:包括
byte,int,short初始值为0;long初始值为0L。 - 浮点型:
float初始值为0.0f;double初始值为0.0d。
- 整型:包括
-
字符类型:
char初始值为'\u0000'。 -
布尔类型:
boolean初始值为false;
-
-
包装类
- 与其他的引用类型一样,默认初始值为
null。
- 与其他的引用类型一样,默认初始值为
使用上的不同:
集合等泛型机制中的类型变量只能是包装类或其他引用类型,而不能是基本数据类型。
存储方式及位置的不同:
- 基本类型变量是直接存储变量的值保存在栈中,能高效地进行存取。
- 包装类变量需要通过引用指向实例,具体的实例保存在堆中。