1주차 Unit 7.1 — try-finally → try-with-resources

Psj·2일 전

F-lab

목록 보기
46/52

Unit 7.1 — try-finally → try-with-resources

F-LAB JAVA · 1주차 · Phase 7 · 예외 처리와 자원 관리


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • try-finally의 4가지 구조적 문제는?
  • AutoCloseable 인터페이스는 왜 필요했나?
  • 컴파일러는 try-with-resources를 어떻게 풀어내나?
  • Suppressed Exception은 정확히 어떤 상황에서 생기나?
  • 자원의 close 순서는? 그렇게 정한 이유는?
  • Java 9에서 뭐가 더 좋아졌나?
  • ILIC에서 try-with-resources를 어떻게 쓰고 있나?

🎯 핵심 한 문장

try-with-resources는 AutoCloseable 자원을 컴파일러가 대신 닫아주는 Java 7+ 문법으로, try-finally가 가진 "장황함 · 예외 마스킹 · close 누락 · NPE 위험" 4가지 문제를 동시에 해결한다.

비유 — 호텔 키 자동 반납

시대동작문제
try-finally (Java 6 이전)체크아웃 시 손님이 직접 프론트에 키 반납까먹으면 분실 요금. 가방 분실 + 키 반납을 같이 처리하다 둘 중 하나만 처리됨
try-with-resources (Java 7+)방을 나서면 NFC가 자동 반납 처리사고가 있어도 모든 자원이 정리되고, 사고 기록은 suppressed에 남음

🧭 9개 섹션 로드맵

1. 탄생 배경       — try-finally의 비극 (Java 6 이전 코드)
2. AutoCloseable   — 모든 것이 시작되는 인터페이스
3. 문법과 규칙     — try-with-resources 정확한 사용법
4. 컴파일러 변환   — javap로 본 진짜 모습 (Java 7 vs 9)
5. 예외 마스킹     — Suppressed Exception의 정체
6. 닫는 순서       — 왜 역순인가
7. ILIC 실무 코드  — JDBC, IO, HTTP, Excel
8. 흔한 실수 9가지 — 안티패턴과 처방
9. 면접 질문 + 자기 점검

1️⃣ 탄생 배경 — try-finally의 비극

1.1 Java 6 이전, 가장 많이 본 코드

JDBC로 데이터 1건 조회. 자원 3개(Connection, PreparedStatement, ResultSet)를 닫아야 한다.

public Shipment findById(Long shipmentId) {
    Connection conn = null;
    PreparedStatement pstmt = null;
    ResultSet rs = null;

    try {
        conn = dataSource.getConnection();
        pstmt = conn.prepareStatement(
            "SELECT * FROM shipments WHERE id = ?"
        );
        pstmt.setLong(1, shipmentId);
        rs = pstmt.executeQuery();

        if (rs.next()) {
            return mapToShipment(rs);
        }
        return null;
    } catch (SQLException e) {
        throw new DataAccessException(e);
    } finally {
        // 닫는 순서: 역순 (rs → pstmt → conn)
        if (rs != null) {
            try { rs.close(); } catch (SQLException ignored) {}
        }
        if (pstmt != null) {
            try { pstmt.close(); } catch (SQLException ignored) {}
        }
        if (conn != null) {
            try { conn.close(); } catch (SQLException ignored) {}
        }
    }
}

비즈니스 로직(SQL 실행 4줄) vs 자원 정리(15줄).
배보다 배꼽이 더 크다.


1.2 4가지 구조적 문제

❶ 장황함 (Verbose)

  • 자원 1개당 try-catch-ignored 블록 1개
  • 자원 N개 → 본문보다 finally가 더 길어짐
  • 핵심 로직을 읽기 위해 finally를 스크롤로 넘겨야 함

❷ NPE 위험

finally {
    rs.close();      // ❌ rs가 null이면 NPE
    pstmt.close();   // ❌ 위에서 NPE 나면 여기 못 옴
    conn.close();    // ❌ 그래서 conn이 영영 안 닫힘 → Connection Pool 고갈
}

