本工具使用的開源套件

本工具程式碼中捆綁了 1 個開源套件。

Java 速查 — 簡明參考

Java 17+ 語法、OOP、集合與最常用標準庫速查手冊,覆蓋約 80% 日常場景。

J

Java Java 17 (LTS)

JDK (OpenJDK / Oracle) · OOP · 泛型 · 函數式 · 靜態 · 強類型 · 名義類型

學習路徑

先跑通「Hello World 與構建環境」(javac/java 或 Maven)→ 熟悉變量、類型與流程控制 → 用集合與 Stream API 處理數據 → 深入面向對象(繼承/接口/泛型)→ 掌握異常處理與 lambda → 最後按需查文件、網絡、時間與構建調試。FAQ 節適合回頭避坑。

1.Hello World 與構建環境

從零編寫、編譯並運行一個 Java 程序:main 方法、javac/java、包與構建工具。

最小程序

每個 Java 程序都從 main 方法開始:public static void main(String[] args)。System.out.println 輸出一行。

1
2
3
4
5
6
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
// 文件名必須與 public 類同名:Hello.java

編譯與運行

javac 把 .java 編譯成 .class 字節碼,java 啓動 JVM 運行。javap 反編譯查看字節碼。

1
2
3
4
// $ javac Hello.java # 生成 Hello.class
// $ java Hello # 運行(不含 .class 後綴)
// $ javap -c Hello # 反編譯查看字節碼
// $ java -version # 查看 JDK 版本

命令行參數

main(String[] args) 接收命令行參數,args[0] 起為用户參數。args.length 判斷數量。

1
2
3
4
5
6
7
8
public static void main(String[] args) {
if (args.length == 0) {
System.out.println("No args");
return;
}
System.out.println("Hello, " + args[0] + "!");
}
// $ java Hello Nick

退出碼

System.exit(code) 終止 JVM 並返回退出碼,0 成功、非 0 失敗。非 main 線程仍在運行時可強制退出。

1
2
3
4
5
if (error) {
System.err.println("failed");
System.exit(1); // 非 0 表示失敗
}
// 正常結束不調用也會返回 0

包聲明

package 聲明所在包,對應目錄結構,用全限定名區分類型。缺省為匿名包。

1
2
3
4
5
package com.example.app;
// 文件放在 com/example/app/ 目錄下
public class Main {
// 全限定名:com.example.app.Main
}

import 導入

import 導入其他包的類或靜態成員,省去全限定名。java.lang 默認導入無需聲明。

1
2
3
4
5
import java.util.List;
import java.util.ArrayList;
import static java.lang.Math.PI; // 靜態導入
import java.util.*; // 通配導入(不推薦濫用)
List<String> list = new ArrayList<>();

類與文件名

public 類名必須與文件名一致。一個 .java 文件可含多個非 public 類,但只能有一個 public 類。

1
2
3
4
5
// FileName.java 中:
public class FileName { // 與文件名同名
public static void main(String[] args) { }
}
class Helper { } // 非 public,可同文件

Maven / Gradle

大型項目用構建工具:Maven(pom.xml)與 Gradle(build.gradle)管理依賴、編譯、測試與打包。

1
2
3
4
5
6
7
8
// pom.xml (Maven)
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
// $ mvn compile / mvn test / mvn package
// $ gradle build / gradle run

2.變量與常量

變量聲明、類型推斷、final 常量、作用域與類型轉換。

變量聲明

聲明 = 類型 + 名稱 + 可選初始化。Java 局部變量必須先初始化才能用,字段有默認值。

1
2
3
4
5
6
int count = 42;
double price = 19.99;
String name = "Nick";
boolean ok = true;
char c = 'A';
// 同時聲明多個:int a = 1, b = 2;

var 類型推斷

var(Java 10+)由編譯器推斷類型,僅限局部變量。類型不明確時顯式聲明更清晰。

1
2
3
4
5
var count = 42; // int
var name = "Nick"; // String
var list = new ArrayList<String>(); // ArrayList<String>
// var 不能用於字段、方法參數、返回值
// var 只是推斷,運行時類型不變

final 常量

final 局部變量只能賦值一次;final 字段必須在聲明或構造函數初始化;static final 是常量。

1
2
3
4
5
6
final int MAX = 100; // 只讀局部變量
class Config {
final String name = "app"; // final 字段
static final double PI = 3.14; // 常量
}
// 再賦值 final 變量會編譯錯誤

作用域

塊作用域:大括號內的變量只在塊內可見。嵌套塊可遮蔽外層同名變量(不建議)。

1
2
3
4
5
6
7
int x = 1;
{
int y = 2; // 僅本塊可見
System.out.println(x + y);
}
// y 在塊外不可訪問
// System.out.println(y); // 編譯錯誤

命名規範

類名 UpperCamelCase、變量/方法 lowerCamelCase、常量 UPPER_SNAKE_CASE、包名全小寫。

1
2
3
4
5
class UserProfile { }
int userId = 1;
String getUserName() { return "nick"; }
static final int MAX_RETRY = 3;
package com.example.app;

類型轉換

隱式轉換:小範圍→大範圍(int→long→double)。顯式強轉可能丟失精度(截斷)。

1
2
3
4
5
6
int i = 42;
long l = i; // 隱式:int → long
int back = (int) l; // 顯式強轉
long big = 3000000000L;
int overflow = (int) big; // 溢出:截斷為負值
String s = String.valueOf(i); // 數字 → 字符串

null 與 NPE

null 是引用類型的空值。對 null 調方法/取字段拋 NullPointerException(NPE)。Objects 工具可安全處理。

1
2
3
4
5
6
7
8
String s = null;
// s.length() // NPE!
if (s != null) {
System.out.println(s.length());
}
// Java 8+ 用 Optional 表達可空:
Optional<String> opt = Optional.ofNullable(s);
opt.ifPresent(System.out::println);

字面量

整數字面量可用 _ 分隔(可讀性)、0x 十六進制、0b 二進制。long 加 L、float 加 f。

1
2
3
4
5
6
7
int million = 1_000_000; // 下劃線分隔
int hex = 0xFF; // 255
int bin = 0b1101; // 13
long big = 42L;
float f = 3.14f;
double d = 3.14;
char ch = '\u0041'; // 'A'

3.數據類型

基本類型與包裝類、字符串、數組、枚舉、泛型與 record。

基本類型

Java 8 種基本類型:byte/short/int/long(整數)、float/double(浮點)、char、boolean。值直接存儲。

1
2
3
4
5
6
7
8
byte b = 1;
short s = 2;
int i = 42;
long l = 42L;
float f = 3.14f;
double d = 3.14;
char c = 'A';
boolean flag = true;

包裝類

Integer/Double/Boolean 等包裝類把基本類型裝進對象。自動裝箱拆箱(boxing/unboxing)在必要時發生。

1
2
3
4
5
Integer num = 42; // 自動裝箱 int → Integer
int value = num; // 自動拆箱
Integer parsed = Integer.parseInt("42");
// 裝箱比較注意:
// new Integer(42) == new Integer(42) 為 false

字符串

String 是不可變字符序列,equals 比內容、== 比引用。每次修改字符串生成新對象。

1
2
3
4
5
String s = "hello";
String t = new String("hello");
boolean eq = s.equals(t); // true(內容)
boolean sameRef = s == t; // false(引用)
String upper = s.toUpperCase(); // "HELLO"

數組

數組定長、下標訪問、length 屬性。int[] 聲明、new int[n] 創建、{} 初始化。

1
2
3
4
5
6
int[] arr = new int[5]; // 默認 0
int[] nums = { 1, 2, 3 };
int first = nums[0];
nums[2] = 99;
int len = nums.length; // 3(屬性,非方法)
int[][] grid = new int[3][3]; // 二維

枚舉

enum 定義常量集合,可帶字段與方法。switch 可直接用枚舉值。編譯期類型安全。

1
2
3
4
5
6
enum Color { RED, GREEN, BLUE }
Color c = Color.RED;
String name = c.name(); // "RED"
int ord = c.ordinal(); // 0(聲明順序)
Color parsed = Color.valueOf("BLUE");
// enum 可帶構造與字段:enum Status { OK(200), ERR(500); ... }

泛型

泛型把類型參數化:List<T>、Map<K,V>。編譯期類型檢查,運行時擦除(type erasure)。

1
2
3
4
5
6
7
List<String> names = new ArrayList<>();
Map<String, Integer> ages = new HashMap<>();
// 泛型方法:
static <T> T first(List<T> list) {
return list.get(0);
}
// 通配符:List<? extends Number>

