跳到主要内容

数据类型与包装类

问题

Java 有哪些基本数据类型?包装类的作用是什么?自动装箱/拆箱是怎么回事?

答案

八大基本数据类型

Java 提供了 8 种基本数据类型(Primitive Types),分为 4 类:

类型关键字位数默认值范围
整型byte80-128 ~ 127
整型short160-32768 ~ 32767
整型int320-2^31 ~ 2^31-1
整型long640L-2^63 ~ 2^63-1
浮点型float320.0fIEEE 754
浮点型double640.0dIEEE 754
字符型char16'\u0000'0 ~ 65535
布尔型boolean1*falsetrue/false
boolean 的大小

JVM 规范没有明确规定 boolean 的大小。在 HotSpot 中,单个 boolean 变量占 4 字节(当作 int 处理),而 boolean[] 数组中每个元素占 1 字节。

包装类(Wrapper Class)

每个基本类型都有对应的包装类:

基本类型包装类继承关系
byteByteNumberObject
shortShortNumberObject
intIntegerNumberObject
longLongNumberObject
floatFloatNumberObject
doubleDoubleNumberObject
charCharacterObject
booleanBooleanObject

为什么需要包装类?

  1. 泛型不支持基本类型List<int> 不合法,必须用 List<Integer>
  2. 集合框架只能存对象Map<String, Integer> 中的 value 必须是对象
  3. 提供了丰富的工具方法Integer.parseInt()Integer.toBinaryString()
  4. 允许 null 值:数据库中的 NULL 无法用 int 表示,但 Integer 可以

自动装箱与拆箱

Java 5 引入了自动装箱(Autoboxing)和自动拆箱(Unboxing),编译器自动插入转换代码:

AutoboxingExample.java
// 自动装箱:编译器自动调用 Integer.valueOf(10)
Integer a = 10;

// 自动拆箱:编译器自动调用 a.intValue()
int b = a;

// 运算时自动拆箱
Integer x = 10;
Integer y = 20;
// x + y 实际执行的是 x.intValue() + y.intValue()
int sum = x + y;
拆箱的 NullPointerException

自动拆箱时如果包装类为 null,会抛出 NPE,这是常见的坑:

Integer num = null;
// 编译通过,但运行时抛出 NullPointerException
int value = num; // 等价于 num.intValue()

在从数据库查询结果映射到实体类时尤其需要注意,数据库字段可能为 NULL。

Integer 缓存池(IntegerCache)

这是面试最高频的考点之一。Integer.valueOf()-128 ~ 127 范围内的值使用缓存:

IntegerCacheDemo.java
// valueOf 使用缓存,相同值返回同一个对象
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true(同一个缓存对象)

Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println(c == d); // false(超出缓存范围,创建了新对象)

// 自动装箱本质上调用的就是 Integer.valueOf()
Integer e = 127;
Integer f = 127;
System.out.println(e == f); // true

Integer g = 128;
Integer h = 128;
System.out.println(g == h); // false

缓存池源码(JDK 8):

Integer.java(简化)
public static Integer valueOf(int i) {
// 如果值在缓存范围内,直接返回缓存的对象
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// 否则创建新的 Integer 对象
return new Integer(i);
}

