iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
自我挑戰組

從0開始學習Java系列 第 11

把 Set / List / Map / Collection / Collections 都串在一起

  • 分享至 

  • xImage
  •  
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

public class PaymentDemo {

    // === 模擬:付款方式 & 狀態 ===
    enum PaymentMethod { CREDIT_CARD, ATM, CVS, BARCODE }
    enum PaymentStatus { INIT, PAID, FAILED, REFUND }

    // === 訂單物件 ===
    static class Order {
        private final String orderId;
        private int amount;
        private PaymentMethod method;
        private PaymentStatus status;
        private final LocalDateTime createdAt;

        public Order(String orderId, int amount, PaymentMethod method, PaymentStatus status) {
            this.orderId = orderId;
            this.amount = amount;
            this.method = method;
            this.status = status;
            this.createdAt = LocalDateTime.now();
        }

        public String getOrderId() { return orderId; }
        public int getAmount() { return amount; }
        public PaymentMethod getMethod() { return method; }
        public PaymentStatus getStatus() { return status; }
        public LocalDateTime getCreatedAt() { return createdAt; }

        public void setStatus(PaymentStatus status) { this.status = status; }
        public void setAmount(int amount) { this.amount = amount; }
        public void setMethod(PaymentMethod method) { this.method = method; }