record 類型

record(Java 14+/16 正式)一行聲明不可變數據傳輸類:自動生成構造函數、equals/hashCode/toString。

1
2
3
4
5
record Person(String name, int age) { }
var p = new Person("Nick", 30);
String n = p.name(); // 訪問器(非 getName)
String s = p.toString(); // Person[name=Nick, age=30]
// 不可變:字段隱式 final

自動裝箱陷阱

裝箱拆箱在 == / 運算中暗藏坑:-128~127 的 Integer 有緩存池,之外 == 比較可能 false。

1
2
3
4
5
Integer a = 100, b = 100;
boolean same = a == b; // true(緩存池)
Integer c = 200, d = 200;
boolean diff = c == d; // false!應使用 equals
boolean ok = c.equals(d); // true

Object 根類

所有類隱式繼承 Object:toString、equals、hashCode、clone、finalize。重寫 equals 必須重寫 hashCode。

1
2
3
4
5
6
7
8
9
10
11
class User {
private String name;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof User u)) return false;
return name.equals(u.name);
}
@Override
public int hashCode() { return name.hashCode(); }
}

4.引用與內存

引用語義、null、對象拷貝、堆棧、GC 與字符串池。

引用語義

Java 沒有指針,對象變量保存的是引用(類似指針)。賦值共享同一對象,修改彼此可見。

1
2
3
4
5
StringBuilder a = new StringBuilder("a");
StringBuilder b = a; // 引用拷貝,共享對象
b.append("b");
System.out.println(a); // "ab"(a 也變了)
// 基本類型是值拷貝:int x = y 互不影響

null 引用

null 表示引用不指向任何對象。比較 null 用 == / !=,傳參可能產生 NPE。防禦性判空。

1
2
3
4
5
6
String s = getMaybe();
if (s != null) {
System.out.println(s.length());
}
// Objects.requireNonNull(s, "s must not be null");
// Objects.toString(s, ""); 安全轉換

Object 方法

toString 描述對象、equals 內容比較、hashCode 哈希。默認實現是引用比較,內容語義需重寫。

1
2
3
4
5
6
7
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override
public String toString() { return "(" + x + "," + y + ")"; }
}
System.out.println(new Point(1, 2)); // "(1,2)"

堆與棧

對象分配在堆(GC 管理)、局部變量與引用在棧。new 的對象引用存儲在棧、對象體在堆。

1
2
3
4
void method() {
int x = 1; // 棧:基本類型
Person p = new Person(); // p 引用在棧,對象在堆
} // 方法結束,棧釋放,對象等 GC

GC 垃圾回收

JVM 自動回收無引用對象,無需手動 free。System.gc() 只是建議,不保證立即執行。

1
2
3
4
5
// 不再引用對象即可被回收
Person p = new Person();
p = null; // 原對象失去引用,等待 GC
// System.gc(); // 僅建議,生產環境不調用
// 分代回收:新生代 / 老年代

拷貝語義

數組/對象賦值是引用共享。需要獨立副本用 clone、Arrays.copyOf 或手動複製。深拷貝需逐層複製。

1
2
3
4
5
6
int[] a = { 1, 2, 3 };
int[] shallow = a; // 共享
int[] copy = a.clone(); // 獨立副本
copy[0] = 99;
System.out.println(a[0]); // 1(copy 獨立)
// 對象數組 clone 是淺拷貝,元素仍共享

字符串常量池

字面量字符串緩存在常量池:相同字面量共享。new String() 創建新對象不進池。intern() 手動入池。

1
2
3
4
5
6
String a = "hello";
String b = "hello";
boolean same = a == b; // true(池中同一對象)
String c = new String("hello");
boolean diff = a == c; // false(堆上新對象)
String d = c.intern(); // 入池後 a == d 為 true

finalize 與 Cleaner

finalize 在 GC 前調用但時機不保證(已廢棄)。釋放外部資源用 AutoCloseable + try-with-resources。

1
2
3
4
5
6
7
@Override
protected void finalize() { } // 已廢棄,不要依賴
// 正確做法:
class Conn implements AutoCloseable {
public void close() { /* 釋放資源 */ }
}
try (var c = new Conn()) { } // 自動 close

5.流程控制

if/else、switch、for/while 循環、break/continue 與三元。

if / else

if/else 按條件分支。條件必須是布爾表達式。可用 else-if 鏈多分支。

1
2
3
4
5
6
7
8
9
10
int score = 85;
String grade;
if (score >= 90) {
grade = "A";
} else if (score >= 60) {
grade = "B";
} else {
grade = "F";
}
System.out.println(grade);

switch 表達式

switch(Java 14+)可用 -> 表達式返回值和 case 合併。傳統 switch 語句也可用箭頭語法。

1
2
3
4
5
6
7
8
int day = 3;
String name = switch (day) {
case 1 -> "Monday";
case 2, 3 -> "Tue/Wed"; // 多值合併
default -> "other";
};
// 傳統 switch 也能用 -> :
// switch (day) { case 1 -> System.out.println("Mon"); }

for 循環

經典 for:初始化、條件、步進。需要下標或逆序時用。遍歷集合優先增強 for。

1
2
3
4
5
6
for (int i = 0; i < 10; i++) {
System.out.println(i); // 0..9
}
for (int i = arr.length - 1; i >= 0; i--) {
System.out.println(arr[i]); // 逆序
}

增強 for

for-each 遍歷數組和 Iterable 集合,無需下標。不可修改集合結構(會拋異常)。

1
2
3
4
5
for (String name : names) {
System.out.println(name);
}
for (int n : nums) { /* 只讀遍歷 */ }
// 需要下標/刪除時用 Iterator 或普通 for

while / do-while

while 先判斷後執行;do-while 至少執行一次。循環次數未知的場景(讀流、輪詢)用 while。

1
2
3
4
5
6
int i = 0;
while (i < 5) { i++; } // 先判斷
int x = 0;
do {
x++; // 至少執行一次
} while (x < 3);

break 與 continue

continue 跳本輪、break 退出循環、帶標籤 break/continue 控制嵌套循環。

1
2
3
4
5
6
7
8
outer:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) continue; // 跳過本輪
if (i == 2) break outer; // 跳出全部
}
}
// 標籤作用域:outer: 標記外層循環

三元運算符

cond ? a : b 一行表達 if/else。兩側類型需兼容。嵌套三元可讀性差,避免。

1
2
3
4
5
int age = 20;
String type = age >= 18 ? "adult" : "minor";
// 等價:
String t2;
if (age >= 18) t2 = "adult"; else t2 = "minor";

return 與提前返回

return 結束方法並返回值(void 直接 return;)。提前返回讓方法更易讀(guard clause)。

1
2
3
4
5
6
boolean isValid(String s) {
if (s == null || s.isEmpty()) return false;
if (s.length() > 10) return false;
return true;
}
// 提前返回避免深層嵌套

6.方法與 Lambda

方法簽名、參數、重載、遞歸、Lambda 與函數式編程。

方法定義

方法 = 訪問修飾符 + 返回類型 + 名稱 + 參數表 + 主體。void 表示無返回值。

1
2
3
4
5
6
7
public int add(int a, int b) {
return a + b;
}
public void say(String msg) {
System.out.println(msg); // void 無返回
}
static int timesTwo(int x) { return x * 2; }

參數傳遞

Java 一律按值傳遞:基本類型傳副本,引用類型傳引用的副本(改成員影響原對象,改引用不影響)。

1
2
3
4
5
6
void set(int x) { x = 99; } // 基本類型副本
void mutate(List<String> l) { l.add("x"); }
int n = 1;
set(n); // n 仍是 1
var list = new ArrayList<String>();
mutate(list); // list 被添加了 "x"

可變參數

... 可變參數接收任意數量同類型參數,本質是數組。必須放參數表最後,只能有一個。

1
2
3
4
5
6
7
8
int sum(int... nums) {
int total = 0;
for (int n : nums) total += n;
return total;
}
int s = sum(1, 2, 3); // 6
int s2 = sum(); // 0(空數組)
int s3 = sum(new int[]{1,2}); // 也可傳數組

方法重載

同名方法不同參數列表(數量/類型)為重載。按實參類型選最匹配版本。返回類型不參與區分。

1
2
3
4
5
int parse(String s) { return Integer.parseInt(s); }
int parse(int x) { return x; } // 類型不同
int parse(String s, int radix) { // 數量不同
return Integer.parseInt(s, radix);
}

遞歸

方法調用自身為遞歸。必須要有基線條件(base case)否則棧溢出。