private static class IntegerCache {
static final int low = -128;
static final int high; // 默认 127,可通过 JVM 参数调整
static final Integer[] cache;

static {
int h = 127;
// 可通过 -XX:AutoBoxCacheMax=<size> 调整上限
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for (int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
}
}
其他类型的缓存
  • Byte:全部缓存(-128 ~ 127,刚好覆盖全部值)
  • Short:-128 ~ 127
  • Long:-128 ~ 127
  • Character:0 ~ 127
  • BooleanTRUEFALSE 两个静态常量
  • Float / Double没有缓存

== 与 equals 的区别

比较方式基本类型引用类型(包装类)
==比较值比较内存地址(引用)
equals()比较值内容(包装类重写了 equals
EqualsDemo.java
// 基本类型用 == 比较值
int a = 10;
int b = 10;
System.out.println(a == b); // true

// 包装类用 == 比较的是引用地址
Integer x = new Integer(10);
Integer y = new Integer(10);
System.out.println(x == y); // false(两个不同的对象)
System.out.println(x.equals(y)); // true(值相等)

// 混合比较:包装类会自动拆箱
Integer m = 10;
int n = 10;
System.out.println(m == n); // true(m 拆箱后比较值)
面试要点

包装类之间比较值时必须使用 equals(),不要用 ==。即使在缓存范围内 == 碰巧为 true,也不应依赖这个行为。

类型转换

自动类型转换(隐式)—— 小范围 → 大范围:

byte → short → int → long → float → double

char

强制类型转换(显式)—— 大范围 → 小范围,可能有精度丢失:

double d = 3.14;
int i = (int) d; // 3,小数部分截断

long big = 130L;
byte small = (byte) big; // -126,溢出

整型字面量默认为 int,浮点字面量默认为 double

// 编译错误:整型字面量默认是 int,超出 int 范围需要加 L
// long num = 2147483648; // 编译错误
long num = 2147483648L; // 正确

// 编译错误:浮点字面量默认是 double,赋值给 float 需加 F
// float f = 3.14; // 编译错误
float f = 3.14F; // 正确

常见面试问题

Q1: int 和 Integer 有什么区别?

答案

维度intInteger
类型基本数据类型引用类型(包装类)
存储位置栈 / 对象字段内堆(对象)
默认值0null
泛型支持不支持支持
占用空间4 字节16 字节(对象头 12B + 数据 4B)
比较方式== 比较值== 比较引用,equals() 比较值

Q2: Integer a = 127; Integer b = 127; a == b 的结果是什么?为什么?

答案

结果是 true。自动装箱调用 Integer.valueOf(127),由于 127 在 IntegerCache 的缓存范围 [-128, 127] 内,两次调用返回的是同一个缓存对象,所以 == 比较的引用地址相同。如果换成 128,结果就是 false,因为超出缓存范围会创建新对象。

Q3: 自动装箱在什么场景需要特别注意?

答案

  1. 循环中的自动装箱:频繁创建包装类对象,导致性能下降和 GC 压力
    // 反例:每次 += 都会创建新的 Long 对象
    Long sum = 0L;
    for (int i = 0; i < 1000000; i++) {
    sum += i; // 拆箱 → 加法 → 装箱
    }
    // 正例:使用基本类型
    long sum = 0L;
  2. 拆箱时的 NPE:包装类为 null 时拆箱会抛 NullPointerException
  3. 集合中的比较List.contains() 内部使用 equals(),但如果不小心用了 == 做判断会出错

Q4: float 能精确表示所有 int 值吗?

答案

不能。float 有 32 位,但其中 1 位符号位、8 位指数位、23 位尾数,只能精确表示尾数在 23 位以内的整数(约 224=167772162^{24} = 16777216)。超过这个范围的 int 值赋给 float 会有精度丢失:

int big = 16777217; // 2^24 + 1
float f = big;
System.out.println((int) f); // 16777216,精度丢失

Q5: short s = 1; s = s + 1;short s = 1; s += 1; 有什么区别?

答案

  • s = s + 1; 编译错误。s + 11int 类型,s 会提升为 int,结果是 int,赋值回 short 需要强制转换。
  • s += 1; 编译通过。复合赋值运算符 += 隐含了强制类型转换,等价于 s = (short)(s + 1);

Q6: switch 语句支持哪些数据类型?

答案

Java 版本支持类型
1.0 ~ 1.4byteshortcharint
5.0新增对应的包装类(自动拆箱)、enum
7新增 String
14+ (preview) / 17 (preview) / 21模式匹配(Pattern Matching for switch)

不支持 longfloatdoubleboolean

Q7: 为什么 Java 不支持无符号整数?

答案

Java 设计者 James Gosling 认为无符号整数容易引发混淆和 Bug(尤其是有符号与无符号混合运算时)。Java 8 开始提供了 Integer.toUnsignedLong()Integer.divideUnsigned() 等方法来处理无符号需求,但底层表示仍然是有符号的。

Q8: 基本类型存在栈上还是堆上?

答案

不绝对,取决于上下文:

  • 局部变量中的基本类型:存在栈帧的局部变量表中
  • 对象的字段中的基本类型:随对象存在堆上
  • 数组中的基本类型:数组对象在堆上,元素也在堆上

另外 JIT 编译器的逃逸分析可能把未逃逸的对象标量替换到栈上。

相关链接