새소식

독서/이펙티브 자바 3판

Ch01 객체 생성과 파괴

  • -

step1 : 생성자 대신 정적 팩터리 메서드를 고려하자

클래스는 public 생성자 대신 정적 팩토리 메서드를 제공할 수 있다.

 

 

장점 5가지

 

1st 이름을 가질 수 있다.

 

2nd 호출 될 때마다 인스턴스를 새로 생성하지 않아도 된다. 

-> 이에 대한 예시는 enum이 대표적이다. 사용되는 값들의 개수가 정해져 있으면 해당 값을 미리 생성해놓고 캐싱할 수 있는 구조로 만들 수 있다. 즉 정적 팩터리 메서드와 캐싱구조를 함께 사용하면 매번 새롭게 만들어 줄 필요가 없다.

 

3rd 반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있다.

-> 상속을 사용할 때, 확인할 수 있다. 이때 정적 팩터리 메서드가반환값을 반환할 때, 상황에 따라 하위 클래스 타입의 객체를 반환할 수 있다는 것이다.

 

4th 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.

-> 만약 특정 인터페이스들을 상속받은 구현체들이 잇다면 객체 생성시 상황에 따라서 유동적으로 해당하는 구현체 타입으로 반환한다고 생각하면 쉽다.

 

5th 정적 팩터리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.

 

 

 

단점 3가지

 

1st 상속을 하려면 public이나 protected 생성자가 필요하므로 정적 팩토리 메서드만 제공된다면 상속이 불가능하다.

 

2nd 정적 팩토리 메서드를 다른 개발자들이 찾기 어렵다.

 

 

정적 팩토리 메서드 명명방식

 

from : 하나의 매개변수를 받아 해당 타입의 인스턴스를 반환하는 형변환 메서드

of : 여러 매개변수를 받아 적합한 타입의 인스턴스를 반환하는 집계 메서드

value of : from 과 of의 더 자세한 버전

instance 혹은 getInstance : 매개변수로 명시한 인스턴스를 반환하지만, 같은 인스턴스임을 보장하지 않음

create 혹은 newInstance : 위와 같으나 매번 새로운 인스턴스를 생성해 반환함을 보장

 

정리 

정적 팩터리 메서드와 public 생성자는 각자의 쓰임새가 있으니 상대적인 장단점을 이해하고 사용하는 것이 좋다. 그렇다고 하더라도 정적 팩터리를 사용하는 게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하던 습관에 주의하자.

 

 

 

step2 : 생성자에  매개변수가 많다면 빌더를 고려하라

정적 팩터리와 생성자는 똑같은 제약이 있는데 선택적 매개변수가 많을 때 적절히 대응하기 어렵다는 것이다.

 

 

1st 점층적 생성자 패턴도 쓸 수 있지만, 매개변수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다는 단점.

 

2nd 자바빈즈 패턴에서는 객체 하나를 만들려면 메서드를 여러개 호출해야 하고, 객체가 완성되기 전까지 일관성이 무너진 상태에 놓임.

-> 때문에 클래스를 불변으로 만들 수 없으며 프로그래머가 추가작업을 해줘야한다.

 

위의 대안으로 점층적 생성자 패턴의 안정성과 자바 빈즈 패턴의 가독성을 겸비한 빌더 패턴이 있다. (매개변수가 4개 이상이면 고려하자)

-> 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰이기 좋은데, 잘못된 매개변수를 일찍 발견하려면 어떤 매개변수가 잘못되어있는지 알려주는 메세지를 담아 IllegalArgumentException을 던지면 된다.

 

정리 

생성자나 정적 패터리가 처리해야 할 매개변수가 많다면 빌더 패턴을 선택하는게 더 낫다. 매개변수 중 다수가 필수가 아니거나 같은 타입이면 특히 더 그렇다. 빌더는 점층적 생성자보다 클라이언트 코드를 읽고 쓰기가 훨씬 간결하고, 자바빈즈 보다 훨씬 안전하다.

 

 

 

step3 : private 생성자나 열거 타입으로 싱글턴임을 보증하라

싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말하는데, 전형적인 예로 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트를 들 수 있다. 단점으로 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트가 테스트하기 어려울 수 있는데, 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 인스턴스를 가짜 구현으로 대체할 수 없기 때문이다.

 

 

싱글턴을 만드는 방법 2가지

 

1st 생성자는 private으로 감춰두고, 유일한 인스턴스에 접근할 수 있는 수단으로 public static 멤버를 하나 마련해두는 방법

장점으로는

 

-> 해당 클래스가 싱글턴임이 API에 명백히 드러난다.

-> 간결하다는 점이다.

 

2nd 정적 패터리 메서드를 public static 멤버로 제공하는 방법

장점으로는

 

-> API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다는 점이다.

-> 원하는만큼 정적 팩터리를 제너릭 싱글턴 팩터리로 만들 수 있다는 점이다.

 

