基本数据类型及对应的包装类
boolean --------------------Boolean
byte --------------------Byte
short --------------------Short
int ---------------------Integer
long ---------------------Long
float ---------------------Float
double --------------------Double
char ----------------------Character
一、为什么要需要包装类
原因有二:
1、java的设计思想是万物即对象。java中只有这八个基本数据类型不是对象,为了更好的体现面向对象的设计思想,就为这八个基本数据类型提供包装类。
2、包装类中提供了很多方法和属性,比基础数据类型功能多。
二、几个包装类的特点:
1、这八个包装类都存在java.lang包下,使用时不需要导包。
2、六个与数字有关的包装类,都继承了Number类。将Number类下 xxxValue() 重写, 将一个包装类类型转化为该方法对应的基本类型(拆包)。
以Integer包下的源码为例:
short i = integer.shortValue();//这个方法主要是用来转换数据类型
3.八个包装类都实现了Serializable, Comparable接口,因此都有这两个接口中定义的方法。支持序列化和反序列化。
4、除Character类,其他七个类每个类都已一个含有自己类型参数的构造方法以及一个含有String 类型的构造方法重载。
Character 类中只有一个含有char 类型参数的构造方法。
5、虽然包装类是类,但是赋值时不一定需要通过构造函数。
例子:
这是因为在JDK1.5之后,包装类可以自动拆装包,因此包装类可以直接赋值,并且当包装类和对应数据类型的数据可以直接进行比较
6、Inetger.parseInt("123")与new Integer("123")区别
两个方式都是将字符串类型转变成int类型。但是第一个时直接将字符串转变成int类型,但是第二个通过调用构造函数方式,先将字符串构建成Integer对象,再通过自动拆包转变成int类型.
7、我们可以在代码中查看某种类型的取值范围,代码如下:
三、高频区间缓存
这个特性是包装类很重要的用途之一,用于高频区间的数据缓存。以Integer为例来说,在数值区间为-128~127时,会直接复用已有对象。在这区间之外的数字才会在堆上产生。
为什么会有高频区域缓存?以Integer为例,先看Integer的底层代码
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
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() {}
}
从这段代码中我们可以看出,在Integer类中定义了一个静态的内部类,并且在这个静态内部类中定义了一个静态的数组Integer cache[],这个数组中存储的值为-128~127,这个数组就是高频数据缓存。当调用Integer类时,首先会在内存的方法区中创建这些静态方法、属性。
从这段代码中可以看出,当我们给Integer对象赋值时,首先会去这个缓存中寻找。如果缓存中有,那么就直接从缓存中拿取这个值赋给对象,如果缓存中没有,则会new一个存有所需值的Integer对象,并将该对象返回。
以下面这些代码+包装类在内存中的加载方式图为例。
i1和i2这两个Integer类型的变量在创建时,没有调用构造函数,而是直接对其进行赋值(这两个变量会自动在底层进行装包),先去静态元素区中寻找,发现静态元素区中的cache[]数组中有这两个值,于是直接返回这两个值所在的地址引用,并不用在堆内存中new一个空间。由于静态元素拥有同一个空间,因此返回的地址引用是一样的。
i3和i4这两个变量,是调用了构造函数创建的,因此必须在堆内存中分别new一个空间,用来存储各自的值,并将各自空间的地址引用返回给各自的变量。
倘若这时,将i1和i2的值改为1000,则当创建这两个变量时,会发现者在cache[]这个数组中找不到该值,于是会帮助他俩在堆内存中各自new一个空间,再将所需的值从常量池中取来存在堆内存里,并返回在堆内存中的引用,因此,返回的值会不相同。
各包装类高频区域的取值范围:
Boolean:使用静态final定义,就会返回静态值;
Byte :缓存区 -128~127,全部缓存;
Short :缓存区 -128~127,部分缓存;
Character :缓存区0~127,部分缓存;
Long :缓存区-128~127,部分缓存;
Integer :缓存区-128~127,部分缓存
Float 和Double不会有缓存
注意:
Integer是唯一可以修改缓存范围的包装类。
根据注释可以得知,修改Integer缓存的方法是:
在VM optons中加入参数:-XX:AutoBoxCacheMax=<size>,这边的size中填的值,就是cache[]能缓存的最大值。
四、面试题涉及的知识点以及面试题
1、==和equals()
==可以比较基本数据类型 也可以比较引用数据类型 (变量中存储的内容)
如果比较基本类型比较是变量中存储的值
如果比较引用类型比较是变量中存储的地址引用
equals() 是Object类中继承过来的方法 每一个引用类型的对象都可以调用,默认继承的父类中equals()比较方法与==一致,但如果想要改变比较规则 可以重写equals方法
一般而言所有继承来的equals方法都会进行重写。由于Integer类就重写了equals() ,所以Integer比较的是数值 。
八种包装类都对equals()进行了重写,所以当用这个方法进行比较时,比较的都是存储的值。
2、高频缓存
这个知识点在前面有说,直接放运行结果:
3、面试题
(1)求下面的代码输出值:
Integer age = 10;
Integer age2 = 10;
Integer age3 = 133;
Integer age4 = 133;
System.out.println((age==age2)+""+(age3==age4));
true,false
(2)求下面Double代码的输出结果?
Double num = 10d;
Double num2 = 10d;
Double num3 = 133d;
Double num4 = 133d;
System.out.println((num==num2)+""+(num3==num4));
false,false(Double没有高频缓存,所以都是通过new堆内存,值在堆内存空间里。)
(3)一下程序的输出结果:
int i = 100;
Integer j = new Integer(100);
System.out.println(i==j);
Syste.out.println(j.equals(i));
true ,true
解析:当Integer和int进行比较时,Integer会自动拆箱为int。因此就相当于两个int比较。
(4)以下程序的执行结果是:
final int iMax = Integer.MAX_VALUE;
System.out.println(iMax+1);
A: 2147483648
B: -2147483648
C: 程序报错
D: 以上都不是
B
解析:这是因为在整数在内存中使用的是补码的形式。补码最高位为符号位,0表示整数,1表示负数。当执行+1时,最高位变成了1,结果就是B。
(5)以下程序的执行结果是:
Set<Short> set = new HashSet<>();
for(short i = 0;i<5;i++){
set.add(i);
set.remove(i-1);
}
System.out.println(set.size());
答案:5
解析:short类型-1之后就变成了int类型,remove()的时候在集合中找不到int类型的数据,所以就没有删除任何元素。
(6)short s=2;s=s+1; 会报错么? short s=2;s+=1;会报错么?
第一个报错,第二个正确。因为当short类型的变量进行s+1操作时,操作结果会自动变成int类型,将int类型的结果再赋值给short型的s时,就会报错。而+=操作符是JVM的操作符,会经过特殊处理,因此不会报错。(在整数计算时,所有精度小于int的类型在计算时,会自动将结果变成int类型。)
(7)一下程序的执行结果是:
Integer i1 = new Integer(10);
Integer i2 = new Integer(10);
Integer i3 = Integer.valueOf(10);
Integer i4 = Integer.valueOf(10);
System.out.println((i1==i2)+""+(i2==i3)+""+(i3==i4));
false,false;true;
i1和i2是调用构造函数,每次会在堆内存中new一个新对象;i3,i4通过Integer的valueOf()方法,该方法在创建值时,会先使用高频缓存池中的对象。因此 ,i3,i4中存的地址都是同一个静态区地址。如果i3,i4中值不是10,而是>128的值,就应该是三个false了。