Java 数据类型详解
目录
- 数据类型分类
- 基本数据类型
- 引用类型
- [基本类型 vs 引用类型](#基本类型 vs 引用类型)
- 包装类
- [String 详解](#String 详解)
- 字符串常量池
- 内存布局
- 对象生命周期
- 常见问题
数据类型分类
Java 数据类型
├── 基本类型(8种)
│ ├── 整数类型:byte, short, int, long
│ ├── 浮点类型:float, double
│ ├── 字符类型:char
│ └── 布尔类型:boolean
└── 引用类型
├── 类(如 String)
├── 接口
└── 数组
基本数据类型
整数类型
| 类型 | 位数 | 范围 | 默认值 | 示例 |
|---|---|---|---|---|
byte |
8位 | -128 ~ 127 | 0 | byte b = 100; |
short |
16位 | -32,768 ~ 32,767 | 0 | short s = 10000; |
int |
32位 | -21亿 ~ 21亿 | 0 | int i = 100000; |
long |
64位 | 极大 | 0L | long l = 100000L; |
java
byte a = 127; // ✅ 最大值
// byte b = 128; // ❌ 超出范围
int c = 100;
long d = 100000L; // long 需加 L 或 l
long e = 10000000000L; // ✅ 超出int范围必须加L
// long f = 10000000000; // ❌ 编译错误,超出 int 范围
浮点类型
| 类型 | 位数 | 精度 | 默认值 | 示例 |
|---|---|---|---|---|
float |
32位 | 单精度(7位有效数字) | 0.0f | float f = 3.14f; |
double |
64位 | 双精度(15位有效数字) | 0.0 | double d = 3.14; |
java
double a = 3.14; // 默认是 double
// float b = 3.14; // ❌ 编译错误
float c = 3.14f; // ✅
字符类型
| 类型 | 位数 | 说明 | 默认值 | 示例 |
|---|---|---|---|---|
char |
16位 | 单个 Unicode 字符 | \u0000 | char c = 'A'; |
java
char a = 'A'; // 字符
char b = '中'; // 中文
char c = 65; // 数字对应字符 'A'
char d = '\u0041'; // Unicode 转义,'A'
char e = '\u0000'; // null 字符,char 的默认值
// char f = 'AB'; // ❌ char 只能存单个字符
布尔类型
| 类型 | 取值 | 默认值 | 示例 |
|---|---|---|---|
boolean |
true, false | false | boolean flag = true; |
java
boolean a = true;
boolean b = false;
引用类型
什么是引用类型
引用类型存储的是对象的地址(引用),对象本身存储在堆中。
常见引用类型
| 类型 | 说明 | 示例 |
|---|---|---|
| String | 字符串类 | String s = "hello"; |
| 数组 | 数组 | int[] arr = {1, 2, 3}; |
| 接口 | 接口类型 | List<String> list; |
| 自定义类 | 用户定义的类 | Person p = new Person(); |
基本类型 vs 引用类型
| 对比维度 | 基本类型 | 引用类型(如 String) |
|---|---|---|
| 存储位置 | 栈中,直接存值 | 栈存引用,堆存对象 |
| 大小 | 固定(如 int 4字节) | 不固定 |
| 默认值 | 0/false/\u0000 | null |
| 比较 | == 比较值 |
== 比较地址,equals() 比较内容 |
内存图示
基本类型:
栈
┌─────────┐
│ int a │ = 10
└─────────┘
直接存值
引用类型:
栈 堆
┌─────────┐ ┌─────────┐
│String s │ ────────→│ "hello" │
└─────────┘ └─────────┘
引用变量 实际对象
比较示例
java
// 基本类型
int a = 10;
int b = 10;
System.out.println(a == b); // true(比较值)
// 引用类型
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false(不同地址)
System.out.println(s1.equals(s2)); // true(内容相同)
包装类
每个基本类型都有对应的包装类:
| 基本类型 | 包装类 | 说明 |
|---|---|---|
byte |
Byte |
|
short |
Short |
|
int |
Integer |
|
long |
Long |
|
float |
Float |
|
double |
Double |
|
char |
Character |
|
boolean |
Boolean |
java
int a = 10; // 基本类型
Integer b = 10; // 包装类
Integer c = Integer.valueOf(10); // 显式创建
// 自动装箱/拆箱
Integer d = a; // 自动装箱
int e = d; // 自动拆箱
String 详解
String 是什么?
String 是类,不是基本类型,是不可变的引用类型。
String 的不可变性
java
String a = "hello";
String b = a; // a 和 b 指向同一对象
a = "world"; // a 指向新对象
System.out.println(b); // 输出: hello(b 不变)
System.out.println(a == b); // false(已不是同一对象)
内存变化:
修改前:
a ──→ ["hello"]
↑
b
修改后:
a ──→ ["world"] ← 新对象
b ──→ ["hello"] ← 原对象不变
为什么 String 不可变?
java
// String 简化版源码
public final class String {
private final char value[]; // final,不能修改
// 所有修改操作都返回新 String
public String substring(int beginIndex) {
return new String(value, beginIndex, count);
}
}
| 原因 | 说明 |
|---|---|
| 不可变 | String 对象一旦创建,内容不能改 |
| final | String 类内部用 final 修饰字符数组 |
| 安全 | 多线程环境下,无需加锁就能安全共享 |
String vs 可变对象
java
// String - 不可变
String a = "hello";
String b = a;
a = "world";
System.out.println(b); // hello(不变)
// StringBuilder - 可变
StringBuilder sb1 = new StringBuilder("hello");
StringBuilder sb2 = sb1;
sb1.append(" world");
System.out.println(sb2); // hello world(变了!)
字符串常量池
什么是字符串常量池
┌─────────────────────────────────────────────────────┐
│ 字符串常量池 │
│ (JVM 堆内存中的一块特殊区域,专门存储字符串字面量) │
│ │
│ "hello" │
│ "world" │
│ "java" │
└─────────────────────────────────────────────────────┘
字面量 vs new
| 方式 | 写法 | 存储位置 | 是否复用 |
|---|---|---|---|
| 隐式(字面量) | String s = "hello"; |
字符串常量池 | ✅ 复用 |
| 显式(new) | String s = new String("hello"); |
堆内存(新对象) | ❌ 新对象 |
| 动态创建 | scanner.nextLine() |
堆内存(新对象) | ❌ 新对象 |
字面量(隐式赋值)
java
String a = "hello";
String b = "hello";
流程:
-
JVM 检查常量池中是否有
"hello" -
有 → 直接引用(复用)
-
无 → 在常量池创建
"hello",然后引用常量池:
┌─────────┐
│ "hello" │ ← a ─┐
└─────────┘ │
│ 两者指向同一对象
├─ b
java
System.out.println(a == b); // true(同一对象)
new(显式赋值)
java
String c = new String("hello");
String d = new String("hello");
流程:
-
先在常量池检查/创建
"hello" -
然后在堆内存强制创建新对象
-
变量引用堆上的新对象(不是常量池)
常量池: 堆内存:
┌─────────┐ ┌─────────┐
│ "hello" │ ───→ │ "hello" │ ← c
└─────────┘ └─────────┘
┌─────────┐
│ "hello" │ ← d
└─────────┘
java
System.out.println(c == d); // false(不同对象)
System.out.println(c == a); // false(c 在堆,a 在常量池)
System.out.println(c.equals(a)); // true(值相同)
scanner 输入
java
String c = scanner.nextLine(); // 输入: hello
String d = scanner.nextLine(); // 输入: hello
System.out.println(c == d); // false(每次新对象)
内存布局
Java 版本差异
| 版本 | 常量池位置 |
|---|---|
| Java 6 及之前 | 方法区(永久代 PermGen) |
| Java 7+ | 堆 |
| Java 8+ | 堆(方法区改为元空间 Metaspace) |
现代 Java(7+):字符串常量池在堆上。
完整内存布局
String s1 = "first";
String s2 = new String("second");
栈:
┌─────────┐ ┌─────────┐
│ s1 │ │ s2 │
└─────────┘ └─────────┘
↓ 引用 ↓ 引用
堆(都在堆上):
┌───────────────────────────────────────────────────┐
│ │
│ 常量池区域: 普通堆区域: │
│ ┌─────────┐ ┌─────────────────────┐ │
│ │ "first" │ │ String对象 │ │
│ │"second" │ │ value → "second" │ │
│ └─────────┘ └─────────────────────┘ │
│ ↓ │
│ ┌─────────┐ │
│ │"second" │ ← 在常量池 │
│ └─────────┘ │
│ │
└───────────────────────────────────────────────────┘
new String("xxx") 的步骤
1. 先处理 "xxx" 字面量
→ 检查常量池有没有 "xxx"
→ 没有 → 在常量池创建 "xxx"
→ 有 → 复用
2. 再执行 new String()
→ 在普通堆创建新对象
→ 对象内部引用常量池的 "xxx"
java
String s = new String("second");
// 等价于:
String literal = "second"; // 先处理字面量,进常量池
String s = new String(literal); // 再创建堆对象
对象生命周期
对象的一生
┌─────────────────────────────────────────────────────────┐
│ 对象一生 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 创建 → new String() / scanner.nextLine() │
│ ↓ │
│ 2. 使用 → 被变量引用,可以访问 │
│ ↓ │
│ 3. 失去引用 → 变量指向其他对象或离开作用域 │
│ ↓ │
│ 4. 可回收 → 等待 GC 清理 │
│ ↓ │
│ 5. 回收 → GC 自动回收内存 │
│ │
└─────────────────────────────────────────────────────────┘
示例:引用丢失
java
String s1 = scanner.nextLine(); // 创建对象
s1 = scanner.nextLine(); // s1 指向新对象,原对象可被回收
垃圾回收(GC)
GC = Garbage Collection,Java 自动内存管理机制。
GC 工作原理:
1. 定期扫描堆内存
2. 找出没有被引用的对象
3. 回收内存
不同区域的回收策略
| 类型 | 生命周期 | 回收方式 |
|---|---|---|
| 字面量(常量池) | 程序运行期间 | 一般不回收 |
| 动态对象(堆) | 到无引用时 | GC 回收 |
| 基础类型(栈) | 作用域内 | 自动销毁 |
java
String a = "hello"; // 常量池,程序运行期间存在
String b = scanner.nextLine(); // 堆对象
b = null; // 失去引用,等待 GC
intern() 方法
将字符串内容放入常量池。
java
String s = new String("aaa");
s.intern(); // 将 "aaa" 内容放入常量池(如果还没)
java
String s1 = new String("aaa");
String s2 = new String("aaa");
String s3 = "aaa"; // 字面量,常量池
String s4 = s1.intern(); // 返回常量池中的引用
System.out.println(s1 == s2); // false(堆对象,不同)
System.out.println(s1 == s3); // false(堆 vs 常量池)
System.out.println(s3 == s4); // true(都是常量池)
常见问题
Q1: \u0000 是什么?
\u0000 是 Unicode 字符的转义表示,表示 Unicode 值为 0 的字符。
| 方面 | 说明 |
|---|---|
| Unicode 值 | 0 |
| 名称 | null 字符 / 空字符 |
| 可见性 | 不可见(控制字符) |
| Java 中 | char 的默认值 |
java
char c = '\u0000'; // null 字符
char d = 0; // 等价写法
System.out.println(c == d); // true
System.out.println((int)c); // 输出: 0
Q2: String 引用相同但值不同?
对于 String,不可能。
java
String a = "hello";
String b = a;
a = "world";
System.out.println(b); // hello(b 不变)
核心原因: String 是不可变的,引用相同则值必相同。
Q3: scanner.nextLine() 返回什么?
永远返回 String,无论输入什么。
java
Scanner scanner = new Scanner(System.in);
String input = scanner.nextLine(); // 输入 27 → "27"(字符串)
// 如果要转整数
int num = Integer.parseInt(input); // "27" → 27
| 方法 | 输入 27 | 返回类型 |
|---|---|---|
nextLine() |
"27" |
String |
nextInt() |
27 |
int |
nextDouble() |
27.0 |
double |
Q4: 常量池在堆上吗?
是的,现代 Java(7+)中,字符串常量池在堆上。
但常量池是堆中特殊管理的区域,与普通堆对象有区别:
| 对比维度 | 常量池对象 | 普通堆对象 |
|---|---|---|
| 位置 | 堆(特殊区域) | 堆(普通区域) |
| 创建方式 | 字面量、intern() | new String() |
| 去重 | ✅ 自动去重 | ❌ 不去重 |
| 生命周期 | 一般长期存在 | 无引用后 GC 回收 |
总结速查表
基本类型速查
| 类型 | 位数 | 范围 | 默认值 |
|---|---|---|---|
byte |
8 | -128 ~ 127 | 0 |
short |
16 | ±3.2万 | 0 |
int |
32 | ±21亿 | 0 |
long |
64 | 极大 | 0L |
float |
32 | 单精度 | 0.0f |
double |
64 | 双精度 | 0.0 |
char |
16 | 单字符 | \u0000 |
boolean |
1 | true/false | false |
String 对比速查
| 操作 | 结果 |
|---|---|
String a = "hello"; String b = "hello"; |
a == b → true |
String c = new String("hello"); String d = new String("hello"); |
c == d → false |
a == c |
false(常量池 vs 堆) |
a.equals(c) |
true(值相同) |
记忆口诀
基础类型 引用类型
↓ ↓
栈 栈 + 堆
直接值 引用+对象
字面量 scanner输入
↓ ↓
常量池 堆新对象
可复用 不复用
String → 引用同,值必同(不可变)
StringBuilder → 引用同,值随变(可变)