try-with-resource는 Java 7에서 도입된 기능

리소스를 명시적으로 닫을 필요 없이 자동으로 닫아주는 기능을 제공

 

1. Try-with-Resource란?

java.lang.AutoCloseable 인터페이스를 구현한 리소스에 대해 사용할 수 있는 문법
try 블록이 끝나면 자동으로 해당 리소스의 close() 메서드를 호출
리소스를 명시적으로 닫지 않아도 되므로 코드가 간결하고 안전하며, 예외 처리를 보다 쉽게 관리가능

 

2. 기존 방식과의 비교

2-1. Java7 이전의 try-catch-finally

BufferedReader br = null;
try {
    br = new BufferedReader(new FileReader("example.txt"));
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (br != null) {
        try {
            br.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 

2-2. Try-with-Resource 방식

try (ResourceType resource = new ResourceType()) {
    // 리소스 사용
} catch (ExceptionType e) {
    // 예외 처리
}

 

3. AutoCloseable과 Closeable의 차이

AutoCloseable:
Java 7에서 도입된 인터페이스, try-with-resource에서 사용
close() 메서드는 checked exception 또는 unchecked exception

Closeable:
Java 5에서 도입된 인터페이스, java.io 패키지의 클래스에서 주로 사용
close() 메서드는 반드시 IOException


AutoCloseable은 Closeable보다 더 일반화된 인터페이스, 
예외 처리 방식에 유연성을 제공

 

5. 중첩 리소스 처리

try-with-resource를 사용하면 여러 리소스를 동시에 처리가능

try (
    BufferedReader br = new BufferedReader(new FileReader("input.txt"));
    BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))
) {
    String line;
    while ((line = br.readLine()) != null) {
        bw.write(line);
        bw.newLine();
    }
} catch (IOException e) {
    e.printStackTrace();
}

리소스를 여러 개 선언하면, 선언된 순서와 반대로 닫힌다

 

6. 장점

1. 리소스 누수 방지:
명시적으로 close()를 호출할 필요가 없으므로 실수로 리소스를 닫지 않는 문제를 방지

2. 가독성 향상:
코드가 간결하고 명확

3. 안전한 예외 처리:
리소스를 닫는 과정에서 발생하는 예외를 별도로 처리 X

4. 모든 에러에 대한 스택 트레이스 가능

 

7. 주의사항

1. try-with-resource는 선언된 리소스가 반드시 AutoCloseable을 구현
2. 리소스를 블록 외부에서 사용하려면 try-with-resource를 사용 불가
3. 예외가 발생하면 close() 메서드에서 던진 예외와 원래의 예외가 동시에 발생할 수 있으므로, 
getSuppressed() 메서드로 추가 정보를 확인 필요

try (MyResource resource = new MyResource()) {
    throw new RuntimeException("예외 발생");
} catch (Exception e) {
    for (Throwable suppressed : e.getSuppressed()) {
        System.out.println("Suppressed: " + suppressed);
    }
}

 

 

참고 : 

https://mangkyu.tistory.com/217

https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%98%88%EC%99%B8-%EC%B2%98%EB%A6%AC-Try-With-Resource-%EB%AC%B8%EB%B2%95

'Java' 카테고리의 다른 글

불변 객체(Immutable Object)  (0) 2025.01.18
Record Class (불변 데이터 클래스)  (2) 2025.01.17
제네릭(Generic)  (0) 2025.01.17

불변 객체(Immutable Object) : 한 번 생성되면 내부 상태(필드 값)를 변경할 수 없는 객체

 즉, 객체가 생성된 이후에는 해당 객체의 상태를 변경하는 어떤 메서드나 동작도 존재하지 않음

 

대표적인 불변 객체의 예:

String 클래스

Integer, Long, Double 등 기본형 래퍼 클래스

 

불변 객체의 장점

1. 내부 상태 변경 불가
	객체가 생성된 이후에는 그 값이 변경되지 않음
	모든 필드는 final로 선언되어야 하며, 생성자에서만 초기화 가능
	
2. 가비지 컬렉션의 성능을 높일 수 있음 
	불변객체를 이용하면 GC의 스캔빈도와 범위가 줄게되어 GC의 성능에 도움이 됨
    
3. equals와 hashCode의 안정성 보장
	객체의 상태가 변하지 않기 때문에, 해시 기반 컬렉션(HashMap, HashSet 등)에서 안전하게 사용가능
    
4. 스레드 안전성(Thread-Safety) 
	불변 객체는 항상 동일한 값을 보장하므로 동기화를 신경쓸 필요가 없음
    병렬 프로그래밍에 유용함
* 멀티 스레드 환경에서는 공유 자원에 대해 서로 변경하면 값이 덮어씌워지는 문제가 발생

 

불변 객체를 선호하고 중요하게 여기는 이유

1.	스레드 안전성
	불변 객체는 여러 스레드에서 동시에 사용되더라도 상태가 변경되지 않으므로 
    동기화 코드 없이 안전하게 사용할 수 있다
	ex. String 클래스는 스레드 간 안전하게 공유 가능
2.	안정성과 신뢰성
	객체의 상태가 변하지 않으므로 한 번 설정된 값은 변하지 않는다
	디버깅과 유지보수가 용이
3.	캡슐화 강화
	불변 객체는 내부 상태가 외부에 노출되지 않도록 설계되기 때문에 캡슐화 원칙을 강화
	불변성을 유지하기 위해 외부에서 내부 필드에 접근할 수 없도록 방지
4.	캐싱과 재사용 가능
	불변 객체는 상태가 변하지 않으므로 동일한 상태를 가진 객체를 캐싱하여 재사용 가능
	ex. Integer 클래스의 valueOf 메서드는 -128부터 127 사이의 값을 캐싱하여 성능을 최적화
5.	컬렉션에서 안정적 동작
	불변 객체는 HashMap, HashSet 등의 컬렉션에서 안정적으로 동작
	해시 기반 컬렉션에서는 객체의 상태가 변경되면 해시 충돌이 발생할 수 있는데, 불변 객체는 이런 문제를 방지

 

불변 객체의 단점

1.	초기화 시 성능 비용
	불변 객체를 새로 생성하려면 기존 객체를 복사하여 새로운 객체를 생성해야 하므로 메모리와 CPU 비용이 증가할 수 있음
	다만, 작은 크기의 객체나 캐싱 기법을 통해 이러한 비용을 줄일 수 있음
2.	메모리 사용량 증가
	상태가 변할 때마다 새로운 객체를 생성하므로, 가변 객체보다 더 많은 메모리를 소비할 수 있음
	* 주로 대량의 데이터를 처리할 때 문제

 

불변 객체 설계 방법

1. 모든 필드를 final로 선언:

private final String name;
private final int age;

2. 클래스를 final로 선언 (상속 방지):

public final class Person {
    // 필드 및 생성자
}

3. 생성자를 통해 필드를 초기화:

public Person(String name, int age) {
    this.name = name;
    this.age = age;
}

4. 필드에 대한 getter는 원시값이나 불변 객체만 반환:

public String getName() {
    return name;
}


5. 참조 타입 필드는 복사본을 반환:

private final List<String> hobbies;

public Person(String name, int age, List<String> hobbies) {
    this.name = name;
    this.age = age;
    this.hobbies = new ArrayList<>(hobbies); // 방어적 복사
}

public List<String> getHobbies() {
    return Collections.unmodifiableList(hobbies); // 불변 리스트 반환
}

불변 객체의 활용 예시
1. 불변 객체 클래스

public final class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + '}';
    }
}


