八股面试基础之数据类型详解篇

Java 八股面试基础之数据类型详解篇

前提补充知识点:原码、反码、补码详解

可以把它看成计算机为"如何用二进制表示负数,并让加减法容易计算"走过的三步。

先说结论:

原码最直观,但计算麻烦;反码让减法能转成加法,但有两个零;补码同时解决运算统一和零重复的问题,所以现代计算机的有符号整数通常使用补码。Java 的 byteshortintlong 都是补码表示。

以 4 位二进制表示整数为例。

表示法 +3 -3 范围 主要问题/意义
原码 0011 1011 -7 ~ +7 最直观,但加减法要分情况
反码 0011 1100 -7 ~ +7 可将减法转加法,但有 +0-0
补码 0011 1101 -8 ~ +7 加减法统一、零唯一,现代计算机使用

1. 原码:最符合人的直觉

原码(sign-magnitude)用最高位表示正负:

  • 0:正数
  • 1:负数
  • 后面的位表示绝对值

所以:

复制代码
+3 = 0011
-3 = 1011

它的意义是容易理解:把最高位当"正负号",后面直接写数值。

但问题是,计算机不能直接把原码相加当作普通加法:

复制代码
+3 + (-3)
0011 + 1011 = 1110

结果并不是 0。CPU 必须先判断两个数的符号,再决定做"绝对值加法"还是"绝对值相减",硬件逻辑更复杂。

而且原码有两个零:

复制代码
+0 = 0000
-0 = 1000

这会浪费一个编码,也增加比较与判断的复杂度。

2. 反码:让负数更适合做加法

反码的规则是:

  • 正数的反码与原码相同;
  • 负数的反码:保留符号位,其余位按位取反。

因此:

复制代码
+3 的原码/反码:0011
-3 的原码:      1011
-3 的反码:      1100

这样,3 + (-3) 的结果更接近正确答案:

复制代码
  0011   // +3
+ 1100   // -3 的反码
------
  1111   // -0

反码的历史意义是:将减法尽量转化为二进制加法。不过如果产生最高位进位,还要把进位"绕回去"加到最低位(end-around carry),规则仍不够自然。

它也仍有两个零:

复制代码
+0 = 0000
-0 = 1111

3. 补码:在反码基础上加 1

补码的规则是:

  • 正数的补码与原码相同;
  • 负数的补码 = 对其绝对值的二进制按位取反,再加 1

所以 -3

复制代码
+3:          0011
按位取反:     1100
加 1:        1101   // -3 的补码

现在做计算:

复制代码
  0011   // +3
+ 1101   // -3 的补码
------
1 0000

固定为 4 位时,最左侧溢出的 1 直接丢弃,得到:

复制代码
0000  // 0

于是 CPU 可以用同一个加法器处理:

复制代码
a - b  等价于  a + (-b)

补码还消除了 -0

复制代码
0000 = 0

原本表示 -0 的那个编码被利用起来表示最小负数:

复制代码
1000 = -8

因此,4 位补码范围是 -8 ~ +7;32 位 int 的范围就是:

复制代码
-2^31 ~ 2^31 - 1

一句适合面试的总结:

原码通过符号位直接表示正负,直观但运算要区分符号;反码通过按位取反让减法可以更多地转为加法,但仍有正零和负零;补码在反码基础上加一,使加减法能统一使用二进制加法器、零只有一种表示,并能充分利用全部二进制编码,因此成为现代计算机有符号整数的标准表示方式。

一、八种基本的数据类型

Java 支持数据类型分为两类:基本数据类型和引用数据类型。

基本数据类型共有 8 种,可分为三类:

  • 数值型:整数类型(byteshortintlong)和浮点类型(floatdouble)。
  • 字符型:char
  • 布尔型:boolean
数据类型 占用大小(字节) 位数 取值范围 默认值 描述
byte 1 8 -128 (-2^7)127 (2^7 - 1) 0 最小的整数类型,适合节省内存,例如处理文件或网络流时存储小范围整数。
short 2 16 -32768 (-2^15)32767 (2^15 - 1) 0 较少使用,通常在需要节省内存且数值范围在该区间时使用。
int 4 32 -2147483648 (-2^31)2147483647 (2^31 - 1) 0 最常用的整数类型,可满足大多数日常整数计算。
long 8 64 -9223372036854775808 (-2^63)9223372036854775807 (2^63 - 1) 0L 表示很大的整数;当 int 范围不够时使用,字面量后需加 Ll
float 4 32 -3.4028235E383.4028235E38 0.0f 单精度浮点数,精度相对较低;字面量后需加 Ff
double 8 64 -1.7976931348623157E3081.7976931348623157E308 0.0d 双精度浮点数,精度比 float 高,是 Java 中表示小数的默认类型。
char 2 16 '\u0000' (0)'\uffff' (65535) '\u0000' 表示单个字符,采用 Unicode 编码,可表示各种语言的字符。
boolean 无明确字节大小(理论上 1 位) 无明确位数 truefalse false 用于逻辑判断,只有两个取值,常用于条件判断和循环控制。

