文章目录
- 包装类
- 前言
- 一、为什么需要包装类
- 二、包装类的使用
- 1、拆箱与装箱
- 2、经典面试题
- 三、基本数据类型和包装类的区别
- 1.包装类可以为null,但基本类型不可以
- 2、包装类可用于泛型,而基本数据类型不可以
- 3、基本数据类型比包装类更高效
- 4、两者的判等规则不同
- 总结
包装类
前言
JDK中有三个特殊的类:包装类、String类以及Object类,这三个类是开发中非常常见的类,所以今天拿出来单独讲讲。每一个类被设计出来都是有其存在的必要性,尤其是Object类,自打JDK1.0以后就默认导入了,可见其重要性。
一、为什么需要包装类
众所周知,Java语言是一个面向对象的语言,但是Java中的基本数据类型却不是面向对象的。基本数据类型不具备对象的特性(没有成员变量和API可以调用)因此,Java为每种数据类型分别设计了对应的类,即包装类。
基本数据类型 | 对应的包装类 |
byte | Byte |
short | Short |
int | Integer |
long | Long |
char | Character |
float | Float |
double | Double |
boolean | Boolean |
包装类的特点:
1)所有包装类都是final类型,不能创建他们的子类。
2)包装类是不可变类,一个包装类的对象创建之后,他所包含的基本数据类型就不能被改变。
3)可以看到所有的包装类都是继承自Object类的,其中除了Boolean和Character,其他是多重继承的。
二、包装类的使用
包装类使用最多,也是被提到最多的,就是装箱(boxing)和拆箱(unboxing),当然还有一些API。
1、拆箱与装箱
既然这些包装类是为了让基本数据类型面向对象而设计出来的,那么必然要实现两者的相互转换,而基本数据类型—>包装类就是装箱,包装类—>基本数据类型就是拆箱,有手动和自动两种方式。
手动使用方法:
包装类 | 装箱 | 拆箱 |
Byte | valueOf() | byteValue() |
Short | valueOf() | shortValue() |
Integer | valueOf() | intValue() |
Long | valueOf() | longValue() |
Float | valueOf() | floatValue() |
Double | valueOf() | doubleValue() |
Character | valueOf() | charValue() |
Boolean | valueOf() | booleanValue() |
自动拆装箱(以Integer为例):
自动装箱与自动拆箱实际上是JDK默认调用了valueOf()和intValue()
//自动装箱,执行了Integer iii = Integer.valueOf(5)
Integer i=5;
//自动拆箱,实际上执行了 int iii2 = iii.intValue()
int ii=i;
System.out.println(ii);
2、经典面试题
先看题:
// 1)基本类型和包装类型
int a = 100;
Integer b = 100;
System.out.println(a == b);
// 2)两个包装类型
Integer c = 100;
Integer d = 100;
System.out.println(c == d);
// 3)
c = 200;
d = 200;
System.out.println(c == d);
答案是什么呢?
第一段代码,基本类型和包装类型进行 == 比较,这时候 b 会自动拆箱,直接和 a 比较值,所以结果为 true。
第二段代码,两个包装类型都被赋值为了 100,这时候会进行自动装箱,那 == 的结果会是什么呢?
按照java中对象的比较需要使用eaquls()来看:将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符。那结果是 false?但这次的结果却是 true,是不是感觉很意外?
第三段代码,两个包装类型重新被赋值为了 200,这时候仍然会进行自动装箱,但此时的结果又变成了false。为什么?为什么?为什么?
解释:
我们来看看源码:
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() {}
}
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
java针对-128—127之间的数据做了一个数据缓冲池,如果数据是该范围内的数,会从 IntegerCache 中取,然后比较,所以第二段代码(100 在这个范围之内)的结果是 true,而第三段代码(200 不在这个范围之内,所以 new 出来了两个 Integer 对象)的结果是 false。
希望大家记住一点:当需要进行自动装箱时,如果数字在 -128 至 127 之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
三、基本数据类型和包装类的区别
1.包装类可以为null,但基本类型不可以
这一区别使得包装类可以应用于POJO(Plain Ordinary Java Object),翻译一下就是无规则的Java对象,只有属性字段和getter setter方法。
代码示例如下:
class Writer {
private Integer age;
private String name;
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
为什么这种无规则的java对象内部属性必须使用包装类型呢?这是因为数据库的查询结果可能是 null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把 Integer 对象转换成 int 值),就会抛NPE(NullPointerException) 异常。
举个栗子来说就是我们如果自定义了一个Student类,其中有一个属性是成绩score,如果用Integer而不用int定义,一次考试,学生可能没考,值是null,也可能考了,但考了0分,值是0,这两个表达的状态明显不一样。如果我们用包装类型的话,null的话证明没有考,0的话证明考了0分;但是如果我们用基本类型的话,这两种情况都是一个样的,没法区分的。
结论:使用基本类型可能会在一定程度上增大系统的复杂性,让坑变得越来越多。 还有这种使用包装类型定义变量的方式,通过异常来阻断程序的运行,进而可以被立马识别到这种綫上问题。但是我们如果使用基本数据类型的话,系统可能认为无异常,从而继续运行。
2、包装类可用于泛型,而基本数据类型不可以
泛型不能使用基本类型,因为使用基本类型时会编译出错。
List<int> list = new ArrayList<>(); // 提示 Syntax error, insert "Dimensions" to complete ReferenceType
List<Integer> list = new ArrayList<>();
为什么呢?因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是 Object 类及其子类——基本类型是个特例。
3、基本数据类型比包装类更高效
基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。在程序运行过程中,包装类需要进行解引用。比如下面这段代码,性能就很差。
long t1 = System.currentTimeMillis();
Long sum = 0L;
for (int i = 0; i < Integer.MAX_VALUE;i++) {
sum += i;
}
long t2 = System.currentTimeMillis();
System.out.println(t2-t1);
sum 由于被声明成了包装类型 Long 而不是基本类型 long,所以 sum += i 进行了大量的拆装箱操作(sum 先拆箱和 i 相加,然后再装箱赋值给 sum),导致这段代码运行完花费的时间足足有 2986 毫秒;如果把 sum 换成基本类型 long,时间就仅有 554 毫秒,完全不一个量级。
4、两者的判等规则不同
Integer a = new Integer(10); //a和b存放在栈区,里面放着堆区的一个地址 10存放在堆区的该地址的内存里
Integer b = new Integer(10);
int c=20;
int d=20;
System.out.println(c == d); //true 直接比较栈区存储的数值 相等返回true
System.out.println(a == b); // false 比较的是a、b的地址
System.out.println(a.equals(b)); // true 比较的是其指向的地址内部的值10
对于基本数据类型,判断两个变量相等的情况就是使用“= =” 判定符,两者只要值相等,那么其结果就为true。
对于包装类,既然其是类,那么比较两者相等时就需要使用eaquls方法,判断其内部的值是否相等,否则使用“= =”判定符号比较的是其指向的地址是否相等。
将“= =”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符。
总结
以上就是包装类和基本数据类型的所有内容了,String类和Object类内容比较多,隔两天再写~