if (rs != null) 체크를 매번 손으로 써야 한다.
한 번이라도 까먹으면 Connection Leak → 운영 장애.

❸ 중첩 try-catch

close()IOException이나 SQLException을 던질 수 있다.
finally 안에서 또 try-catch를 써야 한다.

finally {
    if (rs != null) {
        try {                    // 또 try
            rs.close();
        } catch (SQLException e) {
            // 여기서 뭐 할까? 로그? 그냥 무시?
        }
    }
    // ... ×3
}

❹ 예외 마스킹 (Exception Masking) — 가장 위험

본문에서 예외가 발생했고, finally에서도 예외가 발생하면
finally 예외가 본문 예외를 덮어쓴다.

try {
    rs = pstmt.executeQuery();      // ❌ SQLException("invalid SQL")
    return mapToShipment(rs);
} finally {
    rs.close();                     // ❌ SQLException("connection reset")
}
// 호출자는 "connection reset"만 보게 됨
// 진짜 원인인 "invalid SQL"은 영영 사라짐

운영에서 가장 디버깅이 어려운 종류의 버그다.
스택트레이스만 보고 엉뚱한 곳을 며칠 헤맨다.


1.3 Apache Commons IOUtils의 등장 — 임시방편

문제가 워낙 심각하다 보니 Apache가 유틸을 만들었다.

import org.apache.commons.io.IOUtils;

InputStream in = null;
try {
    in = new FileInputStream("invoice.pdf");
    // ... 처리
} finally {
    IOUtils.closeQuietly(in);   // null 체크 + try-catch 캡슐화
}

하지만 이것도 임시방편.

  • null 체크와 예외 무시를 한 줄로 줄였을 뿐
  • 여전히 자원마다 try-finally 필요
  • 예외 마스킹은 그대로
  • "quietly"라는 이름이 암시하듯 — 예외를 조용히 삼킨다

Java 7은 이 문제를 언어 레벨에서 해결해야 했다.


2️⃣ AutoCloseable — 모든 것의 출발점

2.1 인터페이스 정의

public interface AutoCloseable {
    void close() throws Exception;
}

단 한 줄. 메서드 하나. 그러나 의미는 무겁다.

"나는 닫혀야 할 자원이다. try-with-resources가 알아서 닫아라."

2.2 Closeable과의 차이

Java 5에 이미 Closeable이 있었지만 한계가 있었다.

// java.io.Closeable (Java 5)
public interface Closeable extends AutoCloseable {
    void close() throws IOException;   // IOException만
}

// java.lang.AutoCloseable (Java 7) — 더 일반화
public interface AutoCloseable {
    void close() throws Exception;     // 어떤 예외든 가능
}
항목CloseableAutoCloseable
패키지java.iojava.lang
등장Java 5Java 7
예외IOExceptionException (더 일반)
멱등성 권장✅ 강하게 권장권장 (강제 아님)
적용 대상IO 스트림 위주DB · 락 · 트랜잭션 · 모든 자원

관계: Closeable extends AutoCloseable

→ 모든 Closeable은 자동으로 AutoCloseable.
→ try-with-resources에서 둘 다 사용 가능.

2.3 멱등성(idempotent) 권장

public void close() {
    if (closed) return;     // 이미 닫혔으면 무시
    closed = true;
    // 실제 정리 작업
}

close()가 여러 번 호출돼도 안전해야 한다.
JDK의 모든 Closeable 구현체가 이 규약을 지킨다.


2.4 무엇이 AutoCloseable인가 — 카테고리별 정리

📁 IO 스트림 (java.io)
   InputStream · OutputStream · Reader · Writer
   FileInputStream · BufferedReader · ObjectOutputStream
   PrintWriter · DataInputStream

📊 NIO (java.nio)
   FileChannel · SocketChannel · ServerSocketChannel
   AsynchronousFileChannel · DirectoryStream

💾 JDBC (java.sql)
   Connection · Statement · PreparedStatement
   ResultSet · CallableStatement

🌐 네트워크 (java.net)
   Socket · ServerSocket · HttpURLConnection
   HttpClient (Java 11+)

