Java 基本数据类型包装类

本文最后更新于:2024年3月18日 凌晨

Java 基本数据类型包装类

为什么需要包装类?

  • 很多人会有疑问,既然Java中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢?
  • 因为Java是一种面向对象语言,很多地方都需要使用对象而不是基本数据类型,比如,在集合类中,我们是无法将int,double等类型放进去的,因为集合的容器要求元素是Object类型。
  • 为了让基本类型也具有对象的特征,就出现了包装类型,它相当于将基本类型"包装起来”,使得它具有了对象的性质,并且为其添加了属性和方法,丰富了基本类型的操作。

每个Java基本类型均有相应的类型包装类,例如Integer类包装int值,Float类包装float值,下表列出了基本数据类型和相应包装类。

基本数据类型 相应包装类
boolean Boolean
char Character
double Double
float Float
long Long
int Integer
short Short
byte Byte

构造方法

数值类型的包装类均提供了以相应基本类型的数据作为参数的构造方法,同时也提供了以字符串类型作为参数的构造方法,但如果字符串中数据不能代表相应数据类型则会抛出NumberFormatException异常,例如,Integer(整型)类的构造方法如下:

  • public Integer(int value):根据整数值创建Integer对象。
  • public Integer(String s):根据数字字符串创建Integer对象。

获得包装类的值

每个包装类均提供有相应的方法用来从包装对象中抽取相应的数据,对于Boolean类的对象,可以调用booleanValue()方法,对于Character对象,可以调用charValue()方法,其他6个类可以利用如下方法抽取数值数据,这些方法在它们的父类Number中定义,但Number类是抽象类,没有具体实现这些方法,每个包装子类提供了方法的具体实现。

  • public byte byteValue()
  • public short shortValue()
  • public int intValue()
  • public long longValue()
  • public float floatValue()
  • public double doubleValue()

静态方法

  • 包装类提供了各种static方法,例如,Character类提供有isDigit(char ch)方法可判断一个字符是否为数字,除Character类外的所有包装类均提供有valueOf(String s)的静态方法,它将得到一个相应类型的对象,例如,Long.valueOf("23")构造返回一个包装了数据值23的Long对象。

  • Integer类的toString(int i,in tradix)方法返回一个整数的某种进制表示形式,例如,Inter.toString(12,8)的结果为14,方法toString(12,8)的结果为14,方法toString(int i)返回十进制表示形式,方法toBinaryString(int i)返回整数的二进制串表示形式。

  • 还有一组非常有用的静态包装方法是parseXXX()方法,他们是:

    • public static byte Byte.parseByte(String s)
  • public static short Short.parseShort(String s)

    • public static int Integer.parseInt(String s)
  • public static long Long.parseLong(String s)

    • public static float Float.parseFloat(String s)
  • public static double Double.parseDouble(String s)

  • 这些方法以字符串作为参数,返回相应的基本类型数据,在分析时如果数据不正常均会抛出NumberFormatException异常。

拆箱与装箱

  • 将原始类型值转换成对应的对象,比如将int的变量转换成Integer对象,这个过程叫做装箱,反之将Integer对象转换成int类型值,这个过程叫做拆箱。

自动装箱与自动拆箱的实现原理

  • 既然Java提供了自动拆装箱的能力,那么,我们就来看一下,到底是什么原理,Java是如何实现的自动拆装箱功能。
  • 自动拆装箱的代码:
1
2
3
4
public static void main(String[]args){
Integer integer=1; // 装箱。
int i=integer; // 拆箱。
}
  • 对以上代码进行反编译后可以得到以下代码:
1
2
3
4
public static void main(String[]args){
Integer integer=Integer.valueOf(1);
int i=integer.intValue();
}

哪些地方会自动拆装箱

  1. 将基本数据类型放入集合类。
  2. 包装类型和基本类型的大小比较:包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,然后进行比较的。
  3. 包装类型的运算:两个包装类型之间的运算,会被自动拆箱成基本类型进行。
  4. 三目运算符的使用:当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。
1
2
3
4
5
6
7
8
9
boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;
// 其实在int k = flag ? i : j;这一行,会发生自动拆箱,反编译后代码如下:
boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;
  • 第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱,如果这个时候i的值为null,那么久会发生NPE,(自动拆箱导致空指针异常)
  1. 函数参数与返回值。

缓存

  • Java SE的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String... strings) {

Integer integer1 = 3;
Integer integer2 = 3;

if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");

Integer integer3 = 300;
Integer integer4 = 300;

if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
}
  • 我们普遍认为上面的两个判断的结果都是false,虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个if判断都是false的。
  • 在Java中,==比较的是对象引用,而equals()比较的是值。
  • 所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回false,奇怪的是,这里两个类似的if条件判断返回不同的布尔值。
  • 上面这段代码真正的输出结果:
1
2
integer1 == integer2
integer3 != integer4
  • 原因就和Integer中的缓存机制有关,在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能,整型对象通过使用相同的对象引用实现了缓存和重用。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
* 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() {}
}
  • 适用于整数值区间-128 至 +127
  • 只适用于自动装箱,使用构造函数创建对象不适用。
  • 我们只需要知道,当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。
  • 其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程,最大值127可以通过-XX:AutoBoxCacheMax=size修改。
  • 这使我们可以根据应用程序的实际情况灵活地调整来提高性能,到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的,在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。
  • 在Boxing Conversion部分的Java语言规范(JLS)规定如下:
  • 如果一个变量p的值是:
    • -128至127之间的整数。
    • true 和 false的布尔值。
    • ‘\u0000’至 ‘\u007f’之间的字符。
  • 范围内的时,将p包装成a和b两个对象时,可以直接使用a==b判断a和b的值是否相等。

笔试题

1
2
3
4
5
6
7
8
9
10
Integer i1 =59;
int i2 = 59;
Integer i3 = Integer.valueOf(59);
Integer i4 = new Integer(59);
System.out.println(i1 == i2); // true:包装类和基本类型比较时,包装类自动拆箱为基本类型。
System.out.println(i1 == i3); // true:数值59 在-128到127之间,上文所说的缓存,因此为true,若数值不在-128到127之间则为false
System.out.println(i1 == i4); // false:引用类型比较地址值,地址值不同。
System.out.println(i2 == i3); // true:i1的源码是i3,i2和i3比较结果和i2与i1比较结果相同,包装类和基本类型比较时自动拆箱。
System.out.println(i2 == i4); // true:包装类和基本类型比较时自动拆箱。
System.out.println(i3 == i4); // 同i1 == i4

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!