Java 基礎 FAQ

Java 更新於 Oct 9, 2026
章節優先度優先度 5/5 — 必備 — 幾乎每一輪面試都會出現必備優先度 4/5 — 高價值 — 這裡有缺口就會掉關高價值優先度 3/5 — 值得會 — 多半是必備模式的變形值得會優先度 2/5 — 冷門 — 讀過一次即可,除非目標公司已知會問冷門只標在真正需要的章節上 —— 沒標的是背景/參考資料。

範圍 — 語言的核心機制:primitive 與參考型別、字串、初始化順序、Object 的方法、巢狀類別、反射、I/O 與日期時間 API。 另見:faq_OOP.md(OOP 四大支柱、覆寫、SOLID)· java_collection.md(集合)· java_exception.md(例外)· java_generics.md(泛型)· jvm.md(類別載入、記憶體、GC)。


1) Primitive 與參考型別 優先度 5/5 — 必備 — 幾乎每一輪面試都會出現

Primitive 位元 範圍/說明 預設值
byte 8 −128 … 127 0
short 16 ±32 K 0
int 32 ±21 億 0
long 64 ±9.2 E18 0L
float 32 IEEE 754 0.0f
double 64 IEEE 754 0.0d
char 16 一個無號的 UTF-16 code unit '\u0000'
boolean 由 JVM 決定 true / false false

參考型別是類別、介面、陣列與列舉。變數裡放的是參考;物件本身住在 heap 上。 大小由規格固定,所以在 64 位元的 JVM 上 int 一樣是 32 位元。

只有欄位有預設值。區域變數必須先指派才能用 —— 編譯器會擋。

== 與 equals

== equals()
Primitive 比較值 不適用
參考型別 比較身分(是不是同一個物件) 比較邏輯相等,由類別自己定義

Object.equals 比的是身分,所以沒覆寫它的類別,行為就跟 == 一樣。 String、包裝類別、List/Map 等等都覆寫了它。

java
// java
String a = new String("123");
String b = new String("123");
a == b;          // false — two distinct objects
a.equals(b);     // true  — same characters

String c = "123";
String d = "123";
c == d;          // true  — both refer to the same pooled literal

自動裝箱與 Integer 快取 優先度 4/5 — 高價值 — 這裡有缺口就會掉關

java
// java
Integer i = 1;      // boxing   -> Integer.valueOf(1)
int n = i;          // unboxing -> i.intValue()

Integer a = 127, b = 127;
a == b;             // true  — values in [-128, 127] come from a cache
Integer x = 128, y = 128;
x == y;             // typically false — outside the cache the JLS permits, but does
                    // not require, distinct objects. Never rely on either answer

兩個後果:裝箱的值一律用 equals 比較,以及小心拆箱藏起來的 NPE —— Integer count = map.get(k); if (count > 0) 在 key 不存在時會炸。三元運算子也可能 意外拆箱:flag ? 1 : someInteger 會把兩邊都拆箱。


2) 字串 優先度 5/5 — 必備 — 幾乎每一輪面試都會出現

String 是不可變而且 final 的。每一次「修改」都產生新物件, 這就是為什麼字串可以安全地跨執行緒共享、可以放進字串池快取,也可以安全當作 map 的 key (它的雜湊永遠不變)。

字串常數池

字面值會被放進一個位於 heap 的池子裡,所以相同的字面值就是同一個物件。 new String("x") 是刻意在池子外面再造一個物件;"x".intern() 則回傳池子裡的那個。

java
// java
String s1 = "ab";                  // pooled
String s2 = "a" + "b";             // folded at COMPILE time -> the same literal
s1 == s2;                          // true

String part = "a";
String s3 = part + "b";            // built at RUNTIME -> a new object
s1 == s3;                          // false
s1.equals(s3);                     // true

String、StringBuilder 與 StringBuffer

String StringBuilder StringBuffer
可變 否 是 是
執行緒安全 是(不可變) 否 是(synchronized)
速度 反覆修改時最慢 最快 比 builder 慢
用在 值、key、常數 在單一執行緒裡組字串 —— 一般情況 舊程式;共用的 buffer(少見)

