본문 바로가기

STUDY REVIEW/이펙티브 자바 독서스터디

[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 2장 객체 생성과 파괴

2장 객체 생성과 파괴
아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라
아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라
아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라
아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라
아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라
아이템 6. 불필요한 객체 생성을 피하라
아이템 7. 다 쓴 객체 참조를 해제하라
아이템 8. finalizer와 cleaner 사용을 피하라
아이템 9. try-finally보다는 try-with-resources를 사용하라

읽고 느낀 점

 

주간 회의를 하면서 생성자 대신 정적 팩터리 메서드에 대해 이야기가 나왔다. 이야기를 들어보니 한번 만들어놓으면 여러곳에서 중복 활용을 할 수 있어 생산성이 좋아보였다. 그래서 나도 정적 팩터리 메서드를 이용하려 노력했다. 마침 주제에 맞는 개발을 진행중이라 코드가 좀 더 깔끔하게 나왔다. 다음 장이 기대된다.


아이템 1. 생성자 대신 정적 팩터리 메서드를 고려하라

정적 팩터리 메서드란

public static Boolean valueOf(boolean b){
	return b ? Boolean.TRUE : Boolean.FALSE;
}

 

정적 팩터리 메서드 장점과 단점

장점 1. 이름을 가질 수 있다.

public class Car {

    private String color;
    private int power;

    private Car(String color, int power) {
        this.color= color;
        this.power = power;
    }

    public static Car createRedCar(int power) {
        return new Car("red", power);
    }

    public static Car createBlueCar(int power) {
        return new Car("blue", power);
    }
}
/**
** 외부에서
** Car car = new Car("red", 3); 이렇게 생성자를 직접 호출하는 것보다
** Car car = Car.createRedCar(3); 이렇게 호출하는 방식이 훨씬 의미를 파악하기 쉽다.
**/

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

/**
** 불필요한 객체 생성을 방지한다.
** 불변 클래스immutable class의 경우,
** 인스턴스를 미리 만들어 두거나,
** 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다.
**/

public class Robot {
    private String name;
    
    private static final Robot alphaRobot = new Robot("alpha");
    private static final Robot betaRobot = new Robot("beta");

    public Robot(String name) {
        this.name = name;
    }

    public static Robot getInstanceAlphaRobot() {
        return alphaRobot;
    }

    public static Robot getInstanceBetaRobot() {
        return betaRobot;
    }
}

Robot alphaRobot = Robot.getInstanceAlphaRobot();

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

/** 
** 반환할 객체의 타입을 자유롭게 선택할 수 있는 '엄청난 유연성'을 선물한다.
**/
public List<String> foo1() {
	return new ArrayList<String>();
}

public List<String> foo2() {
	return new Vector<String>();
}

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

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

 

단점 1. 상속을 하려면 public이나 protected 생성자가 필요하니 정적 팩토리 메소드만 제공하면 하위 클래스를 만들 수 없다.

- 상속이 불가능한 클래스가 된다. 상속보다 컴포지션을 사용하도록 유도하고 불변 타입으로 만들려면 이 제약을 지켜야 한다는 점에서 장점이 될 수도 있다.

단점 2. 정적 팩토리 메소드는 프로그래머가 찾기 어렵다. 

- API 문서를 잘 써두고, 메소드 이름도 널리 알려진 규약을 따라 지어야 한다.


아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라

점층적 생성자 패턴

  • 필수 매개변수만 받는 생성자, 필수 매개변수와 선택매개변수 1개를 받는 생성자, 필수 매개변수와 선택 매개변수 2개를 받는 생성자. 원하는 매개변수를 모두 포함한 생성자 중 가장 짧은 것을 골라 호출.
  • 이 방식을 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다.

자바빈즈 패턴

  • 매개변수가 없는 생성자를 호출해 인스턴스를 만들고, Setter 를 이용해 원하는 iv의 값을 할당해준다
  • 객체하나를 완성시키기 위해선 메서드를 여러개 호출해야하고, 완전한 값을 세팅하기 전에는 일관성이 무너진다.
    • 클래스를 불변(아이템 17)으로 만들 수 없으며, 스레드 안전성을 위해선 추가작업이 필요하다.

빌더 패턴

  • 필수 매개변수만으로 생성자를 호출해 빌더객체를 얻은 뒤, 빌더가 제공하는 일종의 Setter 메소드를 이용해 원하는 매개변수를 설정한다. 마지막으로 매개변수가 없는 build 메서드를 호출해 객체를 얻는다.
  • 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다.
  • 빌더는 보통 생성할 클래스 안에 정적 멤버클래스로 만들어둔다

아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라

싱글턴 : 인스턴스를 오직 하나만 생성할 수 있는 클래스

 

싱글턴을 만드는 방법 

1. private 생성자와 public static 필드

public class test {
    public static final test INSTANCE = new test();
    private test() { }

    public void sleep() { }
    ...
}

test test1 = test.INSTANCE;
test test2 = test.INSTANCE;

System.out.println(test1 == test2); //true
System.out.println(test1.equals(test2)); //true

2. private 생성자와 public static 정적 팩토리 메소드

public class test {
    private static final test INSTANCE = new test();
    private test() { }

    public static test getInstance() {
        return INSTANCE;
    }
}

3. 원소가 하나인 Enum 타입 선언

public enum test {
    INSTANCE;
    
    public void sleep() { ... }
}

아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라

정적 멤버만 있는 클래스가 존재한다치자. 이 클래스는 인스턴스로 만들어 쓰려고 설계한 것이 아닐 것이다. Ex) java.lang.Math , java.util.Arrays 

 