1
2
3
4
5
int factorial(int n) {
if (n <= 1) return 1; // 基線條件
return n * factorial(n - 1);
}
// 深遞歸注意棧溢出:每層調用佔用調用棧

Lambda 表達式

Lambda = 參數 -> 表達式,是函數式接口的實例。可省略類型推斷與單參數括號。

1
2
3
4
5
6
7
8
Runnable r = () -> System.out.println("run");
BiFunction<Integer, Integer, Integer> add =
(a, b) -> a + b;
// 單參數可去括號:x -> x * 2
// 多行體用花括號 + return:
Comparator<Integer> c = (x, y) -> {
return x.compareTo(y);
};

方法引用

:: 方法引用是 Lambda 的簡寫:ClassName::staticMethod、obj::instanceMethod、ClassName::new。

1
2
3
4
5
6
7
List<String> names = List.of("b", "a");
names.stream().sorted(String::compareTo);
// Class::staticMethod
names.forEach(System.out::println);
// obj::instanceMethod
names.forEach(s -> System.out.println(s));
// Class::new 構造器引用:Supplier<Person> s = Person::new;

函數式接口

只有一個抽象方法的接口可用 Lambda。常見:Runnable、Function、Consumer、Predicate、Supplier。

1
2
3
4
5
Predicate<Integer> isEven = n -> n % 2 == 0;
Function<String, Integer> len = String::length;
Consumer<String> print = System.out::println;
Supplier<Double> rand = Math::random;
// @FunctionalInterface 註解聲明函數式接口

Stream API

Stream 鏈式處理集合:filter 過濾、map 轉換、collect 收集。惰性、不修改原集合。

1
2
3
4
5
6
7
List<Integer> nums = List.of(1, 2, 3, 4);
var evens = nums.stream()
.filter(n -> n % 2 == 0) // [2, 4]
.map(n -> n * 10) // [20, 40]
.toList(); // Java 16+
// 聚合:sum/max/count/anyMatch/allMatch
int sum = nums.stream().mapToInt(Integer::intValue).sum();

7.字符串

字符串字面量、拼接、StringBuilder、常用方法與格式化。

字符串字面量

雙引號字符串、\n 轉義、文本塊(Java 15+)用三個雙引號保留多行格式。

1
2
3
4
5
6
7
8
String s = "Line\nTab\tindent";
String path = "C:\\Program Files\\";
// 文本塊(Java 15+):三個雙引號包裹多行文本,自動縮進
// String html = """
// <div>
// <p>Hello</p>
// </div>
// """.stripIndent();

拼接

+ 拼接字符串,字符串與其他類型拼接自動轉字符串。少量拼接足夠,循環內用 StringBuilder。

1
2
3
4
5
String a = "foo" + "bar"; // "foobar"
int age = 30;
String msg = "Age: " + age; // 自動轉字符串
String joined = String.join(", ", "a", "b", "c");
// 循環拼接請用 StringBuilder(見 mem 節)

常用方法

常用方法:length 取長度、substring 截取、indexOf 查找、replace 替換、split 分割、trim 去除空白與大小寫轉換。

1
2
3
4
5
6
7
String s = " Hello, World ";
int len = s.length(); // 14
char c = s.charAt(0); // ' '
String sub = s.substring(7, 12); // "World"
boolean has = s.contains("World"); // true
String rep = s.replace("World", "Java");
String[] words = s.trim().split(",");

StringBuilder

大量拼接用 StringBuilder(非線程安全)或 StringBuffer(線程安全):append 後 toString。

1
2
3
4
5
6
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100; i++) {
sb.append(i).append(",");
}
String result = sb.toString(); // 一次性轉換
// 可變:刪除/插入可用 sb.deleteCharAt / sb.insert

格式化

String.format 格式化:%d 整數、%s 字符串、%.2f 小數、%n 換行。可帶寬度與標誌。

1
2
3
4
5
double d = 1234.567;
String s = String.format("%.2f", d); // "1234.57"
String t = String.format("%d%%", 50); // "50%"
String pad = String.format("%5d", 42); // " 42"
String n = String.format("%,d", 1234567); // "1,234,567"

正則匹配

String.matches 整體匹配、replaceAll 按正則替換、split 按正則分割。含正則模式時注意轉義。

1
2
3
boolean m = "123-456".matches("\\d{3}-\\d{3}"); // true
String masked = "123456".replaceAll("(\\d{3})(\\d+)", "$1***");
String[] parts = "a,b;c".split("[,;]"); // [a, b, c]

字符處理

charAt 取字符、Character.isDigit/isLetter/isWhitespace 判斷類別。字符串不可變,字符可遍歷。

1
2
3
4
5
6
String s = "Java 17";
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
if (Character.isUpperCase(c)) { }
}
// 也可:for (char c : s.toCharArray())

字符串轉換

Integer.parseInt 字符串轉數字,valueOf/toString 數字轉字符串。解析失敗拋 NumberFormatException。

1
2
3
4
5
6
7
int n = Integer.parseInt("42");
long l = Long.parseLong("42");
double d = Double.parseDouble("3.14");
String s = String.valueOf(42);
String hex = Integer.toHexString(255); // "ff"
// 安全解析:
// try { ... } catch (NumberFormatException e) { }

8.集合與 Stream

List/Set/Map/Queue 常用實現、遍歷、Stream 與排序。

List 列表

List 有序集合,ArrayList 數組實現(查快)、LinkedList 鏈表實現(插刪快)。List.of 不可變列表。

1
2
3
4
5
6
7
8
List<String> list = new ArrayList<>();
list.add("a");
list.add(0, "first"); // 指定位置插入
String x = list.get(1);
list.remove("a");
int size = list.size();
boolean has = list.contains("b");
var fixed = List.of("a", "b"); // 不可變

Set 集合

Set 去重集合:HashSet 無序 O(1)、LinkedHashSet 保插入序、TreeSet 排序。add 重複返回 false。

1
2
3
4
5
6
Set<Integer> set = new HashSet<>();
set.add(1);
boolean added = set.add(1); // false(已存在)
boolean has = set.contains(1);
var ordered = new LinkedHashSet<String>(); // 保序
var sorted = new TreeSet<Integer>(); // 升序

Map 映射

Map 鍵值映射:HashMap O(1)、LinkedHashMap 保插入序、TreeMap 按鍵排序。getOrDefault 安全取值。

1
2
3
4
5
6
7
8
Map<String, Integer> map = new HashMap<>();
map.put("Nick", 30);
Integer age = map.get("Nick");
int safe = map.getOrDefault("X", 0);
map.computeIfAbsent("k", k -> 1); // 缺省則計算
for (Map.Entry<String, Integer> e : map.entrySet()) {
System.out.println(e.getKey() + "=" + e.getValue());
}

Queue / Deque

Queue 先進先出(offer/poll/peek),Deque 雙端隊列(addFirst/addLast)。ArrayDeque 比 LinkedList 快。

1
2
3
4
5
6
7
Queue<Integer> q = new ArrayDeque<>();
q.offer(1); q.offer(2);
int head = q.peek(); // 1(不刪)
int out = q.poll(); // 1(取出)
Deque<Integer> d = new ArrayDeque<>();
d.addFirst(1); d.addLast(2);
int first = d.pollFirst();

遍歷與迭代

增強 for / Iterator / forEach 遍歷集合。遍歷時刪除需用 Iterator.remove 或收集後刪。

1
2
3
4
5
6
7
8
for (String s : list) { }
list.forEach(System.out::println);
// 遍歷刪除:
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}
// 不能用增強 for 直接 remove(會拋異常)

排序

List.sort 或 Collections.sort 排序,Comparator 定製順序,comparing 鏈式組合比較器。

1
2
3
4
5
6
7
List<Integer> nums = new ArrayList<>(List.of(3, 1, 2));
nums.sort(null); // 自然升序
nums.sort(Comparator.reverseOrder()); // 降序
// 定製對象排序:
people.sort(Comparator
.comparing(Person::age)
.thenComparing(Person::name));

Stream 鏈式操作

filter/map/sorted/distinct/limit 鏈式,terminal 操作觸發執行。collect 收集回集合。

1
2
3
4
5
6
7
8
var result = people.stream()
.filter(p -> p.age() >= 18)
.sorted(Comparator.comparing(Person::age).reversed())
.map(Person::name)
.distinct()
.limit(10)
.toList();
// 惰性:沒有 terminal 操作不會真正執行

分組與聚合

Collectors.groupingBy 按鍵分組、partitioningBy 布爾分區、summarizingInt 統計。