        // 以 orderId 當唯一識別,利於在 Set / Map 判斷唯一性
        @Override public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Order)) return false;
            Order order = (Order) o;
            return Objects.equals(orderId, order.orderId);
        }
        @Override public int hashCode() { return Objects.hash(orderId); }

        @Override public String toString() {
            return "Order{" +
                    "orderId='" + orderId + '\'' +
                    ", amount=" + amount +
                    ", method=" + method +
                    ", status=" + status +
                    ", createdAt=" + createdAt +
                    '}';
        }
    }

    // === 通用:依 key 升冪排序 Map 並轉 querystring,再做 SHA-256 (示意用,非特定廠商實作) ===
    static String signParams(Map<String, String> params, String secret) {
        // 1) TreeMap:照 key 升冪(常見於金流簽章)
        Map<String, String> sorted = new TreeMap<>(params);

        // 2) 串接為 key=value&key=value...
        String query = sorted.entrySet().stream()
                .map(e -> e.getKey() + "=" + e.getValue())
                .collect(Collectors.joining("&"));

        // 3) 加鹽/密鑰(依不同金流文件規範處理,這裡示範在前後加上 secret)
        String toHash = secret + query + secret;

        // 4) SHA-256
        return sha256Hex(toHash).toUpperCase();
    }

    static String sha256Hex(String s) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            byte[] out = md.digest(s.getBytes(StandardCharsets.UTF_8));
            StringBuilder sb = new StringBuilder(out.length * 2);
            for (byte b : out) {
                sb.append(String.format("%02x", b));
            }
            return sb.toString();
        } catch (Exception e) {
            throw new RuntimeException("Hash error", e);
        }
    }

    public static void main(String[] args) {
        // ===== 1) Set:維護唯一的支援付款方式,並建立不可變視圖 =====
        Set<PaymentMethod> supported = new HashSet<>();
        supported.add(PaymentMethod.CREDIT_CARD);
        supported.add(PaymentMethod.ATM);
        supported.add(PaymentMethod.CVS);
        supported.add(PaymentMethod.CREDIT_CARD); // 重複,不會加入
        Set<PaymentMethod> supportedReadonly = Collections.unmodifiableSet(supported);
        System.out.println("支援付款方式(唯一路徑 + 不可變):" + supportedReadonly);

        // ===== 2) List:保存訂單建立順序,後續可以排序/過濾 =====
        List<Order> orders = new ArrayList<>();
        orders.add(new Order("A20250822001", 1500, PaymentMethod.CREDIT_CARD, PaymentStatus.INIT));
        orders.add(new Order("A20250822002", 2999, PaymentMethod.ATM,          PaymentStatus.PAID));
        orders.add(new Order("A20250822003",  800, PaymentMethod.CVS,          PaymentStatus.FAILED));
        orders.add(new Order("A20250822004", 5000, PaymentMethod.CREDIT_CARD,  PaymentStatus.PAID));

        System.out.println("\n原始建立順序:");
        orders.forEach(System.out::println);

        // Collections.sort + Comparator:依金額降冪排序
        orders.sort(Comparator.comparingInt(Order::getAmount).reversed());
        System.out.println("\n依金額(大→小)排序:");
        orders.forEach(System.out::println);

        // max/min:找最大/最小金額訂單
        Order maxOrder = Collections.max(orders, Comparator.comparingInt(Order::getAmount));
        Order minOrder = Collections.min(orders, Comparator.comparingInt(Order::getAmount));
        System.out.println("\n最高金額訂單:" + maxOrder);
        System.out.println("最低金額訂單:" + minOrder);

        // ===== 3) Map:以訂單編號快速查詢/更新 =====
        Map<String, Order> orderIndex = new HashMap<>();
        for (Order o : orders) {
            orderIndex.put(o.getOrderId(), o);
        }

        // 模擬:金流回調把某訂單改為 PAID
        String callbackOrderId = "A20250822001";
        Order toUpdate = orderIndex.get(callbackOrderId);
        if (toUpdate != null) {
            toUpdate.setStatus(PaymentStatus.PAID);
            System.out.println("\n回調更新狀態 → PAID:" + toUpdate);
        }

        // 模擬:查詢不存在訂單
        Order notFound = orderIndex.getOrDefault("NO_SUCH_ORDER", null);
        System.out.println("查無訂單結果:" + notFound);

        // ===== 4) 典型彙總:用 Map.merge() 按付款方式累加營收 =====
        Map<PaymentMethod, Integer> revenueByMethod = new EnumMap<>(PaymentMethod.class);
        for (Order o : orders) {
            if (o.getStatus() == PaymentStatus.PAID) {
                revenueByMethod.merge(o.getMethod(), o.getAmount(), Integer::sum);
            }
        }
        System.out.println("\n各付款方式營收彙總(僅 PAID): " + revenueByMethod);

        // ===== 5) Collection 介面:以最上層型別做共通處理(寫法更抽象) =====
        Collection<Order> orderView = orders; // List 是 Collection 的子介面
        long paidCount = orderView.stream().filter(o -> o.getStatus() == PaymentStatus.PAID).count();
        System.out.println("\nPAID 訂單筆數(Collection 流式計算):" + paidCount);

        // ===== 6) 利用 Set 去重:合併多來源的「回調事件」,避免重複處理 =====
        List<String> gatewayCallbacks = List.of(
                "A20250822002", "A20250822002", "A20250822003", "A20250822004"
        );
        Set<String> uniqueCallbackIds = new HashSet<>(gatewayCallbacks); // 去重
        System.out.println("\n回調訂單(原始/去重):" + gatewayCallbacks + " / " + uniqueCallbackIds);

        // ===== 7) 參數簽章(常見:依字典序排序 → 串接 → 雜湊)=====
        Map<String, String> payParams = new HashMap<>();
        payParams.put("MerchantID", "2000132");
        payParams.put("MerchantTradeNo", "A20250822001");
        payParams.put("MerchantTradeDate", "2025/08/22 20:45:00");
        payParams.put("PaymentType", "aio");
        payParams.put("TotalAmount", "1500");
        payParams.put("TradeDesc", "測試訂單");
        payParams.put("ItemName", "測試商品");
        // ... 實務上請依廠商文件補齊必要欄位

        String secret = "YOUR_SECRET_KEY"; // 示意:請改成你的金流密鑰
        String signature = signParams(payParams, secret);
        System.out.println("\n簽章結果(示意):" + signature);

        // ===== 8) Collections 的其他實用技巧 =====
        // 8-1) 建立不可變空集合:避免 null(回傳型別友善)
        List<Order> emptySafeList = Collections.emptyList();
        System.out.println("\n安全空清單(避免 null):" + emptySafeList);

        // 8-2) 建立只讀快照:避免呼叫端誤改
        List<Order> snapshot = Collections.unmodifiableList(new ArrayList<>(orders));
        System.out.println("只讀快照大小:" + snapshot.size());
        // snapshot.add(...)  // 這行若解除註解會拋 UnsupportedOperationException

        // 8-3) 同步化(若多執行緒下要保護 List)
        List<Order> threadSafeList = Collections.synchronizedList(new ArrayList<>(orders));
        synchronized (threadSafeList) { // 迭代時需額外同步
            // do something
        }

        // 8-4) 頻率統計(例如失敗原因/錯誤碼),這裡示範用 Collections.frequency
        List<PaymentStatus> statusList = orders.stream().map(Order::getStatus).toList();
        int failedCount = Collections.frequency(statusList, PaymentStatus.FAILED);
        System.out.println("FAILED 筆數(Collections.frequency):" + failedCount);
    }
}


上一篇
Java 的常見集合類別小範例
下一篇
加密是什麼?
系列文
從0開始學習Java21
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言