반응형

try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다.!

class sampleCode() {

    static String firstLineOfFile(String path) throws IOException {
        BufferedReader br = new BufferedReader(new FileReader(path));
        try {
            return br.readLine();
        } finally {
            br.close();
        }

    }
}

자원이 둘 이상이면 try-finally 방식은 너무 지저분하다!

static void copy(String src, String dst) throws IOException{
    InputStream in = new FileInputStream(src);
    try{
        OutputStream out = new FileOutputStream(dst);
        try{
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while((n=in.read(buf))) >=0 )
                out.write(buf, 0, n);

        }finally{
            out.close();
        }

        }finally{
        in.close();
        }

}

물리적인 문제가 생긴다면 firstLineOfFile 메서드 안의 readLine 메서드가 예외를 던지고,
같은 이유로 close 메서드도 실패할 것이다.
이러한 상황이라면 두번째 예외가 첫 번째 예외를 완전히 삼겨 버림

try-with-resources - 자원을 회수하는 최선책

static String firstLineOfFile(String path) throws IOException {
    try(BufferedReader br = new BufferedReader( new FileReader(path))){
        return br.readLine();
    }
        }

복수의 자원을 처리하는 try-with-resources 짧고 매혹적이다.

static void copy(String src, String dst) throws IOException{
    try(InputStream in = new FileInputStream(src);
        OutputStream out = new FileOutputStream(dst)
        ){
        byte[] buf = new byte[BUFFER_SIZE];
        int n; 
        while((n = in.read(buf)) >= 0)
            out.write(buf, 0, n);
        }
}

readLine과 close 호출 약쪽에서 예외가 발생하면, close에서 발생한 예외는 숨겨지고
readLine에서 발생한 예외가 기록됨

try-with-resouces를 catch 절과 함께 쓰는 모습

static String firstLineOfFile(String path,String defaultVal){
        try(BufferedReader br=new BufferedReader(
        new FileReader(path))){
            return br.readLine();
        }catch(IOException e){
            return defaultVal;
        }
}

정리

꼭 회수해야 하는 자원을 다룰 때는 try~finll 말고, try-with-resources를 사용하자
코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용함

728x90
반응형

finalizer와 cleaner 사용을 피하라

finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다.

cleaner는 finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.

finalizer와 cleanser는 즉시 수행된다는 보장이 없다. 객체에 접근 할 수 없게 된 후 finalizer나 cleaner가 실행되기까지
얼마나 걸릴지 알 수 없다.
finalizer와 cleaner로는 제때 실행되어야 하는 작업은 절대 할 수 없다.

finalizer와 cleaner는 심각한 성능 문제도 동반한다.

finalize가 가비지 컬렉터의 효율을 떨어뜨리기 때문이다.

객체 생성을 막으려면 생성자에서 예외를 던지는 것만드로 충분하지만, finalize가 있다면 그렇지 않다.

final 클래스들은 그 누구도 하위 클래스를 만들 수 없으니 이 공격에서 안전함

final이 아닌 클래스를 finalizer 공격으로부터 방어하려면 아무 일도 하지 않는 finalize 메서드를 만들고 final로 선언하자.

AutoCloaseable을 구현해주고, 클라이언트에서 인스턴스를 다 쓰고 나면 close 메서드를 호출하면 됨

cleaner와 finalizer를 적절히 활용하는 두 번째 예는 네이티브 피어(native peer)와 연결된 객체에서다.

cleaner를 안정망으로 활용하는 AutoCloseable 클래스

public class Room implements AutoCloseable {
    private static final Cleaner cleaner= Cleaner.create();

    private static class State implements Runnable {
        int numJunkPiles;

        State(int numJunkPiles){
            this.numJunkPiles = numJunkPiles;
        }

        @Override public void run(){
            System.out.println("방 청소");
            numJunkPiles = 0;
        }
    }

    private final State state;

    private final Cleaner.Cleanable cleanable;

    public Room(int numJunkPiles){
        state = new State(numJunkPiles);
        cleanable = cleaner.register(this, state);
    }

    @Override public void close(){
        cleanable.clean();
    }

}

모든 Room 생성을 try-with-resources 블록으로 감싼다면 자동 청소는 전혀 필요하지 않음