1
2
3
4
5
6
7
Map<String, List<Order>> byRegion = orders.stream()
.collect(Collectors.groupingBy(Order::region));
Map<Boolean, List<Integer>> part = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// 統計:Collectors.summingInt / averagingInt
int total = orders.stream()
.collect(Collectors.summingInt(Order::amount));

不可變集合

List.of/Set.of/Map.of 創建不可變集合。Collections.unmodifiableList 包裝只讀視圖。

1
2
3
4
5
var fixed = List.of(1, 2, 3); // 不可變
// fixed.add(4); // UnsupportedOperationException
var mutable = new ArrayList<>(fixed); // 可變副本
var view = Collections.unmodifiableList(mutable);
// 包裝後任何修改都拋異常

9.內存與性能

GC、弱引用、內存泄漏、字符串拼接性能與緩衝區。

字符串拼接性能

循環內 + 拼接會反覆創建 String(不可變)。用 StringBuilder 一次組裝,性能顯著提升。

1
2
3
4
5
6
7
// 慢:循環拼接創建大量中間對象
String s = "";
for (int i = 0; i < 1000; i++) s += i;
// 快:StringBuilder 原地追加
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) sb.append(i);
String r = sb.toString();

弱引用與軟引用

WeakReference 不阻止 GC(緩存用),SoftReference 內存不足才回收,PhantomReference 回收後清理。

1
2
3
4
5
WeakReference<BigObj> weak = new WeakReference<>(new BigObj());
System.gc();
BigObj obj = weak.get(); // 可能已為 null
// 軟引用:內存不足才回收(適合圖片緩存)
SoftReference<Image> soft = new SoftReference<>(image);

內存泄漏

常見泄漏:靜態集合持有對象、未關閉資源、監聽器未註銷、ThreadLocal 不清理。避免長生命週期持有短對象。

1
2
3
4
5
// 靜態 Map 一直增長 = 泄漏
static Map<String, Session> sessions = new HashMap<>();
// 用完須移除:sessions.remove(id)
// 或改用 WeakHashMap<String, Session>
// 資源用完關閉:try (var c = open()) { }

ThreadLocal

ThreadLocal 每線程獨立變量副本。Web 容器線程池複用線程,用完不 remove 會跨請求泄漏。

1
2
3
4
5
private static final ThreadLocal<SimpleDateFormat> fmt =
ThreadLocal.withInitial(SimpleDateFormat::new);
String d = fmt.get().format(date);
// 關鍵:用完必須 remove(),尤其線程池環境
// fmt.remove(); 否則線程複用導致對象泄漏

ByteBuffer

ByteBuffer 直接內存(allocateDirect)減少 GC 壓力,適合 NIO 與大數據傳輸。需手動管理。

1
2
3
4
5
6
7
ByteBuffer buf = ByteBuffer.allocate(1024); // 堆
ByteBuffer direct = ByteBuffer.allocateDirect(1024); // 直接內存
buf.putInt(42).putDouble(3.14);
buf.flip(); // 切換到讀模式
int i = buf.getInt();
double d = buf.getDouble();
buf.clear(); // 複用緩衝

OutOfMemoryError

OOM 表示內存耗盡。Heap Space 堆滿、Metaspace 類過多、Direct buffer 過多。調整 JVM 參數或找泄漏。

1
2
3
4
5
// JVM 參數:
// $ java -Xms512m -Xmx2g -XX:+HeapDumpOnOutOfMemoryError App
// -Xmx 最大堆、-Xms 初始堆
// 堆溢出轉儲:生成 .hprof 用工具分析
// 排查:找集合無界增長 / 靜態持有 / 資源泄漏

數組拷貝

System.arraycopy 高效拷貝數組區間。Arrays.copyOf 複製並可能擴容。循環手動拷貝慢。

1
2
3
4
5
int[] src = { 1, 2, 3, 4 };
int[] dst = new int[4];
System.arraycopy(src, 1, dst, 0, 2); // [2, 3, 0, 0]
int[] copy = Arrays.copyOf(src, src.length);
int[] grown = Arrays.copyOf(src, 6); // 擴容補 0

對象池複用

頻繁創建大對象會加大 GC 壓力。用對象池緩存可複用實例,但需注意線程安全與歸還路徑。

1
2
3
4
5
6
7
8
9
10
11
// 簡易對象池:避免頻繁 new 大對象
final class ConnectionPool {
private final ArrayDeque<Connection> idle = new ArrayDeque<>();
synchronized Connection acquire() {
return idle.isEmpty() ? new Connection() : idle.poll();
}
synchronized void release(Connection c) {
idle.push(c); // 歸還複用
}
}
// 池越大內存佔用越高,需與複用收益權衡

10.面向對象

類、封裝、繼承、多態、抽象類、接口與訪問修飾符。

類與對象

class 定義數據類型:字段存狀態、方法定義行為、構造函數初始化、new 創建實例。

1
2
3
4
5
6
7
8
9
10
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String greet() { return "Hi, " + name; }
}
var p = new Person("Nick", 30);

封裝

字段私有(private)、方法公開(public)控制訪問。getter/setter 加校驗邏輯,保護不變式。

1
2
3
4
5
6
7
8
public class Account {
private double balance;
public double getBalance() { return balance; }
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException();
balance += amount;
}
}

繼承

extends 繼承基類。Java 單繼承。super 調用父類構造/方法,子類是父類的一種(is-a)。

1
2
3
4
5
6
7
8
9
public class Animal {
public void speak() { System.out.println("..."); }
}
public class Dog extends Animal {
@Override
public void speak() { System.out.println("Woof"); }
}
Animal a = new Dog();
a.speak(); // "Woof"(多態)

多態

父類/接口引用指向子類實例,調用虛方法按實際類型分派。@Override 標註重寫。

1
2
3
4
5
6
7
8
9
10
11
public class Shape {
public double area() { return 0; }
}
public class Circle extends Shape {
private final double r;
public Circle(double r) { this.r = r; }
@Override
public double area() { return Math.PI * r * r; }
}
Shape s = new Circle(2);
double a = s.area(); // 12.57(按實際類型)

抽象類

abstract 類不能實例化,可含 abstract 方法(子類必須實現)。用於模板基類共享狀態。

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class Shape {
public abstract double area(); // 無實現
public void describe() {
System.out.println("Area: " + area());
}
}
public class Square extends Shape {
private final double side;
public Square(double s) { this.side = s; }
@Override
public double area() { return side * side; }
}

接口

interface 定義契約:方法默認 public abstract。Java 8+ 有 default/static 方法。可多實現。

1
2
3
4
5
6
7
8
9
10
11
public interface Logger {
void log(String msg); // 抽象方法
default void warn(String m) { // default 實現
log("[WARN] " + m);
}
}
public class ConsoleLogger implements Logger {
@Override
public void log(String msg) { System.out.println(msg); }
}
Logger l = new ConsoleLogger(); // 面向接口

訪問修飾符

public 全公開、protected 包內+子類、缺省 包內、private 本類。類成員默認包內可見。

1
2
3
4
5
6
7
public class Demo {
public int pub; // 任意
protected int prot; // 包內 + 子類
int def; // 包內
private int priv; // 僅本類
}
// 頂級類只能是 public 或包內可見(缺省)

static 成員

static 字段/方法屬於類而非實例。static 方法不能訪問實例成員。靜態代碼塊初始化。

1
2
3
4
5
6
7
8
public class Counter {
private static int count; // 類級
public static void inc() { count++; }
public static int get() { return count; }
}
Counter.inc(); // 通過類訪問
Counter.inc();
System.out.println(Counter.get()); // 2

內部類與匿名類

內部類、匿名類、lambda 簡化回調。靜態內部類不持有外部實例,避免內存泄漏。

1
2
3
4
5
6
7
8
// 匿名類(Java 8 前寫法):
Runnable r = new Runnable() {
@Override public void run() { }
};
// 更推薦 Lambda:
Runnable r2 = () -> System.out.println("hi");
// 靜態內部類:static class Builder { }
// 內部類自動持有外部實例引用(注意泄漏)

11.異常處理

try/catch/finally、異常層級、checked/unchecked、try-with-resources 與自定義異常。

try / catch / finally

try 放可能出錯的代碼,catch 捕獲處理,finally 無論成敗都執行(清理資源)。異常向上傳播。

1
2
3
4
5
6
7
try {
int n = Integer.parseInt("abc");
} catch (NumberFormatException e) {
System.out.println("bad number: " + e.getMessage());
} finally {
System.out.println("cleanup"); // 總會執行
}