在迴圈裡串接是經典的錯誤:每個 += 都配置一個新字串再複製,整體變成 O(n²)。 請用 StringBuilder。(單一運算式的 a + b + c 已經會被編成一次 builder 或 invokedynamic 呼叫,所以沒問題。)


3) 運算子與數值的坑 優先度 3/5 — 值得會 — 多半是必備模式的變形

java
// java
int a = 5;
int b = a++;    // b = 5, then a = 6   (post-increment: assign, then add)
int c = ++a;    // a = 7, then c = 7   (pre-increment:  add, then assign)
  • 整數除法會截斷:5 / 2 == 2;5 % -2 == 1(正負號跟著被除數)。
  • 無聲溢位:Integer.MAX_VALUE + 1 會繞回 Integer.MIN_VALUE。想讓它丟例外就用 Math.addExact,二分搜尋則寫 left + (right - left) / 2。
  • 浮點數不精確:0.1 + 0.2 != 0.3。金額請用 BigDecimal(而且要用字串 建構,不要用 double)。
  • && / || 會短路;& / | 不會(而且它們也能做位元運算)。
  • >> 保留正負號,>>> 補零。

4) 變數、static 與 final 優先度 4/5 — 高價值 — 這裡有缺口就會掉關

成員變數 區域變數
宣告位置 類別本體 方法或區塊內
存在於 Heap(跟著實例),static 的話在類別的中繼資料 執行緒的 stack frame
生命週期 和實例/類別一樣長 到區塊結束為止
預設值 有 沒有 —— 必須指派
存取修飾詞 可以加 不行(只能加 final)

static 綁在類別上而不是實例上:只有一份、大家共用、可以用 ClassName.member 存取,在類別初始化時初始化。static 方法碰不到實例狀態、 也沒有 this,而且在子類別裡是被隱藏而不是被覆寫 —— 分派是在編譯期依參考型別決定的。

static 適合常數(static final)、無狀態的輔助方法與工廠方法。可變的 static 狀態會被 JVM 裡每一條執行緒共用 —— 競態與記憶體洩漏的常見來源。

final:final 變數不能重新綁定(但它指向的物件還是可以被改)、final 方法 不能被覆寫、final 類別不能被繼承。final 欄位還帶有安全發布的保證 —— 見 jmm.md。

初始化順序

text
first use of the class →  static fields + static blocks   (once, in source order)
new Foo()              →  instance fields + instance blocks
                       →  constructor body (after its implicit/explicit super(...))

父類別的初始化一定在子類別之前完成。這就是為什麼在建構子裡呼叫可被覆寫的方法很危險: 子類別的覆寫執行時,它自己的欄位都還是預設值。


5) Object 的方法 優先度 4/5 — 高價值 — 這裡有缺口就會掉關

每個類別都繼承這些,而前三個幾乎每次面試都會問:

方法 契約
equals(Object) 自反、對稱、遞移、一致;x.equals(null) 為 false
hashCode() 相等的物件必須有相同的雜湊碼;覆寫 equals 就一定要覆寫它
toString() 要覆寫 —— 預設的 Foo@1b6d3586 在 log 裡毫無用處
getClass() 執行期的類別(final)
clone() 淺複製;需要實作 Cloneable,否則丟 CloneNotSupportedException
wait / notify / notifyAll monitor 的協調 —— 見 java_multi_thread.md
finalize() 已棄用;絕對不要依賴它。用 try-with-resources 或 Cleaner
java
// java
@Override public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Point p)) return false;     // pattern matching, Java 16+
    return x == p.x && y == p.y;
}
@Override public int hashCode() { return Objects.hash(x, y); }

違反契約會讓物件從 HashMap/HashSet 裡「憑空消失」:查找算出來的 bucket 和插入時 算的不一樣。record 會正確地把兩個都生出來 —— 見 java_modern.md。

淺複製與深複製

clone() 複製的是欄位的值,所以被參考的物件是共用的。深複製會把它們重新造一份:

java
// java
// A NEW list, but the SAME hobby objects inside it — a container (one-level) copy.
Person copy = new Person(original.name(), new ArrayList<>(original.hobbies()));