위 2가지 방법으로 만든 싱글턴 클래스를 직렬화하려면 모든 인스턴스 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공해야 한다. 이렇게 하지 않으면 직렬화된 인스턴스를 역직렬화할 때마다 새로운 인스턴스가 만들어진다.

 

위의 방법을 대체하는 방안으로 원소가 하나인 열거 타입을 선언하는 방법이 있다. public 필드 방식과 비슷하지만 추가 노력없이 직렬화 할 수 있고, 리플렉션 공격에서도 제 2의 인스턴스가 생기는 일을 막아준다. 대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다. 그러나 싱글턴이 Enum 이외의 클래스를 상속해야 한다면 이 방법은 사용할 수 없다.

 

 

 

https://www.youtube.com/watch?v=3iypR-1Glm0

자바 리플렉션 관한 참고 링크 : https://hudi.blog/java-reflection/

 

자바 리플렉션 (Reflection) 기초

리플렉션 (Reflection) JVM은 클래스 정보를 클래스 로더를 통해 읽어와서 해당 정보를 JVM 메모리에 저장한다. 그렇게 저장된 클래스에 대한 정보가 마치 거울에 투영된 모습과 닮아있어, 리플렉션

hudi.blog

 

직렬화와 역직렬화에 관한 참고 링크 : https://steady-coding.tistory.com/576

 

[Java] 직렬화와 역직렬화

java-study에서 스터디를 진행하고 있습니다. 데이터 직렬화와 역직렬화 데이터 직렬화 메모리를 디스크에 저장하거나, 네트워크 통신에 사용하기 위한 형식으로 변환하는 것이다. 데이터 역직렬

steady-coding.tistory.com

step4 : 인스턴스화를 막으려거든 private 생성자를 사용하라

 

step5 : 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

 

사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다. 클래스가 여러 자원 인스턴스를 지원해야 하며, 클라이언트가 원하는 자원을 사용해야 한다. 이 조건을 만족하는 간단한 패턴이 있는데, 바로 인스턴스를 생성할 때 생성자에 필요한 자원을 넘겨주는 방식이다. 

 

 

정리

클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 싱글턴과 정적 유틸리티 클래스는 사용하지 않는것이 좋다. 이 자원들을 클래스가 직접 만들게 해서도 안 된다. 대신 필요한 자원을 (혹은 그 자원을 만들어주는 팩터리를) 생성자에 (혹은 정적 팩터리나 빌더로) 넘겨주자. 의존 객체 주입이라 하는 이 기법은 클래스의 유연성, 재사용성, 테스트 용이성을 기막히게 개선해준다.

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

step6 : 불필요한 객체 생성을 피하라

 

생성자 대신 팩터리 메서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다.

 

Stateless Object(무상태 객체) : 인스턴스 변수가 없는 객체

Immutable Object (불변 객체) : 상태를 바꿀 수 없는 객체

 

 

 

step7 : 불필요한 객체 생성을 피하라

 

해당 참조를 다 쓴 후 null 처리하면 좋다. 객체 참조를 null 처리하는 일은 예외적인 경우여야 하는데, 가장 좋은 방법은 그 참조를 담은 변수를 유효 범위 밖으로 밀어내는 것이다. 메모리 누수는 겉으로 잘 드러나지 않아 시스템에 수년감 잠복하는 사례도 있다. 이런 누수는 철저한 코드 리뷰나 힙 프로파일러 같읕 디버깅 도구를 동원해야만 발견되기도 하기 때문에, 예방법을 익혀두는 것이 중요하다.

 

 

 

step8 : finalizer와 cleaner 사용을 피하라

 

자바는 2가지 객체 소멸자를 제공하는데 finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다. cleaner는 finalizer보다 덜 위험하지만, 여전히 예측할 수 없고, 느리며, 일반적으로 불필요하다. 위 2개로 제때 실행되어야 하는 작업은 절대 할 수 없다. 따라서 상태를 영구적으로 수정하는 작업에서는 절대 finalizer와 cleaner에 의존하지 말자. cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자. 물론 이런 경우라도 불확실성과 성능 저하에 주의해야 한다.

 

 

 

step9 : try-finally 보다는 try-with-resources를 사용하라

 

꼭 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자. 예외는 없다. 코드는 더 짧고 분명해지며, 만들어지는 예외 정보도 훨씬 유용하다. try-finally로 작성하면 실용적이지 못할 만큼 코드가 지저분해지는 경우라도, try-with-resources로는 정확하고 쉽게 자원을 회수할 수 있다. 또한 catch 절과 함께 쓰일 수 있어 더 유용하다.

'독서 > 이펙티브 자바 3판' 카테고리의 다른 글

CH6 람다와 스트림  (0) 2023.05.15
CH05 열거 타입과 애너테이션  (0) 2023.05.05
CH04 제네릭  (0) 2023.05.02
Ch02 모든 객체의 공통 메서드  (0) 2023.04.25
Ch03 클래스와 인터페이스  (0) 2023.04.25
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.