多 catch

多個 catch 按類型匹配,具體異常在前、泛異常在後。multi-catch 用 | 合併無關類型。

1
2
3
4
5
6
7
8
9
try {
process();
} catch (FileNotFoundException e) {
// 具體異常
} catch (IOException e) {
// 泛異常
} catch (IllegalArgumentException | IllegalStateException e) {
// multi-catch:| 合併(無繼承關係)
}

拋出與重拋

throw new 拋異常。catch 裏 throw;(Java 7+)原樣重拋保留堆棧。方法聲明 throws 標記可能拋出的檢查異常。

1
2
3
4
5
6
7
throw new IllegalArgumentException("invalid value");
public void read() throws IOException {
throw new IOException("io error");
}
try { read(); } catch (IOException e) {
throw new RuntimeException(e); // 包裝重拋
}

自定義異常

自定義異常繼承 Exception(檢查)或 RuntimeException(非檢查)。慣例類名以 Exception 結尾。

1
2
3
4
5
6
7
8
9
public class ConfigException extends RuntimeException {
public ConfigException(String message) {
super(message);
}
public ConfigException(String message, Throwable cause) {
super(message, cause);
}
}
throw new ConfigException("bad config");

檢查與非檢查異常

檢查異常(IOException 等)必須處理或聲明;非檢查異常(RuntimeException 子類)編譯期不強制。

1
2
3
4
5
6
7
// 檢查異常:必須 catch 或 throws
public void read() throws IOException {
Files.readString(Path.of("a.txt"));
}
// 非檢查異常:可不處理
int x = Integer.parseInt("abc"); // 編譯通過,運行拋異常
// 自定義異常常用 RuntimeException 子類

try-with-resources

try (資源) { } 自動調用 AutoCloseable 的 close(),異常時也會關。替代手動 finally 關閉。

1
2
3
4
5
try (var reader = new BufferedReader(
Files.newBufferedReader(Path.of("a.txt")))) {
String line = reader.readLine();
} // 自動關閉 reader
// 多個資源:try (var a = ...; var b = ...) { }

異常的代價

異常捕獲開銷高(填堆棧),不做流程控制。可預判錯誤用返回值/判空/optional。

1
2
3
4
5
6
// 慢:用異常做流程控制
boolean ok1;
try { Integer.parseInt(s); ok1 = true; }
catch (NumberFormatException e) { ok1 = false; }
// 快:先判空/正則再解析
boolean ok2 = s != null && s.matches("\\d+");

日誌與異常

異常要記錄(含堆棧),別 System.out 打印後繼續。日誌框架(SLF4J+Logback)分級輸出。

1
2
3
4
5
6
7
// SLF4J + Logback:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
private static final Logger log =
LoggerFactory.getLogger(App.class);
// 記錄異常堆棧:
// log.error("failed", e); // e 保留堆棧

12.文件與 IO

Files/Path 現代 IO、Scanner、Reader/Writer 與流式讀取。

Files 讀寫

java.nio.file.Files 現代 API:readString/writeString、readAllLines、copy/move/delete。

1
2
3
4
5
String content = Files.readString(Path.of("in.txt"));
Files.writeString(Path.of("out.txt"), content);
List<String> lines = Files.readAllLines(Path.of("in.txt"));
Files.copy(Path.of("a"), Path.of("b"), StandardCopyOption.REPLACE_EXISTING);
Files.deleteIfExists(Path.of("tmp"));

Scanner 輸入

Scanner 讀控制台或文件:nextLine 讀行、nextInt/nextDouble 讀類型。hasNext 判斷還有輸入。

1
2
3
4
5
6
7
Scanner sc = new Scanner(System.in);
System.out.print("Name: ");
String name = sc.nextLine();
int age = sc.nextInt();
// 從文件讀:
var file = new Scanner(Path.of("a.txt"));
while (file.hasNextLine()) System.out.println(file.nextLine());

Reader / Writer

字符流讀寫文本:BufferedReader 逐行、BufferedWriter 寫、try-with-resources 自動關閉。

1
2
3
4
5
6
7
8
9
10
try (var reader = new BufferedReader(
new FileReader("a.txt"));
var writer = new BufferedWriter(
new FileWriter("b.txt"))) {
String line;
while ((line = reader.readLine()) != null) {
writer.write(line);
writer.newLine();
}
} // 自動關閉兩個流

字節流

InputStream/OutputStream 讀寫字節:FileInputStream、BufferedInputStream 緩衝。數據搬運用 transferTo。

1
2
3
4
5
6
7
8
9
try (var in = new FileInputStream("a.bin");
var out = new FileOutputStream("b.bin")) {
byte[] buf = new byte[8192];
int read;
while ((read = in.read(buf)) != -1) {
out.write(buf, 0, read);
}
}
// 簡單版:Files.copy(Path.of("a"), Path.of("b"))

控制台 IO

System.out 輸出、System.in 輸入、System.err 錯誤輸出。printf 格式化輸出。

1
2
3
4
5
System.out.print("No newline");
System.out.println("With newline");
System.out.printf("%s is %d years old%n", "Nick", 30);
System.err.println("Error message"); // 標準錯誤
// 格式化:%n 換行(跨平台),不用 \n

Path 路徑

Path 表示路徑,resolve 拼接、getFileName、toAbsolutePath、exists、Files.walk 遞歸遍歷。

1
2
3
4
5
6
7
8
9
Path dir = Path.of("data");
Path file = dir.resolve("a.txt"); // data/a.txt
String name = file.getFileName().toString();
boolean exists = Files.exists(file);
// 遞歸列出:
try (var stream = Files.walk(dir)) {
stream.filter(p -> p.toString().endsWith(".txt"))
.forEach(System.out::println);
}

序列化

ObjectOutputStream/ObjectInputStream 序列化對象(需 implements Serializable)。JSON 更常用。

1
2
3
4
5
6
7
8
9
10
class User implements Serializable {
private static final long serialVersionUID = 1L;
String name;
}
// 寫入:
try (var out = new ObjectOutputStream(
new FileOutputStream("u.bin"))) {
out.writeObject(new User());
}
// 讀取:ObjectInputStream in = ...; User u = (User) in.readObject();

臨時文件

Files.createTempFile 在系統臨時目錄創建文件,用完須 Files.deleteIfExists 清理,避免殘留堆積。

1
2
3
4
5
6
Path tmp = Files.createTempFile("app-", ".log");
System.out.println(tmp); // 系統臨時目錄
Files.writeString(tmp, "temp data");
// 用完即刪,避免殘留:
Files.deleteIfExists(tmp);
// 臨時目錄:Files.createTempDirectory("app");

13.常見陷阱(FAQ)

Java 開發者最常踩的坑:== vs equals、裝箱緩存、併發、異常與集合修改。

== vs equals

字符串/對象比較內容必須用 equals,== 比較引用。字面量緩存池讓 == 偶然成立,勿依賴。

1
2
3
4
5
6
7
// BAD:== 比較引用,字面量池掩蓋問題
String a = "nick";
String b = new String("nick");
boolean bad = a == b; // false
// GOOD:equals 比較內容
boolean good = a.equals(b); // true

Integer 緩存池

自動裝箱的 -128~127 有緩存,== 比較可能為 true;範圍外為 false。對象比較一律 equals。

1
2
3
4
5
6
7
// BAD:用 == 比較裝箱值
Integer a = 200, b = 200;
boolean bad = a == b; // false
// GOOD:equals 比較
Integer c = 200, d = 200;
boolean good = c.equals(d); // true

判空與 NPE

對 null 調方法必 NPE。可空值用 Optional 或提前判空,別到處 try-catch NPE。

1
2
3
4
5
6
7
8
// BAD:不做判空
String s = find();
System.out.println(s.length()); // 可能 NPE
// GOOD:判空或 Optional
String t = find();
System.out.println(t == null ? 0 : t.length());
// 或 Optional.ofNullable(find()).map(String::length).orElse(0)

異常吞掉

catch 後不處理就是吞異常,排查困難。至少打日誌或原樣重拋(包裝到合適異常)。

1
2
3
4
5
6
7
8
// BAD:靜默吞掉
catch (IOException e) { }
// GOOD:記錄或拋給上層
catch (IOException e) {
log.error("read failed", e);
throw new RuntimeException("read failed", e);
}

equals 與 hashCode

重寫 equals 必須重寫 hashCode,否則放 HashSet/HashMap 行為錯誤(同一邏輯對象算不同 key)。