FloatDouble 的最大值会以科学记数法输出。结尾的 E+数字 表示 E 前的数字乘以 10 的相应次方:3.14E33.14 × 1000 = 31403.14E-33.14 / 1000 = 0.00314Float.MIN_VALUE / Double.MIN_VALUE 表示最小正非零值(如 1.4E-45),不是负的最小值;浮点类型的最小负值是 -MAX_VALUE

注意:

  • 浮点数默认类型为 double;声明 float 常量时,末尾必须加 fF
  • 整数默认类型为 int;声明 long 字面量时,末尾加 lL
  • 基本类型的包装类中,char 对应 Characterint 对应 Integer,其余通常为首字母大写。
  • char 是无符号类型,不能为负数,范围从 0 开始。

二、int 和 long 是多少位、多少字节?

  • int 是 32 位(bit),占 4 个字节(byte),是有符号整数,取值范围 -2^312^31 - 1。最大正数为 2,147,483,647;超出范围会溢出。
  • long 是 64 位,占 8 个字节,也是有符号整数,取值范围 -2^632^63 - 1。处理很大的整数(例如文件大小)时,若 int 范围不够,应使用 long

三、long 和 int 可以互转吗?

可以。long 的范围大于 int,因此 int → long 是安全的;long → int 可能导致数据丢失或溢出。

java 复制代码
int intValue = 10;
long longValue = intValue; // 自动转换,安全

long longValue = 100L;
int intValue = (int) longValue; // 强制类型转换,可能会有数据丢失或溢出

longValue 超出 int 范围时,转换结果是截断后的低位部分。转换前应检查值是否位于 int 的范围内,以避免数据丢失或溢出。

四、数据类型转换方式你知道哪些?

  • 自动类型转换(隐式转换) :当目标类型范围大于源类型时,Java 自动转换,无需显式转换。例如 int → longfloat → double
  • 强制类型转换(显式转换) :当目标类型范围小于源类型时,需要使用强制转换,可能导致数据丢失或溢出。例如 long → intdouble → int。语法:目标类型 变量名 = (目标类型) 源类型;
  • 字符串转换 :例如 Integer.parseInt() 将字符串转为 intDouble.parseDouble() 将字符串转为 double
  • 数值之间的转换 :可以通过包装类提供的方法进行,例如 CharacterInteger

五、类型互转会出现什么问题吗?

基本数据类型转换的问题

小范围数据类型赋给大范围数据类型时,Java 会自动转换,通常安全:

java 复制代码
int num = 100;
long bigNum = num; // 自动将 int 转换为 long

大范围数据类型赋给小范围数据类型时,可能发生数据溢出或精度损失。

数据溢出:目标类型无法容纳原数据时发生。byte 类型范围是 -128 ~ 127。300 的二进制为 00000001 00101100,强制转换为 byte 时会丢弃高位字节,只保留低 8 位 00101100,十进制为 44。

java 复制代码
int largeNum = 300;
byte b = (byte) largeNum; // b 的值为 44

精度损失:进行浮点数类型转换时可能发生。doubleint 时也会损失精度:

java 复制代码
double d = 3.14;
int i = (int) d; // i 的值为 3,小数部分 0.14 被舍弃
对象引用转换的问题

向上转型自动进行,通常安全:

java 复制代码
class Animal {}
class Dog extends Animal {}

Dog dog = new Dog();
Animal animal = dog; // 自动向上转型

向下转型需要手动进行,有风险。如果父类对象真实类型不是目标子类实例,转型时会抛出异常:

java 复制代码
Animal animal = new Animal();
Dog dog = (Dog) animal; // 运行时抛出 ClassCastException

Java 对象在运行时记录真实类型;向下转型时会检查真实类型是否与目标类型兼容。可使用 instanceof 检查:

java 复制代码
if (animal instanceof Dog) {
    Dog dog = (Dog) animal; // 只有确认 animal 是 Dog 的实例时才进行转换
}