2. 사용 예

public class Main {
    public static void main(String[] args) {
        Person person = new Person("Alice", 25);

        System.out.println(person); // 출력: Person{name='Alice', age=25}

        // 상태 변경 불가
        // person.setName("Bob"); // 컴파일 에러 발생
    }
}

 

 

참고 : 

https://devoong2.tistory.com/entry/Java-%EB%B6%88%EB%B3%80-%EA%B0%9D%EC%B2%B4Immutable-Object-%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90

 

'Java' 카테고리의 다른 글

Try With Resource  (2) 2025.01.22
Record Class (불변 데이터 클래스)  (2) 2025.01.17
제네릭(Generic)  (0) 2025.01.17

* JDK14에서 preview로 등장하여 JDK16에서 정식 스펙으로 포함

불변(immutable) 객체를 쉽게 생성할 수 있도록 하는 유형의 클래스
	- 필드가 자동으로 final로 선언되어 불변 객체를 생성 (필드 캡슐화)
	- class가 final로 선언, final 클래스(상속불가)
	- 클래스의 필드 선언, 생성자, getter, equals, hashCode, toString이 모두 자동으로 생성 
	- 컴파일 타임에 컴파일러가 코드를 추가
	- getter를 사용할 때, getFieldName()이 아니라 fieldName()을 사용
	- Record 클래스가 제공해주는 메소드들은 재정의가 가능

1. 생성자는 모든 field를 포함
2. toString()도 모든 field를 포함
3. equals(), hashCode() 메서드는 invokedynamic based mechanism을 사용
4. getter는 field이름과 유사한 이름으로 생성 ex) id(), email()..
5. 기본적으로 java.lang.Record class를 상속받기 때문에 다른 class를 상속받을 수 없음
6. class가 final이기 때문에 다른 subclass를 생성할 수 없음
7. 모든 field는 불변이기 때문에 setter는 제공하지 않음
8. DTO(data transfer object)나 domain model class에 사용

Q. 레코드(record)를 JPA의 Entity 클래스로 사용할 수 없을까요?
레코드는 바이트코드로 본 것처럼 final 클래스(상속불가)이고, abstract로 선언할 수 없습니다.
따라서 레코드를 Entity 클래스에 사용할 수 없는데요, 그 이유는 JPA의 지연로딩에 있습니다.