1
2
3
4
5
6
7
8
// BAD:只重寫 equals
class A { public boolean equals(Object o) { ... } } // hashCode 不一致
// GOOD:兩者同時重寫
class B {
public boolean equals(Object o) { /* 相同邏輯 */ }
public int hashCode() { return Objects.hash(fields); }
}

遍歷時刪除

增強 for 裏 List.remove 拋 ConcurrentModificationException。用 Iterator.remove 或收集後刪。

1
2
3
4
5
6
7
8
9
10
// BAD:增強 for 中刪除
for (String s : list) {
if (s.isEmpty()) list.remove(s); // 拋異常
}
// GOOD:Iterator 刪除
Iterator<String> it = list.iterator();
while (it.hasNext()) {
if (it.next().isEmpty()) it.remove();
}

日期可變性

舊 Date/Calendar 可變易出錯。統一用 java.time(LocalDate/LocalDateTime)不可變安全。

1
2
3
4
5
6
7
// BAD:可變 Date
Date d = new Date();
d.setTime(0); // 狀態被外部修改
// GOOD:java.time 不可變
LocalDate today = LocalDate.now();
LocalDate next = today.plusDays(1); // 返回新對象

線程安全

HashMap/ArrayList 非線程安全,多線程寫入出問題。用 ConcurrentHashMap 或同步包裝。

1
2
3
4
5
6
7
8
// BAD:多線程寫 HashMap
Map<String, Integer> map = new HashMap<>();
// 併發 put 可能損壞內部結構
// GOOD:併發集合
Map<String, Integer> safe = new ConcurrentHashMap<>();
safe.put("k", 1);
// 或 Collections.synchronizedMap(new HashMap<>())

Stream 空值

Stream 裏元素為 null 時 filter/map 可能 NPE。先用 filter(Objects::nonNull) 過濾。

1
2
3
4
5
6
7
8
// BAD:null 元素進 map
list.stream().map(String::toUpperCase) // 有 null 則 NPE
// GOOD:先過濾 null
list.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.toList();

循環拼接

循環內 += 拼接產生大量中間 String,性能差。用 StringBuilder。

1
2
3
4
5
6
7
8
// BAD:循環拼接
String s = "";
for (int i = 0; i < 10000; i++) s += i;
// GOOD:StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) sb.append(i);
String r = sb.toString();

14.併發與線程

Thread、synchronized、線程池 ExecutorService 與 CompletableFuture。

Thread 線程

Thread 或 Runnable 創建線程,start 啓動、join 等待、sleep 暫停。大多場景線程池更合適。

1
2
3
4
5
6
7
8
9
10
// 方式一:繼承 Thread
Thread t = new Thread(() -> {
System.out.println("worker");
});
// 方式二:Runnable
Runnable task = () -> System.out.println("run");
Thread t2 = new Thread(task);
t2.start(); // 啓動
// t2.join(); // 等待完成
// Thread.sleep(100); // 暫停毫秒

synchronized

synchronized 加鎖保證互斥。鎖實例方法鎖 this、靜態方法鎖 Class、代碼塊可鎖任意對象。

1
2
3
4
5
6
7
8
private int count;
public synchronized void increment() { count++; }
// 等價:synchronized(this) { count++; }
// 靜態同步:
public static synchronized void inc2() { }
// 鎖對象通常用專用 final 字段:
private final Object lock = new Object();
synchronized (lock) { /* 臨界區 */ }

volatile

volatile 保證可見性(線程間立即可見),但不保證原子性。狀態標誌用 volatile,計數用 AtomicInteger。

1
2
3
4
5
6
7
private volatile boolean running = true;
// 其他線程讀取立即可見
// 但 volatile 不解決原子操作:
// volatile int n; n++; // 非原子!
// 用 AtomicInteger:
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();

線程池

ExecutorService 管理線程複用。newFixedThreadPool/newCachedThreadPool,用完 shutdown。

1
2
3
4
5
6
7
ExecutorService pool = Executors.newFixedThreadPool(4);
for (int i = 0; i < 10; i++) {
pool.submit(() -> System.out.println(Thread.currentThread().getName()));
}
pool.shutdown(); // 不再接收新任務
// pool.awaitTermination(5, TimeUnit.SECONDS);
// 推薦:Executors.newVirtualThreadPerTaskExecutor()(Java 21)

Future 與 Callable

Callable 有返回值的任務,Future.get 阻塞取結果。FutureTask 可手動控制。

1
2
3
4
5
6
7
8
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<Integer> f = pool.submit(() -> {
return compute(); // Callable 有返回值
});
int result = f.get(); // 阻塞等待結果
// f.get(5, TimeUnit.SECONDS); // 超時
// f.cancel(true); // 取消
pool.shutdown();

CompletableFuture

異步編排:thenApply 轉換、thenCombine 合併、allOf 等待多個。回調鏈式不阻塞主線程。

1
2
3
4
5
6
7
8
CompletableFuture.supplyAsync(() -> fetch())
.thenApply(data -> parse(data))
.thenAccept(result -> System.out.println(result))
.exceptionally(ex -> { log(ex); return null; });
// 等待多個:
var all = CompletableFuture.allOf(f1, f2);
all.join();
// join() 阻塞等待,結果經 get() 取出

併發集合

ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue 線程安全。替代手動加鎖的普通集合。

1
2
3
4
5
6
7
Map<String, Integer> map = new ConcurrentHashMap<>();
map.put("k", 1);
map.compute("k", (k, v) -> v == null ? 1 : v + 1);
Queue<Task> q = new ArrayBlockingQueue<>(100);
q.offer(task); // 滿則返回 false
Task t = q.poll(); // 空則返回 null
// CopyOnWriteArrayList 讀多寫少場景

Lock 接口

ReentrantLock 比 synchronized 靈活:可超時、可中斷、可公平。Lock 必須手動 unlock(finally)。

1
2
3
4
5
6
7
8
9
10
11
private final ReentrantLock lock = new ReentrantLock();
void work() {
lock.lock();
try {
/* 臨界區 */
} finally {
lock.unlock(); // 必須 finally 釋放
}
}
// lock.tryLock(1, TimeUnit.SECONDS) 帶超時
// ReadWriteLock 讀寫分離提升併發

15.網絡與 HTTP

HttpClient、URL 請求、JSON 序列化與 WebSocket。

HttpClient 基礎

java.net.http.HttpClient(Java 11+)發 HTTP 請求。send 同步、sendAsync 異步。構建器配置。

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://example.com"))
.build();
HttpResponse<String> resp =
client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.statusCode());
System.out.println(resp.body());

POST 與 JSON

POST 請求帶 JSON body。Jackson 或 Gson 序列化反序列化 JSON。

1
2
3
4
5
6
7
8
9
String json = "{\"name\":\"Nick\"}";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json))
.build();
var resp = client.send(req, HttpResponse.BodyHandlers.ofString());
// 解析:Jackson ObjectMapper om = new ObjectMapper();
// User u = om.readValue(resp.body(), User.class);

異步請求

sendAsync 返回 CompletableFuture,不阻塞調用線程。多請求併發 thenApply 處理。

1
2
3
4
5
6
7
HttpClient client = HttpClient.newHttpClient();
var req = HttpRequest.newBuilder(URI.create("https://example.com")).build();
client.sendAsync(req, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println)
.join(); // 阻塞等待(演示)
// 真正異步環境不調 join

URL 與 URLConnection

舊 API URL/HttpURLConnection 簡單請求。多數場景用 HttpClient 更簡潔。查詢參數需 URLEncoder 編碼。

1
2
3
4
5
6
7
URL url = new URL("https://example.com");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
int code = conn.getResponseCode();
String body = new String(conn.getInputStream().readAllBytes());
// 參數編碼:
String q = URLEncoder.encode("中文", StandardCharsets.UTF_8);

JSON 解析

Jackson/Gson 主流 JSON 庫。@JsonProperty 映射字段名,Java 17 新特性可結合 record。

1
2
3
4
5
6
7
8
9
// Gson:
Gson gson = new Gson();
User u = gson.fromJson(json, User.class);
String out = gson.toJson(u);
// Jackson:
ObjectMapper om = new ObjectMapper();
User u2 = om.readValue(json, User.class);
String s2 = om.writeValueAsString(u2);
// Jackson 依賴 pom 添加

調用 Web API

組合:構建請求 → 檢查狀態碼 → 反序列化。處理 404/500 等非成功狀態。

