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
// 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();
}
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
// 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를 사용하자.
예외는 없다.
코드는 더 짧고 분명해지고, 만들어지는 예외 정보도 훨씬 유용하다.
+이해안되는걸 쉽게 이해하게 해주는 블로그들
'STUDY REVIEW > 이펙티브 자바 독서스터디' 카테고리의 다른 글
[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 7장 람다와 스트림 (0) | 2022.01.12 |
---|---|
[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 6장 열거 타입과 애너테이션 (0) | 2022.01.12 |
[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 5장 제네릭 (0) | 2021.12.22 |
[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 4장 클래스와 인터페이스 (0) | 2021.12.15 |
[EFFECTIVE JAVA] 이펙티브 자바 독서스터디 - 3장 모든 객체의 공통 메서드 (0) | 2021.12.08 |