// Genuinely deep: copy every element too.
List<Hobby> hobbies = original.hobbies().stream().map(Hobby::new).toList();
Person deep = new Person(original.name(), new ArrayList<>(hobbies));

對值型別而言,不可變勝過複製:既然什麼都不會變,就沒有什麼要複製。


6) 建立物件的幾種方式 優先度 3/5 — 值得會 — 多半是必備模式的變形

方式 例子 會跑建構子嗎?
new new Foo() 會
反射 Foo.class.getDeclaredConstructor().newInstance() 會
clone() foo.clone() 不會
反序列化 ObjectInputStream.readObject() 不會 —— 但會跑第一個非序列化父類別的無參數建構子
工廠/builder List.of()、Integer.valueOf(1) 間接會

會跳過建構子的那兩個,正是為什麼單例還必須防守 readResolve() 與 clone()。


7) 巢狀類別 優先度 3/5 — 值得會 — 多半是必備模式的變形

種類 宣告 會抓著外層實例的參考嗎?
靜態巢狀 static class Node 不會 —— 預設就該用這個
內部類別 class Iter 會 —— Outer.this
區域類別 宣告在方法裡 只有在實例情境下才會 —— static 方法裡不會
匿名類別 new Comparator<>() { … } 只有在實例情境下才會

內部類別讓外層實例活著是真實的洩漏來源(一個長壽的 listener 抓著 Activity 或 service 不放)。除非真的需要外層實例,巢狀的輔助類別都加 static。 lambda 不會每個實例產生一個類別,而且它的 this 是外層的 —— 見 java_functional.md。


8) 反射與標註 優先度 3/5 — 值得會 — 多半是必備模式的變形

反射能在執行期檢視與操作類別 —— Spring、Jackson、JUnit 與所有 DI 容器背後的機制。

java
// java
Class<?> type = Class.forName("com.acme.Order");
Object instance = type.getDeclaredConstructor().newInstance();
Method m = type.getDeclaredMethod("total");
m.setAccessible(true);                 // bypasses access checks (restricted by JPMS)
Object result = m.invoke(instance);

for (Field f : type.getDeclaredFields())
    if (f.isAnnotationPresent(Column.class)) { ... }

成本與注意事項:沒有編譯期檢查(改個名字就變成執行期失敗)、比直接呼叫慢、 JIT 也比較難內聯,而且它會打破封裝。框架會快取 Method/Field handle, 或直接生成 bytecode,讓這個成本只付一次。

**標註(annotation)**是中繼資料;@Retention(RUNTIME) 才能讓反射看得到它 (SOURCE 只存在於編譯期,例如 @Override)。動態代理 (java.lang.reflect.Proxy + InvocationHandler)在執行期實作一個介面, 把每次呼叫都導到同一個 handler —— @Transactional 與 RPC stub 就是這樣運作的; 見 java_spring.md。


9) I/O 與 NIO 優先度 3/5 — 值得會 — 多半是必備模式的變形

傳統的 java.io 是以串流為基礎、而且是阻塞的;java.nio 多了 buffer、channel 與非阻塞的 selector。

位元組串流 字元串流
基底類別 InputStream / OutputStream Reader / Writer
用於 二進位資料 文字(它們會套用字元集)
帶緩衝的包裝 BufferedInputStream BufferedReader
java
// java
// Modern, and enough for most needs:
String text = Files.readString(Path.of("in.txt"));                 // 11+
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
try (Stream<String> stream = Files.lines(path)) { ... }            // lazy, closeable

// FileReader/FileWriter take a Charset since Java 11 — always pass one
try (var in  = new BufferedReader(new FileReader("in.txt", StandardCharsets.UTF_8));
     var out = new BufferedWriter(new FileWriter("out.txt", StandardCharsets.UTF_8))) {
    in.transferTo(out);
}

規則:一定要指定字元集(平台預設在不同機器上不一樣 —— UTF-8 到 Java 18 才變成 預設)、一定要加緩衝(沒緩衝的逐位元組讀取等於每個位元組一次系統呼叫), 而且一定要用 try-with-resources 關閉。

