包装类
- 0:总结
- 1:八种基本类型对应的包装类
- 2:装箱与拆箱
- 3:自动装箱与拆箱
- 4:包装类中的缓存机制
- 5:包装类的四则运算、位运算、比较运算、逻辑运算
- 6:包装类作为方法的形参、返回值
- 7:包装类作为集合的元素
- 8:包装类使用过程中有可能引起的空指针异常
- 9:为什么需要包装类?有了包装类又为什么要保留基本数据类型?(包装类的优缺点)
0:总结
1:意味着所有的“数字型”包装类都可以互相转型
2:装箱(用包装类提供的构造器实现)与拆箱(调用包装类实现Number的xxxValue方法)
3:自动装箱(实际是调用ValueOf()方法)与拆箱(实际是调用xxxValue方法)
4:创建包装类对象有两种方式:new关键字、valueOf()方法
- 使用valueOf()方法创建对象时,就直接返回已经缓存的对象,也就是说不会再新建对象
- 当使用new关键字or使用valueOf()方法创建小于-128大于127的值对象时,就会创建新对象
5:由于缓存机制的存在,当需要比较两个Integer对象的值是否相等时,记住要用equals()方法
6:如果仅仅是打印两个包装类型对象求和的结果,是不会有将和值重新转换成该包装类型的步骤的
7:小结:不同包装类型对象是不能直接转换的,不过有两种途径可以代替:
- 一种是上不同包装类对象进行四则运算后赋给某一种类型;
- 另一种就是利用包装类的方法
8:两个同类型的包装类对象进行比较时比较的其实是各自的基本类型数值
9:两个不同类型的包装类对象进行比较时则在比较基本类型数值之前,会有类型提升or强制类型转换
10:为什么需要包装类?
- 增强了Java面向对象的性质
- 很多地方都需要使用对象而不是基本数据类型
- 包装类还为基本类型添加了属性和方法,丰富了基本类型的操作
11:为什么要保留基本数据类型?
- Java提供了基本数据类型,这种数据的变量不需要使用new在堆上创建,而是直接在栈内存中存储,因此会更加高效
1:八种基本类型对应的包装类
包装类均位于java.lang包
在这八个类中,除了Character和Boolean以外,其他的都是“数字型”,“数字型”都是java.lang.Number的子类。Number类是抽象类,因此它的抽象方法,所有子类都需要提供实现。Number类提供了抽象方法:intValue()、longValue()、floatValue()、doubleValue(),意味着所有的“数字型”包装类都可以互相转型
2:装箱与拆箱
- 装箱:将基本数据类型变为包装类对象,利用每一个包装类提供的构造器实现装箱处理。
Integer num = new Integer(55) ; // 装箱
- 拆箱:将包装类中包装的基本数据类型取出。利用Number类中提供的六种方法。
boolean、char类型分别通过Boolean类中的booleanValue、Character类中的charValue方法拆箱
int data = num.intValue() ; // 拆箱
Number类
在JDK1.5之后,提供了自动拆装箱的机制
3:自动装箱与拆箱
// 自动装箱
Integer x = 65 ;
// 可以直接利用包装类对象操作
System.out.println(++x * 6 );
// 自动拆箱
int y = x ;
自动装箱拆箱到底是怎么实现的呢?(其他包装类相同)
-
自动装箱:调用Integer的valueOf()方法将原始类型值转换成对象
-
自动拆箱:调用Integer的intValue()方法,其他的(xxxValue())这类的方法将对象转换成原始类型值
4:包装类中的缓存机制
前面说到创建包装类对象有两种方式:new关键字、valueOf()方法。我们来看一段代码感受一下它们的区别。
//2、包装类中的缓存机制
Integer num3 = 10;
Integer num4 = 10;
Integer num5 = new Integer(20);
Integer num6 = new Integer(20);
Integer num7 = 128;
Integer num8 = 128;
System.out.println((num3==num4) +" "+ num3.equals(num4));
System.out.println((num5==num6) +" "+ num5.equals(num6));
System.out.println((num7==num8) +" "+ num7.equals(num8));
运行结果为
我们看下它的反编译代码
Integer integer = Integer.valueOf(10);
Integer integer1 = Integer.valueOf(10);
Integer integer2 = new Integer(20);
Integer integer3 = new Integer(20);
Integer integer4 = Integer.valueOf(128);
Integer integer5 = Integer.valueOf(128);
System.out.println((new StringBuilder()).append(integer == integer1).append("\t").append(integer.equals(integer1)).toString());
System.out.println((new StringBuilder()).append(integer2 == integer3).append("\t").append(integer2.equals(integer3)).toString());
System.out.println((new StringBuilder()).append(integer4 == integer5).append("\t").append(integer4.equals(integer5)).toString());
首先,我们查看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);
}
再查看下Integer的内部类IntegerCache的cache数组成员、low、high成员
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;
}
可以发现,只要Integer类第一次被使用到,Integer的静态内部类就被加载,加载的时候会创建-128到127的Integer对象,同时创建一个数组cache来缓存这些对象。
当使用valueOf()方法创建对象时,就直接返回已经缓存的对象,也就是说不会再新建对象;
当使用new关键字or使用valueOf()方法创建小于-128大于127的值对象时,就会创建新对象
//2、包装类中的缓存机制
Integer num3 = 10;
Integer num4 = 10;
Integer num5 = new Integer(20);
Integer num6 = new Integer(20);
Integer num7 = 128;
Integer num8 = 128;
由于num3、num4都小于等于127,它们指向的是同一个缓存的Integer对象,所以用==进行比较的结果是true;
num5、num6由于使用new关键字指向的是两个不同的新对象,结果为false;
num7、num8虽然是采用自动装箱的方式,但执行valueOf()方法的时候,由于不满足条件i >= IntegerCache.low && i <= IntegerCache.high,而同样新建了两个不同的新对象,结果同样是false。
除去Integer,其他包装类有没有缓存呢?
-
Byte,Short,Long类型有缓存(-128-127)
-
Character缓存0-127
-
Boolean缓存TRUE、FALSE
接着,我们再来看看源码中Integer的equals()方法的实现
public boolean equals(Object obj) {
if (obj instanceof Integer) {
return value == ((Integer)obj).intValue();
}
return false;
}
可见equals()方法比较的是Integer对象的值,而不是像= =一样比较的是对象是否是同一个对象。
所以,当需要比较两个Integer对象的值是否相等时,记住要用equals()方法。
用= =比较的话由于缓存机制的存在,可能产生一些让人困扰的结果。
5:包装类的四则运算、位运算、比较运算、逻辑运算
5.1 四则运算和位运算
//四则运算、位运算
Integer num9 = 1;
Integer num10 = 2;
Integer num11 = num9 + num10;
Short num12 = 5;
Integer num13 = num9 + num12;
Long num14 = num9 + 10L;
System.out.println(num9 << 1); //位运算
System.out.println(num9 +" "+ num10 +" "+ num11 +" "+ num12 +" "+ num13 +" "+ num14);
反编译结果如下
Integer integer = Integer.valueOf(1);
Integer integer1 = Integer.valueOf(2);
Integer integer2 = Integer.valueOf(integer.intValue() + integer1.intValue());
Short short1 = Short.valueOf((short)5);
Integer integer3 = Integer.valueOf(integer.intValue() + short1.shortValue());
Long long1 = Long.valueOf((long)integer.intValue() + 10L);
System.out.println(integer.intValue() << 1);
System.out.println((new StringBuilder()).append(integer).append("\t").append(integer1).append("\t").append(integer2).append("\t").append(short1).append("\t").append(integer3).append("\t").append(long1).toString());
可以看到Integer num11 = num9 + num10; 这一句被划分为3个步骤:将两个Integer对象分别进行拆箱;将拆箱得到的两个int数值相加求其和;将和值进行装箱,从而将num11指向缓存数组中值为3的Integer对象。
而Short num12 = 5; 这一句则先将5强制转换成short类型,再将其装箱把值为5的Short对象的引用赋给num12。
而Integer num13 = num9 + num12; 这一句除了Integer num11 = num9 + num10;的3个步骤,中间还有short+int=int的类型自动提升的过程。
而Long num14 = num9 + 10L; 这一句Integer num11 = num9 + num10;的3个步骤,中间还有强制类型转换的过程。需要注意的是,如果是Long num14 = num9 + num10; 的话就会出现类型不匹配的错误,因为num9、num10拆箱之后相加的和是int类型,而Long.valueOf(long)需要的形参是long类型,自然会出错。我们也可以看到,当包装类型对象和基本类型数据进行四则运算的时候,对象是会被拆箱的,然后再按基本类型数据的运算规则进行运算。
另外,如果仅仅是打印两个包装类型对象求和的结果,是不会有将和值重新转换成该包装类型的步骤的,如下面所示
System.out.println(num9 + num10);
反编译结果
System.out.println(integer.intValue() + integer1.intValue());
这里还需要注意一点,尽管基本类型有自动类型提升/强制类型转换,包装类是没有类似的用法的。下面的做法是错的。
Short num3 = 10;
Integer num4 = num3; //错误: 不兼容的类型: Short无法转换为Integer
Long num5 = (Long)num4; //错误: 不兼容的类型: Integer无法转换为Long
Double num6 = num5; //错误: 不兼容的类型: Long无法转换为Double
小结:不同包装类型对象是不能直接转换的,不过有两种途径可以代替:
- 一种是上面讨论的不同包装类对象进行四则运算后赋给某一种类型;
- 另一种就是利用包装类的方法
Integer a = 20;
Long b = a.longValue();
Short c = b.shortValue();
System.out.println(a +" "+ b +" "+ c);
5.2 比较运算和逻辑运算
Integer num9 = 100;
Integer num10 = 200;
Short num11 = 50;
Long num12 = 50L;
System.out.println((num9<num10) +" "+ (num9<200) +" "+ (num9<num11) +" "+ (num9<num12) +" "+ (num9<10L));
反编译结果为
Integer integer = Integer.valueOf(100);
Integer integer1 = Integer.valueOf(200);
Short short1 = Short.valueOf((short)50);
Long long1 = Long.valueOf(50L);
System.out.println((new StringBuilder()).append(integer.intValue() < integer1.intValue()).append("\t").append(integer.intValue() < 200).append("\t").append(integer.intValue() < short1.shortValue()).append("\t").append((long)integer.intValue() < long1.longValue()).append("\t").append((long)integer.intValue() < 10L).toString());
可以看到,
- 两个同类型的包装类对象进行比较时比较的其实是各自的基本类型数值,如num9 < num10;
- 两个不同类型的包装类对象进行比较时则在比较基本类型数值之前,会有类型提升or强制类型转换,如num9 < num11,num9 < num12。
当想比较两个对象是否相等时,注意要使用equals()方法,从前面的讨论也知道,使用==的话比较的其实是引用的对象是否同一个,一般不满足我们的需求。
Integer num13 = new Integer(100);
System.out.println(num9.equals(num13) +" "+ num9.equals(50));
逻辑运算举例:
System.out.println((num9&1));
反编译结果为
System.out.println(integer.intValue() & 1);
6:包装类作为方法的形参、返回值
//包装类作为方法的形参、返回值
public static Integer intToInteger(int i) {
return i;
}
public static int integerToInt(Integer i) {
return i;
}
反编译结果为
public static Integer intToInteger(int i)
{
return Integer.valueOf(i);
}
public static int integerToInt(Integer integer)
{
return integer.intValue();
}
7:包装类作为集合的元素
//包装类作为集合元素
List list = new ArrayList();
list.add(1);
list.add(new Object());
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
可以发现,虽然集合元素要求是对象,add()方法的形参也是对象(public boolean add(E e)),但由于自动装箱,基本数据类型也可以直接加入集合中
List<Integer> list = new ArrayList<>();
for (int i=0; i<5; i++) {
list.add(i);
}
Iterator it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
反编译结果为
ArrayList arraylist = new ArrayList();
for(int i = 0; i < 5; i++)
arraylist.add(Integer.valueOf(i));
for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(iterator.next()));
8:包装类使用过程中有可能引起的空指针异常
//注意包装类可能产生的空引用异常
Boolean flag1 = false;
System.out.println(flag1?"命题为真":"命题为假");
Boolean flag2 = null;
System.out.println(flag2?"命题为真":"命题为假");
Boolean flag3 = true;
这里只是简单演示空指针异常。平时使用时需要注意这一点,比如当Boolean的对象作为形参时,在方法执行体的头部需要做下null检测。
上述代码的反编译结果为
Boolean boolean1 = Boolean.valueOf(false);
System.out.println(boolean1.booleanValue() ? "\u547D\u9898\u4E3A\u771F" : "\u547D\u9898\u4E3A\u5047");
Boolean boolean2 = null;
System.out.println(boolean2.booleanValue() ? "\u547D\u9898\u4E3A\u771F" : "\u547D\u9898\u4E3A\u5047");
Boolean boolean3 = Boolean.valueOf(true);
可见三目运算符的条件表达式的位置一定是boolean值,如果你传入的是Boolean对象,则会自动拆箱转换为boolean值。
另外,三目运算符的其他两个表达式位置也是如此,会把包装类对象转换为相应的基本类型对象。
9:为什么需要包装类?有了包装类又为什么要保留基本数据类型?(包装类的优缺点)
为什么需要包装类?
首先,Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,将每个基本数据类型设计一个对应的类进行代表,这种方式增强了Java面向对象的性质。
其次,如果仅仅有基本数据类型,那么在实际使用时将存在很多的不便,很多地方都需要使用对象而不是基本数据类型。比如,在集合类中,我们是无法将int 、double等类型放进去的,因为集合的容器要求元素是Object类型。而包装类型的存在使得向集合中传入数值成为可能,包装类的存在弥补了基本数据类型的不足。
此外,包装类还为基本类型添加了属性和方法,丰富了基本类型的操作。如当我们想知道int取值范围的最小值,我们需要通过运算,如下面所示,但是有了包装类,我们可以直接使用Integer.MAX_VALUE即可。
//求int的最大值
int max = 0;
int flag = 1;
for (int i=0; i<31; i++) {
max += flag;
flag = flag << 1;
}
System.out.println(max +" "+ Integer.MAX_VALUE); //2147483647 2147483647
为什么要保留基本数据类型?
我们都知道在Java语言中,用new关键字创建的对象是存储在堆里的,我们通过栈中的引用来使用这些对象,所以,对象本身来说是比较消耗资源的。对于经常用到的类型,如int等,如果我们每次使用这种变量的时候都需要new一个对象的话,就会比较笨重了。所以,Java提供了基本数据类型,这种数据的变量不需要使用new在堆上创建,而是直接在栈内存中存储,因此会更加高效。