Java 數據類型詳解
目錄
- 數據類型分類
- 基本數據類型
- 引用類型
- 基本類型 vs 引用類型
- 包裝類
- 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; |
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數據類型