Java 数据类型详解
目录
数据类型分类
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; |
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; |
double a = 3.14; // 默认是 double
// float b = 3.14; // ❌ 编译错误
float c = 3.14f; // ✅
字符类型
| 类型 |
位数 |
说明 |
默认值 |
示例 |
char |
16位 |
单个 Unicode 字符 |
\u0000 |
char c = 'A'; |
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; |
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" │
└─────────┘ └─────────┘
引用变量 实际对象
比较示例
// 基本类型
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 |
|
int a = 10; // 基本类型
Integer b = 10; // 包装类
Integer c = Integer.valueOf(10); // 显式创建
// 自动装箱/拆箱
Integer d = a; // 自动装箱
int e = d; // 自动拆箱
String 详解
String 是什么?
String 是类,不是基本类型,是不可变的引用类型。
String 的不可变性
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 不可变?
// 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 可变对象
// 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() |
堆内存(新对象) |
❌ 新对象 |
字面量(隐式赋值)
String a = "hello";
String b = "hello";
流程:
- JVM 检查常量池中是否有
"hello"
- 有 → 直接引用(复用)
- 无 → 在常量池创建
"hello",然后引用
常量池:
┌─────────┐
│ "hello" │ ← a ─┐
└─────────┘ │
│ 两者指向同一对象
├─ b
System.out.println(a == b); // true(同一对象)
new(显式赋值)
String c = new String("hello");
String d = new String("hello");
流程:
- 先在常量池检查/创建
"hello"
- 然后在堆内存强制创建新对象
- 变量引用堆上的新对象(不是常量池)
常量池: 堆内存:
┌─────────┐ ┌─────────┐
│ "hello" │ ───→ │ "hello" │ ← c
└─────────┘ └─────────┘
┌─────────┐
│ "hello" │ ← d
└─────────┘
System.out.println(c == d); // false(不同对象)
System.out.println(c == a); // false(c 在堆,a 在常量池)
System.out.println(c.equals(a)); // true(值相同)
scanner 输入
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"
String s = new String("second");
// 等价于:
String literal = "second"; // 先处理字面量,进常量池
String s = new String(literal); // 再创建堆对象
对象生命周期
对象的一生
┌─────────────────────────────────────────────────────────┐
│ 对象一生 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 创建 → new String() / scanner.nextLine() │
│ ↓ │
│ 2. 使用 → 被变量引用,可以访问 │
│ ↓ │
│ 3. 失去引用 → 变量指向其他对象或离开作用域 │
│ ↓ │
│ 4. 可回收 → 等待 GC 清理 │
│ ↓ │
│ 5. 回收 → GC 自动回收内存 │
│ │
└─────────────────────────────────────────────────────────┘
示例:引用丢失
String s1 = scanner.nextLine(); // 创建对象
s1 = scanner.nextLine(); // s1 指向新对象,原对象可被回收
垃圾回收(GC)
GC = Garbage Collection,Java 自动内存管理机制。
GC 工作原理:
1. 定期扫描堆内存
2. 找出没有被引用的对象
3. 回收内存
不同区域的回收策略
| 类型 |
生命周期 |
回收方式 |
| 字面量(常量池) |
程序运行期间 |
一般不回收 |
| 动态对象(堆) |
到无引用时 |
GC 回收 |
| 基础类型(栈) |
作用域内 |
自动销毁 |
String a = "hello"; // 常量池,程序运行期间存在
String b = scanner.nextLine(); // 堆对象
b = null; // 失去引用,等待 GC
intern() 方法
将字符串内容放入常量池。
String s = new String("aaa");
s.intern(); // 将 "aaa" 内容放入常量池(如果还没)
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 的默认值 |
char c = '\u0000'; // null 字符
char d = 0; // 等价写法
System.out.println(c == d); // true
System.out.println((int)c); // 输出: 0
Q2: String 引用相同但值不同?
对于 String,不可能。
String a = "hello";
String b = a;
a = "world";
System.out.println(b); // hello(b 不变)
核心原因: String 是不可变的,引用相同则值必相同。
Q3: scanner.nextLine() 返回什么?
永远返回 String,无论输入什么。
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 → 引用同,值随变(可变)
注:同步发布于金蝶开发者社区:Java数据类型