🔒 동시성 (java.util.concurrent)
   ExecutorService (Java 19+에서 AutoCloseable)
   Lock 구현체들

🧰 서드파티
   apache HttpClient
   Apache POI Workbook
   Jedis (Redis 클라이언트)
   MongoCollection 커서

현대 Java에서 자원이라 부를 수 있는 거의 모든 것이 AutoCloseable.


3️⃣ try-with-resources 문법과 규칙

3.1 기본 형태

try (Resource r = new Resource()) {
    // r 사용
}   // 여기서 r.close() 자동 호출

finally 없다. close() 호출 없다.
괄호 () 안에서 선언된 자원은 try 블록 종료 시 무조건 닫힌다.

3.2 여러 자원

try (
    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt = conn.prepareStatement(SQL);
    ResultSet rs = pstmt.executeQuery()
) {
    return mapToShipment(rs);
}
// 닫는 순서: rs → pstmt → conn (선언 역순)

세미콜론으로 구분. 줄바꿈은 자유.

3.3 Java 9+ 효과적으로 final인 변수 사용

Java 7~8까지는 try 괄호 안에서 반드시 새로 선언해야 했다.

// Java 7~8
Connection conn = dataSource.getConnection();
try (Connection c = conn) {     // 의미 없는 임시 변수 c
    // c.use()
}

Java 9부터는 외부에서 선언한 변수도 사용 가능. 단, final 또는 effectively final.

// Java 9+
Connection conn = dataSource.getConnection();
try (conn) {                    // 깔끔!
    // conn.use()
}

Effectively final: 한 번도 재할당되지 않은 변수. final 키워드를 안 붙여도 그렇게 취급된다.

Connection conn = dataSource.getConnection();
conn = anotherSource.getConnection();   // ❌ 재할당
try (conn) { ... }              // 컴파일 에러

3.4 catch · finally와 함께

try (Connection conn = dataSource.getConnection()) {
    // 본문
} catch (SQLException e) {
    log.error("DB error", e);
    throw new DataAccessException(e);
} finally {
    log.info("query finished");
}

순서:
1. 본문 실행
2. (예외 발생 시) 자원 close → catch → finally
3. (정상 종료 시) 자원 close → finally

자원 close가 catch보다 먼저 일어남에 주의.

3.5 자원이 null이면?

InputStream in = null;
try (InputStream i = in) {
    // ...
}

in == null이어도 close()를 호출하지 않는다. NPE 안 난다.
컴파일러가 자동으로 null 체크 추가해줌.


4️⃣ 컴파일러가 만드는 코드 — 진짜 모습

학습 포인트: javap -c -p 로 디컴파일해서 직접 확인할 것.

4.1 Java 7 변환 결과

// 원본
try (InputStream in = new FileInputStream("a.txt")) {
    use(in);
}

// 컴파일러가 만드는 코드 (개념적)
InputStream in = new FileInputStream("a.txt");
Throwable primaryException = null;
try {
    use(in);
} catch (Throwable t) {
    primaryException = t;
    throw t;
} finally {
    if (in != null) {
        if (primaryException != null) {
            try {
                in.close();
            } catch (Throwable suppressed) {
                primaryException.addSuppressed(suppressed);
            }
        } else {
            in.close();
        }
    }
}

핵심 동작:

  • 본문 예외(primary)를 기억해둠
  • close()가 또 예외를 던지면 → primary에 addSuppressed()로 첨부
  • primary가 호출자에게 전파됨 (사라지지 않음)
  • close() 예외는 primary의 그늘에 보관됨

4.2 Java 9 변환 — 단순화

Java 9부터는 컴파일러가 더 짧은 바이트코드를 생성한다.
$closeResource 합성 메서드(synthetic method)를 추가로 만들어서 중복 코드를 줄였다.

// Java 9+ 컴파일러가 추가하는 메서드
private static /* synthetic */ void $closeResource(
    Throwable primary, AutoCloseable resource
) {
    if (primary != null) {
        try {
            resource.close();
        } catch (Throwable suppressed) {
            primary.addSuppressed(suppressed);
        }
    } else {
        resource.close();
    }
}

