中華人民共和國居民身份證號碼為 18 位,前 17 位為本體碼,第 18 位為校驗碼。校驗碼依據 ISO 7064:1983.MOD 11-2** 標準計算,能有效檢測單位數字錯誤、相鄰數字順序錯誤等常見輸入錯誤。本文提供標準 Java 實作與詳細算法解析。
Wi = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]
餘數: 0 1 2 3 4 5 6 7 8 9 10 校驗碼: 1 0 X 9 8 7 6 5 4 3 2
S = Σ(ai × Wi) (i=1..17) Y = S mod 11 校驗碼 = V[Y]
public class IdCardValidator {
// 加權因子陣列 (長度 17)
private static final int[] WEIGHTS = {
7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2
};
// 校驗碼對照表 (索引 = 餘數 0-10)
private static final char[] CHECK_CODES = {
'1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'
};
/**
* 計算身份證號碼的校驗碼
* @param id17 前 17 位數字字串
* @return 校驗碼字元 ('0'-'9' 或 'X')
* @throws IllegalArgumentException 輸入非 17 位純數字
*/
public static char calculateCheckCode(String id17) {
if (id17 == null || id17.length() != 17 || !id17.matches("\\d{17}")) {
throw new IllegalArgumentException("身份證本體碼必須為 17 位純數字");
}
int sum = 0;
for (int i = 0; i < 17; i++) {
int digit = id17.charAt(i) - '0'; // char 轉 int
sum += digit * WEIGHTS[i];
}
int remainder = sum % 11;
return CHECK_CODES[remainder];
}
/**
* 驗證完整 18 位身份證號碼是否合法
* @param id18 18 位身份證號碼
* @return true 表示校驗碼正確
*/
public static boolean validate(String id18) {
if (id18 == null || id18.length() != 18) return false;
String id17 = id18.substring(0, 17);
char providedCheckCode = Character.toUpperCase(id18.charAt(17));
char calculatedCheckCode = calculateCheckCode(id17);
return providedCheckCode == calculatedCheckCode;
}
/**
* 完整驗證:格式 + 校驗碼 + 出生日期合法性 + 地址碼存在性
* @param id18 18 位身份證號碼
* @return 驗證結果物件
*/
public static ValidationResult validateFull(String id18) {
ValidationResult result = new ValidationResult();
result.setIdCard(id18);
// 1. 基本格式
if (id18 == null || id18.length() != 18) {
result.addError("長度必須為 18 位");
return result;
}
if (!id18.substring(0, 17).matches("\\d{17}")) {
result.addError("前 17 位必須為數字");
return result;
}
char lastChar = Character.toUpperCase(id18.charAt(17));
if (!"0123456789X".contains(String.valueOf(lastChar))) {
result.addError("第 18 位必須為數字或 X");
return result;
}
// 2. 校驗碼
if (!validate(id18)) {
result.addError("校驗碼不正確");
return result;
}
// 3. 出生日期合法性 (第 7-14 位: YYYYMMDD)
String birthStr = id18.substring(6, 14);
try {
LocalDate birth = LocalDate.parse(birthStr, DateTimeFormatter.BASIC_ISO_DATE);
LocalDate now = LocalDate.now();
if (birth.isAfter(now) || birth.isBefore(LocalDate.of(1900, 1, 1))) {
result.addError("出生日期不合理");
}
result.setBirthDate(birth);
} catch (DateTimeParseException e) {
result.addError("出生日期格式無效");
}
// 4. 地址碼 (前 6 位) - 這裡僅做基本格式檢查,完整版需查詢行政區劃碼庫
String addressCode = id18.substring(0, 6);
if (!addressCode.matches("\\d{6}")) {
result.addError("地址碼格式錯誤");
}
result.setAddressCode(addressCode);
// 5. 性別 (第 17 位: 奇數男、偶數女)
int genderDigit = id18.charAt(16) - '0';
result.setGender(genderDigit % 2 == 1 ? "男" : "女");
result.setValid(result.getErrors().isEmpty());
return result;
}
// 內部結果類
public static class ValidationResult {
private String idCard;
private boolean valid;
private List errors = new ArrayList<>();
private LocalDate birthDate;
private String addressCode;
private String gender;
// Getters/Setters 省略...
}
// 測試主程式
public static void main(String[] args) {
String[] testCases = {
"11010519900307783X", // 正確範例
"110105199003077839", // 校驗碼錯誤
"11010519900307783", // 長度錯誤
"320124198506150025" // 另一正確範例
};
for (String id : testCases) {
ValidationResult r = validateFull(id);
System.out.printf("%s => %s %s%n", id, r.isValid() ? "✅ 合法" : "❌ 不合法", r.getErrors());
}
}
}
權重陣列 `[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]` 並非隨機,而是基於數學優化:
| 功能 | 實作方向 |
|---|---|
| 行政區劃碼驗證 | 引入最新 `GB/T 2260` 行政區劃代碼庫 (定期更新) |
| 15 位舊版身份證支援 | 轉換邏輯:出生年補 19/20、重新計算校驗碼 |
| 港澳台居民身份證 | 不同格式規則,需獨立驗證器 |
| 高併發效能 | 預計算權重表、避免物件創建、使用基礎型別運算 |
| 單元測試覆蓋 | 邊界值 (1900/2100 年)、閏年、無效日期、所有校驗碼值 |
身份證校驗是表單驗證、實名認證、風控系統的基礎關卡。掌握標準算法並建立可複用、可測試、可擴展的驗證組件,是 Java 後端開發的基本功之一。