클래스에 생성자를 명시적으로 써주지아니하면 컴파일러가 기본생성자를 추가해준다. 사용자는 이 생성자를 보고 그 클래스의 인스턴스를 생성할 수도 있다. 그렇기 때문에 인스턴스화를 막으려면 private 생성자를 명시적으로 추가해주자.


아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라

/**
** 의존 객체 주입이란 DI, Dependency Injection
** 인스턴스 생성 시 생성자에게 필요한 자원을 넘겨주는 방식
**/

public class PetOwner {
    private final PetType petType;

    public PetOwner(PetType petType) {
        this.petType = petType;
    }
}

public class Dog extends PetType {
		...
}

public class Cat extends PetType {
		...
}

PetOwner dogOwner = new PetOwner(dog);

정적 유틸리티(아이템 4)나 싱글턴(아이템 3)과 달리 위 방식은 PetType을 상속받는 객체라면 어느 것이든 PetOwner 생성자를 통해 주입이 가능하다.

이처럼 인스턴스를 생성할 때 생성자에게 필요한 자원을 넘겨주는 방식은 의존 객체 주입의 한 형태이다.

 

이 패턴의 쓸만한 변형? — 생성자에 자원 팩토리를 넘겨주는 방식!

 

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

즉, 팩토리 메소드 패턴을 구현한 것이다. cf. Supplier 인터페이스는 팩토리를 표현한 완벽한 예시이다.

Supplier를 입력으로 받는 메소드는 명시한 타입(Tile)의 하위 타입이라면 무엇이든 생성할 수 있는 팩토리를 넘길 수 있다.


아이템 6. 불필요한 객체 생성을 피하라

박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
// 예시1 - 문자열 생성
String myId1 = "MadPlay";
String myId2 = "MadPlay";
System.out.println(myId1 == myId2); // true

// 예시2 - 생성자로 문자열 생성 
String myId3 = new String("MadPlay");
String myId4 = new String("MadPlay");
System.out.println(myId3 == myId4); // false
 
