Java Try-with-resources: ปิด Resource ย้อนลำดับแม้กลางทางมี Exception
การเปิดไฟล์, connection หรือ lock สำเร็จเพียงครึ่งชุดก็ต้องปิดของที่เปิดแล้วครับ try-with-resources ผูกการได้ resource กับ cleanup ไว้ในโครงสร้างภาษา และปิดย้อนลำดับการประกาศแม้โค้ดใน block โยน exception
WHY: Cleanup เป็นส่วนหนึ่งของ Correctness
finally เขียนได้ แต่เมื่อมีหลาย resource โค้ดจะซ้อนและอาจกลบ exception ต้นเหตุด้วย exception ตอนปิด JLS กำหนดลำดับของ try-with-resources ชัดเจน พร้อมเก็บ exception ตอน close เป็น suppressed exception เมื่อมีปัญหาหลักเกิดก่อน
resource ต้องเป็น AutoCloseable หรือ Closeable หลังสร้างสำเร็จ Java จะรับผิดชอบเรียก close() ให้ หาก constructor ตัวถัดไปล้ม ตัวก่อนหน้าที่ยืนยันแล้วก็ยังถูกปิด
HOW: พิสูจน์ลำดับด้วย Event List
import java.util.ArrayList;
import java.util.List;
public class Main {
static final class Resource implements AutoCloseable {
private final String name;
private final List<String> events;
Resource(String name, List<String> events) {
this.name = name;
this.events = events;
events.add("open:" + name);
}
@Override
public void close() {
events.add("close:" + name);
}
}
public static void main(String[] args) {
List<String> events = new ArrayList<>();
try (Resource first = new Resource("first", events);
Resource second = new Resource("second", events)) {
events.add("use");
}
assert events.equals(List.of(
"open:first", "open:second", "use",
"close:second", "close:first"
));
}
}
ลำดับย้อนกลับเหมือน stack: ตัวหลังมักพึ่งตัวก่อน จึงต้องปิดตัวหลังให้เสร็จก่อน เช่น stream wrapper ปิดก่อน file stream ด้านล่าง
ระวัง Ownership ที่ไม่ชัด
อย่าใส่ resource ที่ method อื่นยังต้องใช้หลัง block จบเข้า try นี้ ถ้า method รับ resource จากผู้เรียก ต้องตกลงว่าใครเป็นเจ้าของการปิด การปิดของที่ยืมมาทำให้ failure เกิดไกลจากต้นเหตุ
เมื่อ close() โยน exception ให้ตรวจ getSuppressed() ใน logging และ test แต่ไม่ควรออกแบบ cleanup ให้ล้มง่าย ปิดแบบ idempotent เมื่อทำได้ และอย่าทำ network call ยาวโดยไม่มี timeout ใน close
Recap
- ประกาศ resource ใน try เพื่อผูก lifetime กับ scope
- จำว่าปิดย้อนลำดับการประกาศ
- กำหนด ownership ก่อนรับหรือส่ง resource ข้าม method
- เก็บ suppressed exceptions โดยไม่กลบสาเหตุหลัก
พรุ่งนี้เลือก finally ที่มี close หลายตัวหนึ่งจุด แล้วเขียน test ลำดับ cleanup ก่อนย้ายมา try-with-resources ครับ