NIO 對伺服器很重要:一條執行緒可以透過 Selector 盯著上千條連線, Netty 與非阻塞的 servlet 容器就是這樣擴展的。有了虛擬執行緒(Java 21), 普通的阻塞式程式碼也能達到類似的規模 —— 見 java_modern.md。

序列化與 transient

實作 Serializable 就能讓 JVM 把一整張物件圖寫成位元組。標成 transient 的 欄位會被跳過 —— 用它來排除祕密(密碼、token)、快取,以及任何不屬於持久狀態的東西 (開著的連線、logger)。

java
// java
public class User implements Serializable {
    private static final long serialVersionUID = 1L;   // pin it — see below
    private String name;
    private transient String password;                 // never written out
}

新程式碼最好避開 Java 原生序列化:它是眾所皆知的反序列化攻擊面, 把你的類別結構和傳輸格式綁死,而且會跳過建構子。請改用有明確 schema 的 JSON/Protobuf。


10) 日期與時間(java.time)優先度 3/5 — 值得會 — 多半是必備模式的變形

Java 8 的這套 API 取代了可變又不執行緒安全的 Date/Calendar。

類別 代表
LocalDate / LocalTime / LocalDateTime 不帶時區的日期/時間 —— 生日、營業時間
Instant UTC 時間線上的一個點 —— 要存要記 log 的就是它
ZonedDateTime 在某個時區下呈現的瞬間,會處理日光節約
Duration / Period 機器時間(秒)/人類時間(月、日)
java
// java
Instant now = Instant.now();                                  // UTC, immutable
LocalDate due = LocalDate.now().plusDays(30);                 // returns a NEW value
ZonedDateTime local = now.atZone(ZoneId.of("Asia/Taipei"));
Duration.between(start, end).toMillis();

這些全都是不可變且執行緒安全的,和 SimpleDateFormat 不同 —— 後者需要 ThreadLocal,或更好的做法是直接換成 DateTimeFormatter。


11) 面試常見問答

Q:Java 是傳值還是傳參考? 永遠是傳值 —— 只是對物件來說,傳的那個值是參考。所以方法可以改動你傳進去的 那個物件,但沒辦法讓你的變數指向別的地方。

Q:String 為什麼是不可變的? 不用同步就有執行緒安全、常數池(否則共享就不安全)、當 map key 時可快取的 hashCode,還有安全性 —— 驗證過的檔案路徑或 URL 不會在檢查之後被改掉。

**Q:運算式裡的 a++ 和 ++a?**見 §3。

Q:可以覆寫 static 方法嗎? 不行。在子類別裡重新宣告是隱藏它;由參考型別決定跑哪一個。

Q:把參數或集合加上 final 有什麼用? 只保證那個繫結不會變。final List<String> xs 還是可以被 add; 要不可修改的請用 List.copyOf(xs)。

Q:int 和 Integer —— 什麼時候差別會浮現? 可為 null(Integer 可以是 null)、身分比較(對裝箱值用 ==)、 效能(裝箱會配置記憶體),以及集合(它們只能裝物件)。

Q:serialVersionUID 是什麼? 反序列化會檢查的版本印記。不寫的話,序列化執行期會依類別結構自己算一個, 於是幾乎任何改動都會讓舊位元組讀不回來(InvalidClassException)。 把它釘死,就能讓相容的改動(加欄位、加方法)仍然讀得回來 —— 但救不了像移除欄位或改型別這種不相容的改動。

**Q:throw 和 throws?**見 java_exception.md。


12) 重點檢查表

text
[ ] Primitive vs reference; defaults; == vs equals
[ ] Integer cache, autoboxing NPEs
[ ] String immutability, the constant pool, StringBuilder in loops
[ ] Integer overflow, floating point, BigDecimal for money
[ ] static/final semantics; static methods are hidden, not overridden
[ ] Static → instance → constructor initialisation order
[ ] Object's methods, and the equals/hashCode contract
[ ] Shallow vs deep copy; the five ways to create an object
[ ] static nested vs inner class, and the leak an inner class causes
[ ] Reflection: what it enables, what it costs; RUNTIME retention
[ ] Charsets, buffering and try-with-resources for I/O; transient
[ ] java.time: Instant for storage, LocalDate for calendar dates

參考資料