深入探讨Java基本数据类型及其包装类型的常量池技术,助力面试成功与编程能力提升
深入探讨Java基本数据类型及其包装类型的常量池技术,助力面试成功与编程能力提升
今天分享一份来自金蝶面试的Java基础面试真题:
- 你了解Java中的几种基本数据类型吗?
- 对于包装类型的常量池技术,你有什么了解?
- 你知道自动装箱与拆箱的原理吗?
了解Java中的基本数据类型
Java语言拥有8种基本数据类型,具体如下:
- 数字类型:
- 4种整数型:
byte
、short
、int
、long
- 2种浮点型:
float
、double
- 4种整数型:
- 1种字符类型:
char
- 1种布尔类型:
boolean
这8种基本数据类型的默认值和所占空间如下:
基本类型 | 位数 | 字节 | 默认值 | 取值范围 |
---|---|---|---|---|
byte | 8 | 1 | 0 | -128 ~ 127 |
short | 16 | 2 | 0 | -32768 ~ 32767 |
int | 32 | 4 | 0 | -2147483648 ~ 2147483647 |
long | 64 | 8 | 0L | -9223372036854775808 ~ 9223372036854775807 |
char | 16 | 2 | 'u0000' | 0 ~ 65535 |
float | 32 | 4 | 0f | 1.4E-45 ~ 3.4028235E38 |
double | 64 | 8 | 0d | 4.9E-324 ~ 1.7976931348623157E308 |
boolean | 1 | false | true、false |
关于boolean
类型,官方文档并未明确规定其占用位数,通常理解为1位,但实际上会根据JVM的实现来决定。
重要的是,Java的每种基本数据类型的存储空间大小是固定的,不会因计算机硬件架构不同而变化,这使得用Java写的程序比用其他多种语言编写的程序更具可移植性(详见《Java编程思想》2.2节)。
注意事项:
- 使用
long
类型数据时,数值后必须加上L,否则会被视为整型。 char a = 'h'
使用单引号,String a = "hello"
使用双引号。
这八种基本类型都有对应的包装类:Byte
、Short
、Integer
、Long
、Float
、Double
、Character
和Boolean
。
包装类型在未赋值时是Null
,而基本类型有默认值且非Null
。
从JVM的层面来看,基本数据类型直接存储在虚拟机栈中的局部变量表,而包装类型则是对象类型,实例存储在堆中。相较于对象类型,基本数据类型占用的空间较小。
你了解包装类型的常量池技术吗?
Java的基本类型包装类大多实现了常量池技术。
其中,Byte
、Short
、Integer
和Long
这四种包装类会默认缓存-128到127范围内的数值,Character
类则缓存0到127之间的字符,而Boolean
类仅返回True
或False
。
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);
}
超出上述缓存范围的数值仍会创建新的对象,缓存范围的设计是为了在性能和资源之间取得平衡。两个浮点类型的包装类Float
和Double
则没有实现常量池技术。
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;
}