Java 8 Stream forEach 與傳統迭代的差異
一開始用 Stream 時,就是 forEach 走天下。後來才發現 Stream forEach 和傳統 Iterator 根本是兩套邏輯,搞混的話效能會爆。 概念差異傳統迭代:外部迭代(External Iteration)你決定怎麼走訪: 1234567891011121314151617List<String> names = Arrays.asList("Alice", "Bob", "Charlie");// for-loop 迴圈for (int i = 0; i < names.size(); i++) { System.out.println(names.get(i));}// Iterator 迴圈Iterator<String> iterator = names.iterator();while (iterator.hasNext()) { System.out.println(iterator.next());}//...
Thread.sleep 的七種替代寫法
每次要延遲執行時,很多人只想到 Thread.sleep()。但你知道嗎?有七種寫法可以選,每種各有優缺點。選對的話,程式真香,選錯的話,維護時會想揍自己。 方法 1:Thread.sleep(ms) - 最基本沒啥好說的,最原始的方式: 1234567try { System.out.println("Before sleep"); Thread.sleep(2000); // 睡 2 秒 System.out.println("After sleep");} catch (InterruptedException e) { Thread.currentThread().interrupt(); // 重新設定中斷狀態} 優點: 簡單直接缺點: 毫秒單位,容易打錯;InterruptedException 必須處理;不易讀 方法 2:TimeUnit.SECONDS.sleep(n) - 可讀性最好用 TimeUnit 包裝,讓意圖更清楚: 1234567try {...
Java Synchronized 六種同步模式完整解析
synchronized 這東西從大學開始就在教,但會用和懂用是兩碼子事。踩過不少坑之後,發現這六種同步模式各有眉角,搞不清楚一定會爆。 模式 1:Synchronized Method最簡單的寫法,直接加在方法上: 1234567891011public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; }} 鎖住的對象是什麼? this(呼叫這個方法的物件實例) 12345678910Counter counter1 = new Counter();Counter counter2 = new Counter();Thread t1 = new Thread(counter1::increment);Thread t...
CGLIB 動態代理原理與使用
剛開始用 Spring AOP 時,以為都是用 JDK Proxy。後來發現為啥有些類別代理不了,還是要靠 CGLIB,才開始研究這東西。CGLIB 現在真的無所不在,有必要搞懂它的原理。 為什麼需要 CGLIB?JDK Proxy 有個死穴:必須有 interface。 12345678910111213// JDK Proxy 能代理這個public interface UserService { void save(User user);}public class UserServiceImpl implements UserService { public void save(User user) { }}// JDK Proxy 代理不了這個 - 沒有 interfacepublic class OrderService { public void createOrder(Order order) { }} 現實中一堆類別沒有 interface,難不...
Java Supplier 函式介面用途與延遲執行
一開始看 Supplier 就覺得「幹嘛不直接傳值就好?」,後來用過幾次才明白,這東西在延遲執行的場景真的超香。 什麼是 SupplierSupplier 是 Java 8 引入的函式介面(Functional Interface),定義很簡單: 1234@FunctionalInterfacepublic interface Supplier<T> { T get();} 特點就是: 不接參數 - 呼叫時不需要傳任何東西 回傳一個值 - 回傳型別由泛型 T 決定 可以 lambda - 因為是 @FunctionalInterface,能用 lambda 表達 核心用途:延遲執行Supplier 最強的地方就是「延遲執行」。把一段邏輯包成 Supplier,等到真正需要時才呼叫 .get() 去執行。 實例 1:Logger 的 Lazy Evaluation最經典的例子: 12345// 不好的做法 - 即使 log level 是 INFO,也會執行運算logger.debug("Current value is &qu...
資料庫悲觀鎖與樂觀鎖完整比較
做過庫存扣款、積分更新這種涉及併發的操作沒有?一開始以為很簡單,後來才發現如果不懂鎖機制,資料一定會爆。悲觀鎖和樂觀鎖是兩種完全不同的思路,沒搞懂選錯一個,後期真的有夠慘。 悲觀鎖(Pessimistic Lock)悲觀鎖的思路就是:「我相信一定會有人跟我搶」,所以先佔著位子再說。 SQL 寫法最常見就是 SELECT … FOR UPDATE: 1234567891011@Repositorypublic class InventoryRepository { @Autowired private JdbcTemplate jdbcTemplate; public Integer getInventoryWithLock(Long productId) { String sql = "SELECT quantity FROM inventory WHERE product_id = ? FOR UPDATE"; return jdbcTemplate.queryForObjec...
Spring TransactionTemplate 程式化交易管理與 Rollback
當初一開始用 Spring,習慣只會 @Transactional 這招。後來才發現 TransactionTemplate 這東西,發現它在某些場景真的有夠方便。 @Transactional 的限制@Transactional 很直觀啦,加一個 annotation 就搞定交易管理。但踩坑的地方是: Proxy 限制 - 同個 class 內部呼叫不會觸發 proxy,交易會爆掉 粒度問題 - 整個方法才是一個交易,沒辦法在同一個方法裡控制多段獨立的交易 Rollback 控制 - 用 rollbackFor 有點硬,不能細粒度決定要不要 rollback TransactionTemplate 的救世主角色TransactionTemplate 提供了「程式化」的交易控制。不用 annotation,直接寫程式邏輯,搭配 callback 來執行。 基本用法首先注入 PlatformTransactionManager: 12345678910111213141516171819202122232425262728@Servicepublic class OrderSe...
Spring @Transactional 類別層級效果與 CGLIB Proxy
@Transactional 是 Spring 最常用的註解之一,但很多人對它的底層原理不太了解,導致寫出一些表面上沒問題,但實際上交易沒有生效的程式碼。我就踩過好幾次這種坑,最後才徹底理解它的運作方式。 類別層級 vs 方法層級@Transactional 可以標在類別上,也可以標在方法上。 標在類別上1234567891011121314151617@Service@Transactionalpublic class UserService { public void updateUser(User user) { // 有交易 } public void deleteUser(String userId) { // 也有交易 } @Transactional(readOnly = true) public User getUser(String userId) { // 覆蓋了類別層級的設定,用 readOnly ...
Spring Batch Partitioner 分區處理流程
在處理大量資料時,如果單線程處理會很慢。Spring Batch 的 Partitioner 就是用來把大任務分成多個小區塊,然後平行處理。我之前有個 ETL job 處理 1000 萬筆資料,用了 Partitioner 之後,執行時間直接從 2 小時降到 15 分鐘。 Partitioner 的核心概念Partitioner 做的事很簡單:把一個大任務分成 N 個小任務。 假設你要處理 1000 萬筆資料: 沒有 Partitioner:一個線程處理全部,花 1 小時 有 Partitioner(8 個 partition):8 個線程各處理 125 萬筆,花 7.5 分鐘 核心元件1. Partitioner InterfacePartitioner 的工作就是定義怎麼分割。你要實現 Partitioner interface: 12345678910111213141516171819202122232425262728public class CustomPartitioner implements Partitioner { @Overri...
Spring Boot REST API 檔案下載實作
檔案下載功能在 web 應用裡很常見,但實作得不夠好的話,會導致記憶體爆炸。我之前有個專案就因為沒注意這點,結果下載一個 1GB 的檔案直接把 Java heap 炸了。 今天就來介紹怎麼用 Spring Boot 安全地實作檔案下載,尤其是大檔案。 問題:傳統做法為什麼不行?傳統的做法可能是這樣的: 1234567891011121314@GetMapping("/download/{fileId}")public ResponseEntity<byte[]> downloadFile(@PathVariable String fileId) { byte[] fileContent = fileService.getFileContent(fileId); // 整個檔案都讀進記憶體! HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); ...














