深入探讨Java基本数据类型及其包装类型的常量池技术,助力面试成功与编程能力提升

今天分享一份来自金蝶面试的Java基础面试真题:

  • 你了解Java中的几种基本数据类型吗?
  • 对于包装类型的常量池技术,你有什么了解?
  • 你知道自动装箱与拆箱的原理吗?

了解Java中的基本数据类型

Java语言拥有8种基本数据类型,具体如下:

  • 数字类型:
    • 4种整数型:byteshortintlong
    • 2种浮点型:floatdouble
  • 1种字符类型:char
  • 1种布尔类型:boolean

这8种基本数据类型的默认值和所占空间如下:

基本类型位数字节默认值取值范围
byte810-128 ~ 127
short1620-32768 ~ 32767
int3240-2147483648 ~ 2147483647
long6480L-9223372036854775808 ~ 9223372036854775807
char162'u0000'0 ~ 65535
float3240f1.4E-45 ~ 3.4028235E38
double6480d4.9E-324 ~ 1.7976931348623157E308
boolean1falsetrue、false

关于boolean类型,官方文档并未明确规定其占用位数,通常理解为1位,但实际上会根据JVM的实现来决定。

重要的是,Java的每种基本数据类型的存储空间大小是固定的,不会因计算机硬件架构不同而变化,这使得用Java写的程序比用其他多种语言编写的程序更具可移植性(详见《Java编程思想》2.2节)。

注意事项:

  1. 使用long类型数据时,数值后必须加上L,否则会被视为整型。
  2. char a = 'h'使用单引号,String a = "hello"使用双引号。

这八种基本类型都有对应的包装类:ByteShortIntegerLongFloatDoubleCharacterBoolean

包装类型在未赋值时是Null,而基本类型有默认值且非Null

从JVM的层面来看,基本数据类型直接存储在虚拟机栈中的局部变量表,而包装类型则是对象类型,实例存储在堆中。相较于对象类型,基本数据类型占用的空间较小。

你了解包装类型的常量池技术吗?

Java的基本类型包装类大多实现了常量池技术。

其中,ByteShortIntegerLong这四种包装类会默认缓存-128到127范围内的数值,Character类则缓存0到127之间的字符,而Boolean类仅返回TrueFalse

Integer缓存源码示例:

public static Integer valueOf(int i) {  
    if (i >= IntegerCache.low && i <= IntegerCache.high)  
        return IntegerCache.cache[i + (-IntegerCache.low)];  
    return new Integer(i);  
}  
private static class IntegerCache {  
    static final int low = -128;  
    static final int high;  
    static {  
        int h = 127;  
    }  
}

Character缓存源码示例:

public static Character valueOf(char c) {  
    if (c <= 127) {  
      return CharacterCache.cache[(int)c];  
    }  
    return new Character(c);  
}  
private static class CharacterCache {  
    private CharacterCache(){}  
    static final Character cache[] = new Character[127 + 1];  
    static {  
        for (int i = 0; i < cache.length; i++)  
            cache[i] = new Character((char)i);  
    }  
}

Boolean缓存源码示例:

public static Boolean valueOf(boolean b) {  
    return (b ? TRUE : FALSE);  
}

超出上述缓存范围的数值仍会创建新的对象,缓存范围的设计是为了在性能和资源之间取得平衡。两个浮点类型的包装类FloatDouble则没有实现常量池技术。

Integer i1 = 33;  
Integer i2 = 33;  
System.out.println(i1 == i2); // 输出 true  

Float i11 = 333f;  
Float i22 = 333f;  
System.out.println(i11 == i22); // 输出 false  

Double i3 = 1.2;  
Double i4 = 1.2;  
System.out.println(i3 == i4); // 输出 false  

接下来,考虑一下下面代码的输出结果是true还是false

Integer i1 = 40;  
Integer i2 = new Integer(40);  
System.out.println(i1 == i2);  

在这段代码中,Integer i1=40 实际上会进行装箱,也就是说等价于 Integer i1=Integer.valueOf(40) 。因此,i1 直接访问的是常量池中的对象。而Integer i2 = new Integer(40)则是创建了新的对象。

因此,结果是false。你答对了吗?

请记住:所有整型包装类对象之间的值比较,应该使用equals方法。

图片

自动装箱与拆箱的原理是什么?

什么是自动装箱和拆箱?

  • 装箱:将基本数据类型用其对应的引用类型包装起来;
  • 拆箱:将包装类型转换为基本数据类型;

例如:

Integer i = 10;  // 装箱  
int n = i;      // 拆箱  

上述代码对应的字节码如下:

L1  
    LINENUMBER 8 L1  
    ALOAD 0  
    BIPUSH 10  
    INVOKESTATIC java/lang/Integer.valueOf (I)Ljava/lang/Integer;  
    PUTFIELD AutoBoxTest.i : Ljava/lang/Integer;  
L2  
    LINENUMBER 9 L2  
    ALOAD 0  
    ALOAD 0  
    GETFIELD AutoBoxTest.i : Ljava/lang/Integer;  
    INVOKEVIRTUAL java/lang/Integer.intValue ()I  
    PUTFIELD AutoBoxTest.n : I  
    RETURN  

通过字节码可以看出,装箱实际上是调用了包装类的valueOf()方法,而拆箱则是调用了xxxValue()方法。

因此,

  • Integer i = 10 实质上等价于 Integer i = Integer.valueOf(10)
  • int n = i 实质上等价于 int n = i.intValue()

需要注意的是:频繁的拆装箱操作会严重影响系统性能,应尽量避免不必要的拆装箱。

private static long sum() {  
    // 应该使用 long 而不是 Long  
    Long sum = 0L;  
    for (long i = 0; i <= Integer.MAX_VALUE; i++)  
        sum += i;  
    return sum;  
}