### 为什么用 BigDecimal,不用 double?

`double` 会出现精度丢失。`double` 执行二进制浮点运算,而二进制无法准确表示某些小数;类似十进制无法准确表示 `1/3 = 0.3333...`,二进制只能表示若干 `1 / (2^n)` 之和,`0.1` 不能精确表示为这种有限组合。

```java
System.out.println(0.05 + 0.01);
System.out.println(1.0 - 0.42);
System.out.println(4.015 * 100);
System.out.println(123.3 / 100);

// 输出:
// 0.060000000000000005
// 0.5800000000000001
// 401.49999999999994
// 1.2329999999999999

商品价格计算出现上述误差时,可能造成无法下单或对账问题。因此牵涉金钱计算时通常使用 BigDecimal

java 复制代码
import java.math.BigDecimal;

public class BigDecimalExample {
    public static void main(String[] args) {
        BigDecimal num1 = new BigDecimal("0.1");
        BigDecimal num2 = new BigDecimal("0.2");

        BigDecimal sum = num1.add(num2);
        BigDecimal product = num1.multiply(num2);

        System.out.println("Sum: " + sum);
        System.out.println("Product: " + product);
    }
}

// 输出:
// Sum: 0.3
// Product: 0.02

该代码创建两个 BigDecimal 对象,分别表示 0.10.2,使用 add() 求和、multiply() 求积。BigDecimal 可确保精确十进制计算,避免 double 的舍入误差。创建 BigDecimal 时应使用字符串参数,而不要直接使用浮点数值,以避免浮点精度问题。

六、装箱和拆箱是什么?

装箱(Boxing)和拆箱(Unboxing)是基本数据类型和对应包装类之间的转换过程:

java 复制代码
Integer i = 10; // 装箱
int n = i;      // 拆箱

自动装箱主要发生在赋值和方法调用两种情况。

赋值时

Java 1.5 以前需要手动转换;现在由编译器完成:

java 复制代码
// before autoboxing
Integer iObject = Integer.valueOf(3);
int iPrimitive = iObject.intValue();

// after Java 5
Integer iObject = 3; // autoboxing - primitive to wrapper conversion
int iPrimitive = iObject; // unboxing - object to primitive conversion
方法调用时

方法调用时可传入原始数据值或对象,编译器会进行转换:

java 复制代码
public static Integer show(Integer iParam) {
    System.out.println("autoboxing example - method invocation i: " + iParam);
    return iParam;
}

// autoboxing and unboxing in method invocation
show(3); // autoboxing
int result = show(3); // unboxing because return type of method is Integer

show 接受 Integer 参数;调用 show(3) 时,int 会转成 Integer,这就是自动装箱。show 返回 Integer,赋给 int result 时发生自动拆箱。

自动装箱的弊端

循环中自动装箱可能创建多余对象、影响性能:

java 复制代码
Integer sum = 0;
for (int i = 1000; i < 5000; i++) {
    sum += i;
}

sum += i 可看成 sum = sum + i+ 不适用于 Integer 对象,因此先拆箱、进行数值相加,再装箱。其内部变化如下:

java 复制代码
int result = sum.intValue() + i;
Integer sum = Integer.valueOf(result);

因为 sumInteger,自动装箱会由编译器替换为 Integer.valueOf(...)。命中 IntegerCache(默认 -128 ~ 127)时可复用对象;本例循环结果超出缓存范围,约会创建 4000 个 Integer 对象,降低性能并加重 GC 负担。应正确声明变量类型,避免自动装箱造成的性能问题。new Integer(int) 自 JDK 9 起被 @Deprecated,应使用 Integer.valueOf(int) 或直接自动装箱。

七、Java 为什么要有 Integer?

Integer 对应 int 的包装类,即把 int 包装成 Object 对象。对象封装可将数据与处理数据的方法结合,例如 IntegerparseInt() 等处理整数的方法。

另一重要原因是 Java 中许多方法或类处理的是对象。ArrayList 集合只能以类作为存储对象;若要将 int 存入 List,必须包装成 Integer

泛型中的应用

泛型只能使用引用类型,不能使用基本类型。因此泛型中使用整数必须使用 Integer

java 复制代码
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
Collections.sort(list);
System.out.println(list);
转换中的应用
java 复制代码
int i = 10;
Integer integer = Integer.valueOf(i);
String str = integer.toString();
System.out.println(str);
集合中的应用

Java 集合只能存储对象,不能存储基本数据类型。例:

java 复制代码
List<Integer> list = new ArrayList<>();
list.add(3);
list.add(1);
list.add(2);
int sum = list.stream().mapToInt(Integer::intValue).sum();
System.out.println(sum);

八、Integer 相比 int 有什么优点?

int 是 Java 的原始数据类型,Integerint 的包装类。

  • 基本类型和引用类型: int 是预定义的基本数据类型,不需要实例化即可使用;Integer 是引用类型,以对象形式使用。使用 int 存储整数通常不需要额外对象分配,基本类型的操作通常比对应引用类型快。
  • 自动装箱和拆箱: Integer 作为 int 包装类,可实现自动装箱和拆箱。int 值赋给 Integer 时 Java 自动装箱;Integer 值赋给 int 时 Java 自动拆箱。
  • 空指针异常: int 成员变量默认值为 0,Integer 作为引用类型默认值为 null。对值为 nullInteger 自动拆箱会抛出空指针异常,因为 null 无法拆箱为基本类型。

九、为什么还要保留 int 类型?

包装类是引用类型,对象的引用和对象本身分开存储;基本类型数据的变量直接存储数据本身。因此基本类型在读写效率和存储效率上通常比包装类高。

在 64 位 JVM、开启引用压缩的情况下,一个 Integer 对象占用 16 字节,而一个 int 类型数据占用 4 字节;前者是后者的 4 倍。

十、说一下 Integer 的缓存

Java 的 Integer 类内部实现静态缓存池,用于存储特定范围内整数对应的 Integer 对象。

默认范围是 -128127。通过 Integer.valueOf(int) 创建处于该范围内的整数对象时,不会每次创建新对象,而会复用缓存中的已有对象。

Integer 缓存是指:Java 会提前创建并复用一部分常用整数对应的 Integer 对象,避免每次装箱都新建对象。

默认缓存范围是:

复制代码
-128 ~ 127

例如:

复制代码
Integer a = 100;
Integer b = 100;

System.out.println(a == b); // true

因为 100 在缓存范围内,自动装箱时大致都会调用:

复制代码
Integer.valueOf(100)

它们拿到的是缓存中的同一个 Integer 对象。

但超出范围:

复制代码
Integer x = 200;
Integer y = 200;

System.out.println(x == y); // false

200 默认不在缓存范围内,通常会得到两个不同的对象;虽然数值相同,但引用不同。

因此,比较两个 Integer 的数值时,不要依赖 ==,应该用:

复制代码
System.out.println(x.equals(y)); // true

或者在确定不为 null 时自动拆箱比较:

复制代码
System.out.println(x.intValue() == y.intValue()); // true

缓存主要是性能优化:小整数在循环、集合、计数等场景出现非常频繁,复用对象能减少创建对象和垃圾回收的开销。

还有一个易错点:

复制代码
Integer a = new Integer(100);
Integer b = new Integer(100);

System.out.println(a == b); // false

new Integer(...) 强制创建新对象,不会使用缓存;而且这个构造器在现代 Java 中已废弃。

面试可以这样答:

Java 的 Integer 默认缓存 -128127 的对象。通过自动装箱或 Integer.valueOf() 创建该范围内的整数时,会复用缓存对象,因此 == 可能返回 true;超出范围通常创建不同对象。由于 == 比较引用,比较 Integer 数值应使用 equals(),并注意空指针问题。

相关推荐
凌波波9 小时前
从经验驱动到数据驱动,到底差在哪一步
数据
Sherotree7 天前
看懂平台数据:阅读、搜索、关注
写作·运营·数据·方法论
拾光Ծ11 天前
【MySQL】基础入门:库操库、数据表操作与数据类型详解
数据库·sql·mysql·数据类型·库操作·表操作
ZISHU_98714 天前
用 TLabel 给 SynTouch BioTac 数据做语义标注:从原始信号到结构化标注
开发语言·人工智能·python·数据·机器人触觉
梦想不只是梦与想23 天前
MySQL 数据库(二):数据类型
数据库·mysql·数据类型
Navicat中国1 个月前
使用 Navicat 轻松生成数据库测试数据
数据库·数据
V_156560272191 个月前
2026年滁州市高新技术企业申报时间、条件、奖补
科技·数据
网络研究院1 个月前
2026年上半年十大网络攻击和数据泄露事件
网络·安全·漏洞·风险·数据·泄露·事件
V_156560272192 个月前
陕南生态科创!2026年汉中市高新技术企业申报时间、条件、流程、区县奖补汇总
科技·数据