자원이 여러 개면 close 로직 중복을 막아준다.
바이트코드 크기 ↓, 메서드 크기 ↓.

4.3 직접 확인하기

# 컴파일
javac Try7.java

# 디컴파일
javap -c -p Try7

# 더 자세히 (LineNumberTable 포함)
javap -c -p -v Try7

📌 실습 과제: 같은 코드를 --release 7--release 9로 컴파일해 바이트코드를 비교해 보라.


5️⃣ Suppressed Exception — 예외 마스킹의 끝

5.1 try-finally의 마스킹 재현

public class Masking {
    public static void main(String[] args) {
        try {
            tryFinally();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    static void tryFinally() throws Exception {
        Resource r = new Resource();
        try {
            r.use();         // 던짐: PRIMARY
        } finally {
            r.close();       // 던짐: CLOSE → PRIMARY를 덮어씀
        }
    }
}

출력:

java.lang.RuntimeException: CLOSE
    at Resource.close(...)
    at Masking.tryFinally(...)

PRIMARY는 어디로 갔는가? 영영 사라졌다.

5.2 try-with-resources의 해결

static void tryWithResources() throws Exception {
    try (Resource r = new <Resource()) {
        r.use();             // PRIMARY
    }                        // close() → suppressed로 첨부
}

출력:

java.lang.RuntimeException: PRIMARY
    at Resource.use(...)
    at Masking.tryWithResources(...)
    Suppressed: java.lang.RuntimeException: CLOSE
        at Resource.close(...)
        at Masking.tryWithResources(...)

→ PRIMARY가 메인. CLOSE는 Suppressed: 라벨로 첨부됨. 둘 다 살아있다.

5.3 Throwable API

public class Throwable {
    public final void addSuppressed(Throwable exception);
    public final Throwable[] getSuppressed();
}
  • addSuppressed(t) — 이미 던져진 예외에 t를 첨부
  • getSuppressed() — 첨부된 예외 배열 반환
  • 컴파일러가 try-with-resources 변환 시 자동으로 호출

5.4 운영에서 의미

운영 장애 디버깅 시:

시나리오try-finallytry-with-resources
본문에서 SQLException + rs.close()에서 SocketExceptionSocketException만 보임SQLException(메인) + SocketException(suppressed)
트랜잭션 롤백 실패 + 본문 비즈니스 예외롤백 실패만 보임비즈니스 예외(메인) + 롤백 실패(suppressed)

원인을 절대 잃지 않는다. 이게 진짜 가치.


6️⃣ 닫는 순서 — 왜 역순인가?

6.1 규칙

try (
    A a = new A();     // 1번째 열림
    B b = new B(a);    // 2번째 열림 (a에 의존)
    C c = new C(b)     // 3번째 열림 (b에 의존)
) {
    // 사용
}
// close 순서: c → b → a  (선언 역순)

6.2 왜?

의존성 그래프 때문이다.

ResultSet  ───depends on───►  PreparedStatement  ───depends on───►  Connection
   (RS)                              (PS)                                (C)
  • RS는 PS의 결과
  • PS는 C에서 만든 명령

만약 정순으로 닫으면?

1. Connection 닫음 → 그 안의 PS, RS는 좀비
2. PS 닫음        → 이미 죽은 자식
3. RS 닫음        → 이미 죽은 손자

→ 일부 드라이버는 NPE 또는 SQLException("connection is closed")을 던진다.
→ 의존성 역순으로 닫아야 자원이 정상 정리된다.

6.3 try-with-resources는 자동

선언 순서대로 열리고, 역순으로 닫는다. 컴파일러가 보장.

try (A a = new A(); B b = new B(a)) { ... }
//     ↑열림1    ↑열림2
//                                       닫힘1↓ 닫힘2↓
//                                       b.close() → a.close()

7️⃣ ILIC 실무 코드 — 실제 패턴 모음

국제종합물류 ILIC 코드베이스에서 try-with-resources를 어떻게 쓰고 있는가.

7.1 JDBC — 조회

@Repository
public class ShipmentRepository {

    private final DataSource dataSource;

    public Optional<Shipment> findById(Long id) {
        String sql = "SELECT * FROM shipments WHERE id = ?";

        try (
            Connection conn = dataSource.getConnection();
            PreparedStatement pstmt = conn.prepareStatement(sql)
        ) {
            pstmt.setLong(1, id);

            try (ResultSet rs = pstmt.executeQuery()) {
                if (rs.next()) {
                    return Optional.of(mapToShipment(rs));
                }
                return Optional.empty();
            }
        } catch (SQLException e) {
            throw new DataAccessException("shipment 조회 실패", e);
        }
    }
}

포인트:

  • ResultSet은 별도 try로 감쌈 — executeQuery()는 PS 이후에 호출되므로
  • 본문 15줄, 자원 관리 5줄 → 비율 역전 ✅
  • catch에서 도메인 예외로 wrapping (SQLException 노출 안 함)

7.2 파일 IO — Invoice PDF 처리

public byte[] readInvoicePdf(String path) {
    try (
        InputStream in = Files.newInputStream(Path.of(path));
        ByteArrayOutputStream out = new ByteArrayOutputStream()
    ) {
        in.transferTo(out);     // Java 9+
        return out.toByteArray();
    } catch (IOException e) {
        throw new InvoiceException("PDF 읽기 실패: " + path, e);
    }
}

포인트:

  • Files.newInputStreamNoSuchFileException 등을 던질 수 있음
  • transferTo()는 Java 9+ 의 편의 메서드 (스트림 → 스트림 복사)

7.3 HTTP — Carrier API 호출

private final HttpClient httpClient = HttpClient.newHttpClient();

public TrackingInfo fetchTracking(String trackingNumber) {
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(BASE_URL + "/tracking/" + trackingNumber))
        .header("Authorization", "Bearer " + apiKey)
        .GET()
        .build();

    try {
        HttpResponse<String> response = httpClient.send(
            request, BodyHandlers.ofString()
        );

        if (response.statusCode() == 200) {
            return parseTracking(response.body());
        }
        throw new CarrierApiException("status: " + response.statusCode());
    } catch (IOException | InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new CarrierApiException("API 호출 실패", e);
    }
}

포인트:

  • HttpClient 자체는 닫지 않음 (재사용)
  • Java 11+ HttpClient는 Java 21부터 AutoCloseable
  • ILIC은 11+ 사용 중이므로 try-with-resources 미적용

7.4 Excel — Apache POI

public List<Cargo> importFromExcel(MultipartFile file) {
    List<Cargo> cargoes = new ArrayList<>();

    try (
        InputStream in = file.getInputStream();
        Workbook workbook = WorkbookFactory.create(in)
    ) {
        Sheet sheet = workbook.getSheetAt(0);

        for (Row row : sheet) {
            if (row.getRowNum() == 0) continue;  // 헤더 스킵
            cargoes.add(rowToCargo(row));
        }
        return cargoes;
    } catch (IOException e) {
        throw new ExcelImportException("Excel 파싱 실패", e);
    }
}

포인트:

  • WorkbookCloseable (POI 3.10+)
  • 닫지 않으면 임시 파일이 디스크에 남는다 (실제 버그 있음)

7.5 Lock — 동시성 자원

Lock 인터페이스 자체는 AutoCloseable이 아니다.
그러나 직접 래퍼를 만들면 try-with-resources 패턴 사용 가능.

public final class CloseableLock implements AutoCloseable {

    private final Lock lock;

    public CloseableLock(Lock lock) {
        this.lock = lock;
        lock.lock();        // 생성 시 획득
    }

    @Override
    public void close() {
        lock.unlock();      // close에서 해제
    }
}

// 사용
public BigDecimal calculateFare(Route route) {
    try (var ignored = new CloseableLock(fareLock)) {
        // 임계 영역 — 자동으로 unlock 보장
        return fareCalculator.compute(route);
    }
}

포인트:

  • 락 해제 누락 = 데드락
  • finally 패턴보다 가독성 압도적
  • ⚠️ try (CloseableLock _ = ...) — 변수명이 의미 없으면 ignored 또는 _ (Java 21+)

8️⃣ 흔한 실수 9가지

실수 1 — try-with-resources 안에서 또 finally로 닫기

// ❌ 이중 close — 일부 자원은 멱등성이 없어 에러
try (InputStream in = new FileInputStream(path)) {
    // ...
} finally {
    in.close();   // 이미 자동 close 됨!
}

// ✅ 자동 close에 맡긴다
try (InputStream in = new FileInputStream(path)) {
    // ...
}

실수 2 — 자원을 변수로 받지 않음

// ❌ FileInputStream이 닫히지 않음
new FileInputStream(path).read();

// ✅ try-with-resources로
try (var in = new FileInputStream(path)) {
    in.read();
}

실수 3 — InputStream을 wrapper로 감싸지만 close가 한 곳만

// ❌ FileInputStream이 안 닫힐 수 있음
BufferedReader br = new BufferedReader(
    new InputStreamReader(
        new FileInputStream(path)
    )
);
try (br) {
    // ...
}
// br.close()는 내부적으로 InputStreamReader → FileInputStream 닫음 (대부분 잘 동작)
// 그러나 BufferedReader 생성자에서 OOM 나면? FileInputStream은 누수!

// ✅ 각각 try에 선언
try (
    FileInputStream fis = new FileInputStream(path);
    InputStreamReader isr = new InputStreamReader(fis);
    BufferedReader br = new BufferedReader(isr)
) {
    // ...
}

→ Java 표준 라이브러리는 잘 닫지만, 서드파티 wrapper는 모름.
중요한 자원일수록 명시적으로 선언하는 게 안전.

실수 4 — close()에서 비즈니스 예외 던지기

// ❌
public void close() throws BusinessException {
    if (state != COMMITTED) {
        throw new BusinessException("not committed");
    }
}

// 호출자
try (var tx = new Transaction()) {
    tx.execute();
    // ❌ close에서 BusinessException 발생 → 마치 본문 예외처럼 보임
}

close()자원 정리만 해야 한다.
비즈니스 검증은 본문에서 끝내라. close에서는 silent하게 마무리만.

실수 5 — null인 자원을 강제로 try에 넣기

// ❌
InputStream in = maybeNull();   // null 반환 가능
try (in) {
    // in이 null이면 NPE는 안 나지만,
    // 본문에서 in.read() 호출 시 NPE
}

// ✅ null 체크 후 분기
InputStream in = maybeNull();
if (in == null) {
    return;
}
try (in) {
    // 안전
}

→ try-with-resources는 자원이 null이어도 close 호출 안 함.
하지만 본문에서 사용하면 NPE. null 체크는 사용 전에 해라.

실수 6 — 사용자가 만든 클래스에 AutoCloseable 안 붙임

// ❌
public class Transaction {
    public void close() { ... }    // close가 있지만 인터페이스 안 구현
}

// 호출 시
try (var tx = new Transaction()) {  // ❌ 컴파일 에러
}

close() 메서드가 있다고 try-with-resources에 못 쓴다.
반드시 implements AutoCloseable 필요.

실수 7 — close()가 멱등하지 않음

// ❌
public class CountingResource implements AutoCloseable {
    private int count = 0;
    public void close() {
        count++;
        if (count > 1) throw new IllegalStateException("already closed");
    }
}

대부분의 자원은 직접 close 안 호출하지만, 코드 리뷰 중 또는 디버깅 시 두 번 호출될 수 있다.
멱등성 유지: if (closed) return;

실수 8 — 자원의 의존성 무시

// ❌
try (
    PreparedStatement pstmt = conn.prepareStatement(SQL);
    Connection conn = dataSource.getConnection()    // ❌ conn이 위에서 이미 사용됨
) { ... }
// 컴파일 에러: pstmt 선언 시점에 conn이 아직 선언 안 됨

// ✅
try (
    Connection conn = dataSource.getConnection();
    PreparedStatement pstmt = conn.prepareStatement(SQL)
) { ... }

선언 순서 = 열림 순서 = 의존성 방향.

실수 9 — Stream API 안에서 자원 누수

// ❌ 람다 안에서 Stream을 닫지 않음
files.stream()
    .map(path -> {
        try {
            return Files.lines(path);   // Stream<String> 반환 — 자원!
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    })
    .flatMap(Function.identity())
    .collect(toList());
// Files.lines로 연 스트림이 어디서 닫히는가? 안 닫힘!

// ✅
files.stream()
    .flatMap(path -> {
        try (Stream<String> lines = Files.lines(path)) {
            return lines.collect(toList()).stream();   // 한 번 모은 뒤 닫음
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    })
    .collect(toList());

Files.lines(), Files.list(), Files.walk()는 모두 Stream을 반환하며 자원.
반드시 try-with-resources로 감쌀 것.


9️⃣ 면접 질문 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
try-with-resources는 왜 등장했나?try-finally의 4가지 문제 (장황 · 예외 마스킹 · close 누락 · NPE) 해결
AutoCloseable과 Closeable의 차이?패키지 / 예외 타입 / Closeable extends AutoCloseable
Suppressed Exception은 뭔가?본문 예외가 메인, close 예외가 첨부. addSuppressed/getSuppressed
자원 close 순서는?선언 역순. 의존성 그래프 때문
try-finally로 같은 동작을 흉내내려면?Throwable 변수로 primary 추적 + addSuppressed 수동 호출 (현실적으로 불가능)
Java 7과 Java 9의 차이?Java 9는 effectively final 변수 사용 가능 + $closeResource 합성 메서드
close()는 멱등해야 하는가?강하게 권장. JDK 표준 구현체는 모두 멱등
Stream도 자원인가?Files.lines/list/walk는 자원. 반드시 닫아야 함

9.2 자기 점검 체크리스트

기본 이해

  • AutoCloseable 인터페이스 정의를 외울 수 있다
  • try-finally의 4가지 문제를 설명할 수 있다
  • Suppressed Exception을 코드로 재현할 수 있다
  • 자원 close 순서가 역순인 이유를 설명할 수 있다

실전 적용

  • JDBC 자원 3종을 try-with-resources로 작성할 수 있다
  • Files.lines() 같은 Stream 자원을 안전하게 닫을 수 있다
  • 사용자 정의 클래스를 AutoCloseable로 만들 수 있다
  • Lock 같은 자원을 try-with-resources로 감쌀 수 있다

면접 대비 — 5분 답변

  • try-finally → try-with-resources 전환의 4가지 이점
  • AutoCloseable vs Closeable
  • Suppressed Exception 메커니즘
  • Java 9의 effectively final 개선
  • 컴파일러가 만드는 코드 (개념적으로)

🎯 핵심 요약 — 3줄 정리

1. try-with-resources = AutoCloseable + 컴파일러 마법

  • try ( ... ) 안에 선언된 자원은 컴파일러가 finally 블록을 자동 생성
  • 본문 종료 시 (정상/예외 무관) 반드시 close() 호출
  • 자원 N개면 선언 역순으로 닫음

2. Suppressed Exception — 예외 마스킹 해결

  • 본문 예외(primary) + close 예외(suppressed) 둘 다 보존
  • addSuppressed() / getSuppressed() API
  • 운영 디버깅에서 원인을 절대 잃지 않음

3. ILIC 전 영역에 적용

  • JDBC(Repository) · 파일 IO · Excel(POI) · 모든 곳
  • Files.lines() 같은 Stream도 자원 — 반드시 닫을 것
  • Lock도 wrapper 만들어 try-with-resources 패턴 적용 가능

📚 다음으로...

Unit 7.2 — Checked vs Unchecked Exception

  • RuntimeException은 왜 unchecked인가?
  • Checked의 부담 vs Unchecked의 자유
  • 도메인 예외 설계 (DataAccessException, BusinessException)
  • ILIC의 예외 계층 구조

추가 학습 자료

  • JEP 213: Milling Project Coin (Java 9 try-with-resources 개선)
  • Effective Java 3rd Edition · Item 9: try-finally보다 try-with-resources를 사용하라
  • Java Language Specification §14.20.3
profile
Software Developer

0개의 댓글