在Java中存儲手機號碼,絕對不要使用數值類型(如 int, long)。

正確的做法是始終使用字符串類型 String。原因和最佳實踐如下:

⚠️ 為什麼不能用 int 或 long?

  1. 精度丟失與格式問題
  • 前導零丟失:號碼 01012345678 存入 long 會變成 1012345678,丟失了北京區號標誌。
  • 國際號碼無法存儲:帶 + 號(如 +861012345678)或國家代碼的號碼無法用數字類型表示。
  • 最大長度限制long 最大為 19 位,但某些國家號碼可能更長,且無法存儲分機號(如 -)。
  1. 語義錯誤:手機號碼不是用於數學計算的“數字”,它只是一個標識符。你不會對電話號碼做加減乘除。

✅ 最佳實踐:使用 String 類型

public class User {
    private String phoneNumber; // 正確:使用String

    // 構造函數、Getter/Setter...
}

核心優勢

  • 完整性:完美保存所有字符(+, -, (, ), 空格,前導零)。
  • 語義正確:明確表示這是文本標識符。
  • 靈活性:輕鬆應對全球所有格式的號碼。

🔒 進階處理:增加校驗與規範存儲

在業務層面,為了數據質量和一致性,建議這樣做:

1. 輸入清洗與標準化 在存入數據庫前,先移除所有非數字字符(除開頭的+號),並統一格式。

public class PhoneNumberUtils {
    /**
     * 標準化手機號碼(中國大陸為例)
     * 輸入: +86-139-1234-5678, (010) 12345678 等
     * 輸出: +8613912345678, 01012345678(去掉所有分隔符,保留+)
     */
    public static String normalize(String phoneNumber) {
        if (phoneNumber == null) return null;
        // 保留開頭的+號,移除其他所有非數字字符
        String normalized;
        if (phoneNumber.startsWith("+")) {
            normalized = "+" + phoneNumber.substring(1).replaceAll("[^0-9]", "");
        } else {
            normalized = phoneNumber.replaceAll("[^0-9]", "");
        }
        return normalized;
    }
}

2. 使用 JSR 380 Bean Validation 進行註解校驗 在實體類中直接使用註解校驗,清晰又強大。

import javax.validation.constraints.Pattern;
import javax.validation.constraints.NotBlank;

public class User {
    @NotBlank(message = "手機號不能為空")
    @Pattern(regexp = "^((\\+86)|(86))?1[3-9]\\d{9}$", 
             message = "請輸入有效的中國大陸手機號")
    private String phoneNumber;

    // 也可以定義更通用的國際號碼正則
    // @Pattern(regexp = "^\\+[1-9]\\d{1,14}$", message = "請輸入有效的E.164格式號碼")
}

3. 數據庫層:使用 VARCHAR 並考慮添加索引

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    phone_number VARCHAR(20) NOT NULL, -- 長度足夠容納國際號碼
    -- 可添加唯一約束
    CONSTRAINT uk_phone UNIQUE (phone_number)
);
-- 為經常查詢的字段添加索引
CREATE INDEX idx_phone ON users(phone_number);

4. 使用專業庫處理(高級場景) 對於需要解析國家代碼、驗證有效性的國際化應用,推薦使用 Google 的 libphonenumber 庫。

// 示例:使用 libphonenumber
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
try {
    PhoneNumber numberProto = phoneUtil.parse("+8613912345678", "CN");
    boolean isValid = phoneUtil.isValidNumber(numberProto); // 驗證有效性
    String formatted = phoneUtil.format(numberProto, 
                                        PhoneNumberFormat.INTERNATIONAL);
} catch (NumberParseException e) {
    // 處理解析異常
}

💡 總結與推薦方案

場景

推薦方案

關鍵點

簡單國內應用

String + 註解校驗

在實體類用 @Pattern 正則校驗,足夠應對大部分場景

國際化應用

String + libphonenumber 庫

能權威解析、驗證、格式化全球號碼

數據庫設計

VARCHAR(20) 左右,加索引

長度預留,對查詢字段建立索引

🚀 完整示例代碼

// 1. 實體類定義
public class User {
    @NotBlank
    @Pattern(regexp = "^((\\+86)|(86))?1[3-9]\\d{9}$")
    private String phoneNumber;
    // ... 其他字段
}

// 2. 服務層處理
@Service
public class UserService {
    public void register(User user) {
        // 標準化存儲
        String normalizedPhone = PhoneNumberUtils.normalize(user.getPhoneNumber());
        user.setPhoneNumber(normalizedPhone);
        // 然後存入數據庫...
    }
}

記住這個核心原則:手機號碼是標識文本,不是數學數字。 堅持用 String,並在業務層做好清洗和校驗,就能構建出健壯的系統。