public class Adult{
    public static void main(String[] args) {
        try(Room myRoom = new Room(7)){
            System.out.println("안녕~");
        }
    }
}

방 청소를 하지 않는 프로그램

public class Teenager {
    public static void main(String[] args) {
        new Room(99);
        System.out.println("아무렴");
    }
}

정리

cleaner는 안정망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자,
물론 이런 경우라도 불확실성과 성능 저하에 주의해야 한다.

728x90
반응형

정적 유틸리티를 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다

public class SpellChecker {
    private static final Lexicon dictionary =  ...;

    private SpellChecker() {} // 객체 생성 방지

    public static boolean isValid(String word) { ... }
    public static List<String> suggestions(String type) { ... }
}

싱글턴을 잘못 사용한 예 - 유연하지 않고 테스트하기 어렵다.

public class SpellChecker {
    private final Lexicon dictionary = ...;

    private SpellChecker(){}
    public static SpellChecker INSTANCE = new SpellChecker(...);

    public boolean isValid(String word) { ... }
    public List<String> suggestions(String typo) { ... }
}

두 방식 모두 사전 하나로 이 모든 쓰임에 대응하기 어렵다.

- 사용하는 자원에 따라 동작이 달라지는 클래스에 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않음

- 클래스가 여러 자원 인스턴스를 지원해야하며 클라이언트가 원하는 자원(dictionary)을 사용해야 함

- 인스턴스가 생성할 때 생성자에 필요한 자원을 넘겨주는 방식

- 의존 객체 주입은 유연성과 테스트 용이성을 높여준다.

public class SpellChecker {
    private final Lexicon dictionary;

    public SpellChecker(Lexicon dictionary){
        this.dictionary = Objects.requireNonNull(dictionary);
    }

    public boolean isValid(String word){}
    public List<String> suggestions(String typo){}
}

의존 객체 주입 패턴

생성자, 정적 팩터리, 빌더 모두에 똑같이 응용할 수 있음

팩터리란 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체

팩터리 메서드 패턴이 가장 쓸만한 변형

자바 8에서 소개한 Supplier 인터페이스가 팩터리를 표현한 완벽한 예

이 방식은 사용해 클라이언트는 자신이 명시한 타입의 하위 타입이라면 무서이든 생성할 수 있는 팩터리를 넘길 수 있음

Mosaic create(Supplier<? extends Title> titleFactory) {}

의존 객체 주입이 유연성과 테스트 용이성을 개선해주긴 하지만, 의존성이 수천 개나 되는 큰 프로젝트에서는 코드를 어지럽게 만듬

스프링 같은 의존 객체 주입 프레임워크를 사용하면 이런 어질러짐을 해소할 수 있음

정리

클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는 것이 좋음
의존 객체 주입이라는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 기막히게 개선해줌

추가 스프링에서 의존성 주입 방법 3가지

1. Field 주입

@Service 
public class FieldInjectionService {

	@Autowired 
    private ExampleService exampleService; 
    
    public void getBuLogic(){ 
    	exampleService.businessLogic(); 
    } 
    
}

 

2. Setter 주입

@Service 
public class SetterInjectionService{

    private ExampleService exampleService; 
    
    @Autowired 
    public void setExampleService(ExampleService exampleService){ 
    	this.exampleService = exampleService; 
    } 
    
    public void getBuLogic(){ 
    	exampleService.businessLogic(); 
    } 
    
}

 

3. Constructor 주입

@Service 
public class ConstructorInjectionService { 

    private final ExampleService exampleService; 
    
    public void ConstructorInjectionService(final ExampleService exampleService) { 
    	this.exampleService = exampleService; 
    } 
    
    public void getBuLogic(){ 
    	exampleService.businessLogic(); 
    } 
    
 }

장점

  1. 불변 객체를 만들 수 있음
  2. 순환참조를 막을 수 있음
  3. NPE 방지

Lombok 라이브러리를 사용하여 생성자 자동생성 어노테이션 이용한 개선

@Service
@RequredArgsConstructor
public class ConstructorInjectionService{

    private final ExampleService exampleService;

    public void getBuLogic(){
        exampleService.businessLogic();
    }
}

 

 

728x90

+ Recent posts