정적 팩터리 메서드에서도 불필요한 객체 생성을 줄일 수 있다. 예를 들어 Boolean(String) 생성자 대신에 Boolean.valueOf(String) 팩터리 메서드를 사용하는 것이 좋다. (자바 9에서 생성자는 Deprecated 되었다.)
생성 비용이 비싼 객체를 재사용하는 것도 중요하다. Pattern 인스턴스는 한 번 사용되고 바로 가비지가 된다.
// AS-IS: 내부에서 생성되는 Pattern 인스턴스는 사용 후 가비지가 된다.
static boolean isTwoAndFourLengthKoreanWord(String s) {
    // 한글 2~4글자 단어 정규식
    return s.matches("[가-힣]{2,4}");
}

// TO-BE: Pattern 인스턴스를 만들어두고 메서드가 호출될 때마다 재사용한다.
private static final Pattern KOREAN_WORD = Pattern.compile("[가-힣]{2,4}");
static boolean isTwoAndFourLengthKoreanWord(String s) {
    return KOREAN_WORD.matcher(s).matches();
}
 
실제 작업은 뒷단 객체에 위임하고 자신은 제 2의 인터페이스 역할을 해주는 객체인 어댑터의 경우도 마찬가지이다.
뒷단 객체 외에는 관리할 상태가 없기때문에 뒷단 객체 하나당 하나의 어댑터만 있으면 된다.
예를 들어 Map 인터페이스의 keySet 메서드는 호출할 때마다 새로운 Set 인스턴스를 반환하지 않는다.
Map<String, String> phoneBook = new HashMap<>();
phoneBook.put("김탱", "010-1234-1234");
phoneBook.put("MadPlay", "010-4321-4321");
Set<String> keySet1 = phoneBook.keySet();
Set<String> keySet2 = phoneBook.keySet();


System.out.println(keySet1 == keySet2); // true
System.out.println(keySet1.size() == keySet2.size()); // true
keySet1.remove("MadPlay");
System.out.println(phoneBook.size()); // 1
 
한변 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해주는 오토 박싱(auto boxing)을 통해서도 불필요한 객체가 만들어진다.
// long이 아닌 Long으로 선언되었다.
// 불필요한 Long 인스턴스가 만들어질 것이다. (변수 i가 sum 변수에 더해질 때마다)
Long sum = 0L;
for (long i = 0; i <= Integer.MAX_VALUE; i++) {
    sum += i;
}

아이템 7. 다 쓴 객체 참조를 해제하라

누수 예방법을 익혀두자

아이템 8. finalizer와 cleaner 사용을 피하라

cleaner는 안전망 역할이나 중요하지 않은 네이티브 자원 회수용으로만 사용하자.
불확실성과 성능 저하에 주의하자.

아이템 9. try-finally보다는 try-with-resources를 사용하라

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

 

 

 

+이해안되는걸 쉽게 이해하게 해주는 블로그들

https://madplay.github.io/post/creating-and-destroying-objects#%EC%95%84%EC%9D%B4%ED%85%9C-7-%EB%8B%A4-%EC%93%B4-%EA%B0%9D%EC%B2%B4-%EC%B0%B8%EC%A1%B0%EB%A5%BC-%ED%95%B4%EC%B2%B4%ED%95%98%EB%9D%BC

 

[이펙티브 자바 3판] 2장. 객체 생성과 파괴

[Effective Java 3th Edition] Chapter 2. Creating and Destroying Objects

madplay.github.io

 

https://otrodevym.tistory.com/entry/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-2%EC%9E%A5-%EA%B0%9D%EC%B2%B4-%EC%83%9D%EC%84%B1%EA%B3%BC-%ED%8C%8C%EA%B4%B4

 

이펙티브 자바 - 2장 : 객체 생성과 파괴

2장 객체 생성과 파괴 아이템 1 : 생성자 대신 정적 팩터리 메서드를 고려하라 전통적으로 public 생성자를 사용하고 더 나아가 정적 팩터리 메서드를 제공할 수 있다. public class item1 { public static voi

otrodevym.tistory.com