1
2
3
4
5
6
7
8
9
10
var req = HttpRequest.newBuilder()
.uri(URI.create("https://api.github.com/users/" + login))
.header("User-Agent", "MyApp")
.GET().build();
var resp = client.send(req, BodyHandlers.ofString());
if (resp.statusCode() == 200) {
var user = om.readValue(resp.body(), User.class);
} else {
System.err.println("status: " + resp.statusCode());
}

WebSocket

java.net.http.WebSocket 雙向長連接。Listener 回調接收消息,onOpen/onText 處理。

1
2
3
4
5
6
7
8
9
10
11
var ws = HttpClient.newHttpClient()
.newWebSocketBuilder()
.buildAsync(URI.create("wss://example.com/ws"),
new WebSocket.Listener() {
public void onOpen(WebSocket webSocket) {
webSocket.sendText("hello", true);
}
// onText/onError/onClose 回調
})
.join();
ws.sendText("msg", true);

超時與重試

HttpRequest.timeout 設置請求超時;HttpClient.connectTimeout 連接超時。超時拋 HttpTimeoutException。

1
2
3
4
5
6
7
8
HttpClient client = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
var req = HttpRequest.newBuilder(uri)
.timeout(Duration.ofSeconds(10))
.build();
// 超時拋 HttpTimeoutException,需捕獲處理
// 簡單重試:循環 catch 後重試(加退避)

16.日期與時間

java.time 本地日期時間、Instant、Duration/Period 與格式化。

LocalDate

LocalDate 日期(無時間):now、of、plusDays/minusMonths。不可變線程安全。

1
2
3
4
5
6
7
LocalDate today = LocalDate.now();
LocalDate date = LocalDate.of(2024, 1, 1);
LocalDate next = date.plusDays(7);
LocalDate prev = date.minusMonths(1);
int year = date.getYear();
boolean leap = date.isLeapYear();
DayOfWeek dow = date.getDayOfWeek();

LocalTime

LocalTime 時間(無日期):now、of、plusMinutes。與 LocalDate 組合 LocalDateTime。

1
2
3
4
5
6
LocalTime time = LocalTime.now();
LocalTime t = LocalTime.of(14, 30, 0);
LocalTime later = t.plusHours(1);
int hour = t.getHour();
// 與日期組合:
LocalDateTime dt = LocalDateTime.of(date, t);

Instant 時間戳

Instant 是 UTC 時間點(epoch 秒/納秒),跨時區存儲用。系統計時用 System.currentTimeMillis。

1
2
3
4
5
6
7
Instant now = Instant.now();
long epochMilli = now.toEpochMilli();
Instant fromEpoch = Instant.ofEpochMilli(1_700_000_000_000L);
// 從系統時間:
long nowMs = System.currentTimeMillis();
// Instant 與 LocalDateTime 互轉需時區:
LocalDateTime ldt = LocalDateTime.ofInstant(now, ZoneId.systemDefault());

Duration 與 Period

Duration 精確到納秒的時間間隔(秒級)、Period 以年月日為單位的日期間隔。

1
2
3
4
5
6
Duration d = Duration.ofMinutes(90);
long seconds = d.toSeconds(); // 5400
Duration between = Duration.between(t1, t2);
Period p = Period.of(1, 2, 3); // 1年2月3天
Period since = Period.between(birthday, today);
int years = since.getYears();

格式化與解析

DateTimeFormatter 格式化和解析。預置 ISO 格式,自定義 pattern(yyyy-MM-dd HH:mm:ss)。

1
2
3
4
5
6
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
String s = LocalDateTime.now().format(fmt);
LocalDateTime parsed = LocalDateTime.parse("2024-01-01 12:30", fmt);
// 預置格式:
String iso = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
// 解析注意異常:DateTimeParseException

時區

ZonedDateTime 帶時區、ZoneOffset 固定偏移。ZoneId.systemDefault 本機時區。存儲用 UTC。

1
2
3
4
5
6
7
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
ZonedDateTime zdt = ZonedDateTime.now(shanghai);
Instant utc = zdt.toInstant();
ZonedDateTime back = utc.atZone(ZoneId.of("UTC"));
// 固定偏移:
ZoneOffset offset = ZoneOffset.ofHours(8);
OffsetDateTime odt = OffsetDateTime.of(ldt, offset);

舊 API 轉換

java.util.Date/Calendar 與新 API 轉換。Date 可變、易錯,新代碼用 java.time。

1
2
3
4
5
6
7
8
// Date → Instant:
Instant inst = new Date().toInstant();
// Instant → Date:
Date d = Date.from(Instant.now());
// 轉換:
LocalDateTime ldt = LocalDateTime.ofInstant(
inst, ZoneId.systemDefault());
// Calendar 早已過時,避免使用

時間戳工具

System.currentTimeMillis 毫秒、nanoTime 納秒差(僅測間隔,不能對時鐘)。時間戳與格式化互轉。

1
2
3
4
5
6
7
long start = System.nanoTime();
compute();
long elapsedNs = System.nanoTime() - start; // 間隔
// 毫秒時間戳 → LocalDateTime:
LocalDateTime t = LocalDateTime.ofInstant(
Instant.ofEpochMilli(System.currentTimeMillis()),
ZoneId.systemDefault());

17.進程與系統

ProcessBuilder 啓動進程、系統屬性、環境變量與運行時信息。

ProcessBuilder

ProcessBuilder 啓動外部程序。參數用列表避免注入。redirectErrorStream 合併輸出。

1
2
3
4
5
6
ProcessBuilder pb = new ProcessBuilder("git", "log", "-1");
pb.redirectErrorStream(true); // 合併錯誤輸出
Process proc = pb.start();
String out = new String(proc.getInputStream().readAllBytes());
int code = proc.waitFor(); // 等待退出
System.out.println(out);

系統屬性

System.getProperty 讀 JVM 系統屬性:user.home、java.version、os.name。setProperty 設置。

1
2
3
4
5
6
String home = System.getProperty("user.home");
String ver = System.getProperty("java.version");
String os = System.getProperty("os.name");
System.setProperty("my.prop", "value");
// 全部:System.getProperties().forEach(...)
// JVM 參數:-Dmy.prop=value

環境變量

System.getenv 讀環境變量(不可修改),getenv() 全量。區分系統屬性與環境變量。

1
2
3
4
5
6
String path = System.getenv("PATH");
String home = System.getenv("HOME");
Map<String, String> all = System.getenv();
// 環境變量只讀;
// 系統屬性用 -D 或 System.setProperty
// 讀取配置建議兩者都查:getenv 優先或屬性優先

Runtime 信息

Runtime 提供 JVM 信息:availableProcessors、maxMemory、totalMemory。gc 建議回收。

1
2
3
4
5
6
7
Runtime rt = Runtime.getRuntime();
int cores = rt.availableProcessors();
long maxMem = rt.maxMemory(); // 最大堆
long used = rt.totalMemory() - rt.freeMemory();
// rt.gc() 建議回收(生產別用)
System.out.printf("cores=%d used=%dMB%n",
cores, used / 1024 / 1024);

命令行參數解析

main args 解析選項。簡單場景手工循環,複雜用 picocli/JCommander 庫。

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
String file = null;
boolean verbose = false;
for (String a : args) {
if (a.equals("-v")) verbose = true;
else if (a.equals("-f")) { /* 取下一個 */ }
else file = a;
}
}
// 複雜 CLI 用 picocli:@Command/@Option 註解

關閉鈎子

Runtime.addShutdownHook 註冊 JVM 退出時的清理。System.exit 觸發。避免在鈎子做耗時操作。

1
2
3
4
5
6
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("shutting down...");
saveState();
}));
// JVM 正常退出/System.exit 時執行
// 鈎子線程併發,別做重活

工作目錄

user.dir 系統屬性是進程當前工作目錄。ProcessBuilder.directory 可指定子進程的起始目錄。

1
2
3
4
5
6
7
String cwd = System.getProperty("user.dir");
System.out.println(cwd); // 當前工作目錄
// 子進程起始目錄(改到其他目錄執行):
Path target = Path.of(System.getProperty("user.home"), "work");
ProcessBuilder pb = new ProcessBuilder("git", "status");
pb.directory(target.toFile());
Process p = pb.start();

系統平台信息

系統屬性提供平台信息:os.name 操作系統、os.arch 架構、file.separator 路徑分隔符、line.separator 換行。

1
2
3
4
5
6
String os = System.getProperty("os.name"); // Windows 11 / Linux
String arch = System.getProperty("os.arch"); // amd64
String sep = System.getProperty("file.separator"); // \ 或 /
String eol = System.getProperty("line.separator"); // 換行符
String pathSep = System.getProperty("path.separator"); // ; 或 :
System.out.println(os + " " + arch);