지연 로딩 방식을 사용할 때, JPA는 엔티티 객체의 프록시 객체를 생성합니다. 
프록시 객체는 원본 객체를 상속하여 생성된 확장 클래스입니다. 
하지만 레코드는 상속이 불가능하므로 엔티티로 사용할 수 없습니다.

 

참고

 

예시 ResponseMessage<T>.java

package org.example.exception;

import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

@Getter
@Builder
@RequiredArgsConstructor
public class ResponseMessage<T> {
    private final HttpStatus status;
    private final String message;
    private final T data;

    public static ResponseEntity<ResponseMessage> error(CustomException e) {
        return ResponseEntity
                .status(e.getCustomErrorCode().getHttpStatus())
                .body(ResponseMessage.builder()
                        .status(e.getCustomErrorCode().getHttpStatus())
                        .message(e.getCustomErrorCode().getMessage())
                        .build());
    }

    public static <T> ResponseEntity<ResponseMessage> success(HttpStatus status, String message, T data) {
        return ResponseEntity
                .status(status)
                .body(ResponseMessage.builder()
                        .status(status)
                        .message(message)
                        .data(data)
                        .build());
    }
}

 

✏️ Class to Record 

package org.example.exception;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;

public record ResponseMessage <T> (HttpStatus status, String message, T data) {

    public static ResponseEntity<ResponseMessage> error(CustomException e) {
        return ResponseEntity
                .status(e.getCustomErrorCode().getHttpStatus())
                .body(new ResponseMessage<>(
                        e.getCustomErrorCode().getHttpStatus(),
                        e.getCustomErrorCode().getMessage(),
                        null
                ));
    }

    public static <T> ResponseEntity<ResponseMessage> success(HttpStatus status, String message, T data) {
        return ResponseEntity
                .status(status)
                .body(new ResponseMessage<>(
                        status,
                        message,
                        data
                ));
    }
}

'Java' 카테고리의 다른 글

Try With Resource  (2) 2025.01.22
불변 객체(Immutable Object)  (0) 2025.01.18
제네릭(Generic)  (0) 2025.01.17

제네릭이란 ? 

자바(Java)와 같은 프로그래밍 언어에서 코드의 재사용성과 타입 안전성을 높이기 위해 사용하는 템플릿

데이터 형식에 의존하지 않고, 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 한다

컴파일 시점에 데이터 타입을 명확히 지정할 수 있어, 코드의 유연성안전성을 동시에 확보

 

 

제네릭의 주요 특징 

1. 타입 안정성(Type Safety)

제네릭을 사용하면 컴파일 시점에 타입이 결정되므로, 잘못된 타입 사용으로 인한 오류를 방지

예: List<String>은 문자열만 저장 가능하며, 컴파일 시점에 다른 타입을 넣으려 하면 에러가 발생

2. 코드 재사용성

제네릭을 사용하면 다양한 데이터 타입에 대해 하나의 코드로 처리 가능 

예: List<T>는 문자열, 숫자, 사용자 정의 객체 등 어떤 타입이든 처리

3. 형변환(Downcasting) 제거

제네릭을 사용하면 형변환(casting)이 필요 없어 코드가 간결하고 안전

예: List<String>에서 값을 꺼낼 때는 문자열(String)로 자동 처리

 

제네릭의 사용법

1. 클래스에서 제네릭 사용

// 제네릭 클래스를 정의
public class Box<T> { 
    private T value;

    public void setValue(T value) { 
        this.value = value; 
    }
    
    public T getValue() { 
        return value; 
    }
}

// 사용하는 예제
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
System.out.println(stringBox.getValue()); // 출력: Hello

Box<Integer> intBox = new Box<>();
intBox.setValue(123);
System.out.println(intBox.getValue()); // 출력: 123

2. 메서드에서 제네릭 사용

// 제네릭 메서드
public static <T> void printArray(T[] array) {
    for (T element : array) {
        System.out.println(element);
    }
}

// 사용하는 예제
String[] stringArray = { "A", "B", "C" };
printArray(stringArray);

Integer[] intArray = { 1, 2, 3 };
printArray(intArray);

3. 제네릭 인터페이스

// 제네릭 인터페이스 정의
public interface Repository<T> {
    void save(T entity);
    T findById(int id);
}

// 구현 클래스
public class UserRepository implements Repository<User> {
    @Override
    public void save(User user) {
        System.out.println("Saving user: " + user.getName());
    }
    
    @Override
    public User findById(int id) {
        return new User(id, "Sample User");
    }
}

 

참고: 

https://inpa.tistory.com/entry/JAVA-☕-제네릭Generics-개념-문법-정복하기

https://st-lab.tistory.com/153

'Java' 카테고리의 다른 글

Try With Resource  (2) 2025.01.22
불변 객체(Immutable Object)  (0) 2025.01.18
Record Class (불변 데이터 클래스)  (2) 2025.01.17