18.正則表達式

Pattern/Matcher、匹配、捕獲組、替換與常用模式。

Pattern 與 Matcher

Pattern.compile 編譯正則(緩存複用),matcher 匹配文本。find/matches/lookingAt 三種匹配。

1
2
3
4
5
6
Pattern p = Pattern.compile("\\d{3}-\\d{4}");
Matcher m = p.matcher("call 123-4567");
boolean found = m.find(); // 部分匹配
Matcher m2 = p.matcher("123-4567");
boolean full = m2.matches(); // 整體匹配
// 直接判斷:"123-4567".matches("\\d{3}-\\d{4}")

捕獲組

圓括號捕獲子串,group(1) 取第 1 組、命名組 (?<name>...) 用 group("name")。find 循環取全部。

1
2
3
4
5
6
7
8
9
Pattern p = Pattern.compile("(\\d{4})-(\\d{2})");
Matcher m = p.matcher("date: 2024-01");
if (m.find()) {
String year = m.group(1); // 2024
String month = m.group(2); // 01
}
// 命名組:
Pattern p2 = Pattern.compile("(?<year>\\d{4})-(?<month>\\d{2})");
// m.group("year") 取 2024

查找全部

find 循環或 matcher.results 拿全部匹配。替換全部用 replaceAll(含 $1 組引用)。

1
2
3
4
5
6
7
8
Matcher m = Pattern.compile("\\w+").matcher(text);
while (m.find()) {
System.out.println(m.group()); // 每個單詞
}
// Java 9+:
m.results().forEach(r -> System.out.println(r.group()));
// 替換:
String masked = "123456".replaceAll("(\\d{3})(\\d+)", "$1***");

替換

replaceAll/replaceFirst 按正則替換,$1/$2 引用組。Matcher.appendReplacement 逐段處理。

1
2
3
4
5
6
7
8
9
String s = "a1b2c3";
String all = s.replaceAll("\\d", "#"); // a#b#c#
String first = s.replaceFirst("\\d", "#"); // a#b2c3
// 回調替換:
Matcher m = Pattern.compile("\\d").matcher(s);
StringBuffer sb = new StringBuffer();
while (m.find()) m.appendReplacement(sb, "{" + m.group() + "}");
m.appendTail(sb);
// sb = "a{1}b{2}c{3}"

Pattern 標誌

Pattern.CASE_INSENSITIVE 忽略大小寫、MULTILINE 讓 ^$ 匹配行、DOTALL 讓 . 匹配換行。

1
2
3
4
5
6
Pattern p = Pattern.compile("^java",
Pattern.CASE_INSENSITIVE | Pattern.MULTILINE);
Matcher m = p.matcher("Java\nnot java\n");
// 內聯標誌:
Pattern p2 = Pattern.compile("(?i)java"); // 忽略大小寫
// (?s) DOTALL、(?m) MULTILINE

常用模式

郵箱、URL、IP、手機號的常用正則。生產級校驗(真郵箱格式)用專門庫。

1
2
3
4
5
String email = "^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$";
String ip = "^((?:\\d{1,3}\\.){3}\\d{1,3})$";
String phone = "^1[3-9]\\d{9}$"; // 中國大陸手機
boolean ok = input.matches(phone);
// 注意:\\d 在 Java 字符串裏是正則 \d

量詞與錨點

* 零或多次、+ 一次或多次、? 零或一次、{n,m} 指定次數。^ 行首、$ 行尾、\\b 詞邊界。

1
2
3
4
5
6
"\\d+" // 一個或多個數字
"colou?r" // color 或 colour
"\\d{2,4}" // 2 到 4 位數字
"^start" // 行首 start
"end$" // 行尾 end
"\\bword\\b" // 獨立單詞 word

前瞻與後顧

前瞻 (?=...) 檢查後文、負向前瞻 (?!...) 排除後文、後顧 (?<=...) 檢查前文,均不消費字符。

1
2
3
4
5
6
7
Pattern p = Pattern.compile("\\d+(?=\\s*元)"); // 後接元才匹配
Matcher m = p.matcher("價格 100 元");
if (m.find()) System.out.println(m.group()); // 100
// 負向前瞻:數字後不接字母
Pattern p2 = Pattern.compile("\\d+(?!\\p{L})");
// 後顧需固定長度:
Pattern p3 = Pattern.compile("(?<=價格為)\\d+");

19.構建與調試

Maven/Gradle、javac/jar、JUnit 與 JVM 調試。

Maven 生命週期

mvn compile/test/package/install 生命週期階段。target 目錄輸出 class 與 jar。

1
2
3
4
5
6
// $ mvn clean compile # 清理並編譯
// $ mvn test # 運行測試
// $ mvn package # 打包 jar
// $ mvn install # 裝到本地倉庫
// $ mvn dependency:tree # 查看依賴樹
// 輸出在 target/ 目錄

pom.xml

pom.xml 定義項目:groupId/artifactId/version 座標、dependencies 依賴、properties 屬性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>app</artifactId>
<version>1.0.0</version>
<properties><maven.compiler.release>17</maven.compiler.release></properties>
<dependencies>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
</project>

Gradle

Gradle 基於 Groovy/Kotlin DSL。task 定義構建步驟,依賴由中央倉庫解析。

1
2
3
4
5
6
7
8
9
// build.gradle:
plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
}
test { useJUnitPlatform() }
// $ gradle build / gradle test / gradle run

JUnit 測試

JUnit 5:@Test 測試方法、@BeforeEach 初始化、assert* 斷言。mvn test 運行。

1
2
3
4
5
6
7
8
9
10
11
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
void add_returns_sum() {
var calc = new Calculator();
assertEquals(5, calc.add(2, 3));
assertTrue(calc.add(1, 1) == 2);
}
}
// 斷言失敗會明確報告期望與實際

jar 打包

jar 命令打包 class。可執行 jar 需要 Main-Class 清單。java -jar 運行。

1
2
3
4
5
// $ jar cf app.jar com/ # 打包目錄
// $ jar cfe app.jar Main com/ # 指定主類
// $ java -jar app.jar # 運行
// 查看內容:jar tf app.jar
// MANIFEST.MF 裏 Main-Class: Main

JVM 參數

-Xmx 最大堆、-Xms 初始堆、-XX:+PrintGCDetails GC 日誌、-D 系統屬性。

1
2
3
4
5
// $ java -Xms256m -Xmx2g -jar app.jar
// $ java -Dserver.port=8080 -jar app.jar
// $ java -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError app
// 查看默認值:java -XX:+PrintFlagsFinal -version
// 調試:java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005

日誌配置

SLF4J 門面 + Logback 實現。logback.xml 配置級別與輸出。生產別用 System.out。

1
2
3
4
5
6
7
8
9
10
11
// logback.xml:
<configuration>
<root level="INFO">
<appender-ref ref="STDOUT" />
</root>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender" />
</configuration>
// 代碼:
private static final Logger log =
LoggerFactory.getLogger(App.class);
log.info("user {} logged in", id);

依賴管理

Maven 依賴按 scope 分類:compile/test/runtime。exclusions 排除傳遞依賴,版本衝突用 dependencyManagement 統一。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.2</version>
<scope>test</scope> <!-- 僅測試期可用 -->
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
<exclusions> <!-- 排除傳遞依賴 -->
<exclusion>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
// 版本衝突統一在 dependencyManagement 中聲明版本

官方鏈接

直達官方文檔與資源。

關於本速查

本頁是 Java 17(LTS)的自包含速查手冊,覆蓋語言核心、JDK 常用類庫與構建生態在真實項目中約 80% 的常見用法。內容偏向現代慣用法:var 局部變量推斷、switch 表達式、文本塊、記錄類(record)、密封類(sealed)、Stream 流式 API。Java 由 Sun Microsystems 於 1995 年發佈,以「一次編寫、到處運行」的 JVM 生態著稱,是企業後端、Android 開發與大數據領域使用最廣的語言之一。 19 個章節各自聚焦一個主題:基礎語法、變量、類型與引用、控制流、函數、字符串、集合框架(List/Set/Map)、內存管理(GC)、面向對象、異常處理、輸入輸出、常見誤區、併發、網絡、時間、進程、正則與構建工具(Maven/Gradle)。每個小節都配有「概念介紹 + 可直接複製的代碼片段」。 所有代碼與文字均在瀏覽器本地渲染,無任何數據離開你的設備。權威參考見 Oracle 官方 Java 文檔。

版本 2.1.0