Java/πŸ“• Effective Java

[Effective Java] 객체 생성과 파괴 上

soogoori 2024. 9. 16. 20:34

1. μƒμ„±μž λŒ€μ‹  정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ !

 

πŸ”Ή 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œ (static factory method) 

πŸ‘‰ ν•΄λ‹Ή 클래슀의 μΈμŠ€ν„΄μŠ€ λ°˜ν™˜
이미 μΊμ‹œλœ 객체 λ°˜ν™˜ν•˜μ—¬ 객체 생성 μ€„μž„


πŸ‘ μž₯점

  • 이름을 κ°€μ§ˆ 수 μžˆμ–΄μ„œ λ°˜ν™˜λ  객체의 νŠΉμ„±μ„ μ œλŒ€λ‘œ μ„€λͺ… κ°€λŠ₯ 
    ex) BigInteger(int, int, Random) vs BigInteger.probablePrime
    πŸ‘‰ λ‘˜ 쀑 '값이 μ†Œμˆ˜μΈ BigIntegerλ₯Ό λ°˜ν™˜ν•˜λ‹€'λŠ” 의미λ₯Ό 더 잘 μ„€λͺ…ν•  수 μžˆλŠ” 것을 λ°”λ‘œ νŒŒμ•… κ°€λŠ₯ 

  • ν•œ ν΄λž˜μŠ€μ— μ‹œκ·Έλ‹ˆμ²˜κ°€ 같은 μƒμ„±μžκ°€ μ—¬λŸ¬ 개 ν•„μš”ν•  μ‹œ 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό ν™œμš©ν•˜λŠ” 것이 λ”μš± 유용
public class Account {
    private String owner;
    private double balance;

    // private μƒμ„±μž
    private Account(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    // 일반 κ³„μ’Œ μƒμ„±μš© νŒ©ν† λ¦¬ λ©”μ„œλ“œ
    public static Account createNormalAccount(String owner, double initialDeposit) {
        return new Account(owner, initialDeposit);
    }

    // VIP κ³„μ’Œ μƒμ„±μš© νŒ©ν† λ¦¬ λ©”μ„œλ“œ
    public static Account createVIPAccount(String owner, double initialDeposit) {
        return new Account(owner, initialDeposit + 1000); // VIP λ³΄λ„ˆμŠ€ μΆ”κ°€
    }

    @Override
    public String toString() {
        return "Account{owner='" + owner + "', balance=" + balance + "}";
    }

    public static void main(String[] args) {
        // 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό ν†΅ν•œ κ³„μ’Œ 생성
        Account normalAccount = Account.createNormalAccount("John Doe", 1000);
        Account vipAccount = Account.createVIPAccount("Jane Doe", 2000);

        System.out.println(normalAccount);
        System.out.println(vipAccount);
    }
}
  • 호좜될 λ•Œλ§ˆλ‹€ μΈμŠ€ν„΄μŠ€ μƒˆλ‘œ 생성 X πŸ‘‰ λΆˆν•„μš”ν•œ λ©”λͺ¨λ¦¬ λ‚­λΉ„ 쀄이고 μ„±λŠ₯ μ΅œμ ν™” κ°€λŠ₯

  • λ°˜ν™˜νƒ€μž…μ˜ ν•˜μœ„ νƒ€μž… 객체λ₯Ό λ°˜ν™˜ν•  수 μžˆμ–΄ λ°˜ν™˜ν•  객체의 클래슀λ₯Ό 자유둭게 선택 κ°€λŠ₯
     
  • μž…λ ₯ λ§€κ°œλ³€μˆ˜μ— 따라 맀번 λ‹€λ₯Έ 클래슀의 객체 λ°˜ν™˜ κ°€λŠ₯
public class Main {
    public static void main(String[] args) {
        // 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό 톡해 Animal νƒ€μž… λ°˜ν™˜
        Animal animal1 = AnimalFactory.createAnimal("dog");
        animal1.speak();  // 좜λ ₯: Woof!

        Animal animal2 = AnimalFactory.createAnimal("cat");
        animal2.speak();  // 좜λ ₯: Meow!
    }
}

 

πŸ‘Ž 단점

  • 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό ν”„λ‘œκ·Έλž˜λ¨Έκ°€ μ°ΎκΈ° 어렀움 πŸ‘‰ μƒμ„±μžμ²˜λŸΌ API μ„€λͺ…에 λͺ…ν™•νžˆ λ“œλŸ¬λ‚˜μ§€ X
  • 선택적 λ§€κ°œλ³€μˆ˜κ°€ λ§Žμ„ λ•Œ 적절히 λ°˜μ‘ 어렀움 (μƒμ„±μžλ„ λ§ˆμ°¬κ°€μ§€)

✳️ ν”νžˆ μ‚¬μš©ν•˜λŠ” λͺ…λͺ… 방식 

  • from : λ§€κ°œλ³€μˆ˜λ₯Ό ν•˜λ‚˜ λ°›μ•„μ„œ ν•΄λ‹Ή νƒ€μž…μ˜ μΈμŠ€ν„΄μŠ€ λ°˜ν™˜ πŸ‘‰ ν˜•λ³€ν™˜ λ©”μ„œλ“œ
  • of : μ—¬λŸ¬ λ§€κ°œλ³€μˆ˜λ₯Ό λ°›μ•„ μ ν•©ν•œ νƒ€μž…μ˜ μΈμŠ€ν„΄μŠ€ λ°˜ν™˜ πŸ‘‰ 집계 λ©”μ„œλ“œ 
  • valueOf : fromκ³Ό of의 더 μžμ„Έν•œ 버전
  • instance λ˜λŠ” getInstance : λ§€κ°œλ³€μˆ˜λ‘œ λͺ…μ‹œν•œ μΈμŠ€ν„΄μŠ€ λ°˜ν™˜ν•˜μ§€λ§Œ 같은 μΈμŠ€ν„΄μŠ€μž„ 보μž₯ X
  • create λ˜λŠ” newInstance : 맀번 μƒˆλ‘œμš΄ μΈμŠ€ν„΄μŠ€λ₯Ό 생성해 λ°˜ν™˜ν•¨ 보μž₯
  • getType : getInstance와 κ°™μœΌλ‚˜ 생성할 ν΄λž˜μŠ€κ°€ μ•„λ‹Œ λ‹€λ₯Έ ν΄λž˜μŠ€μ— νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μ •μ˜ν•  λ•Œ μ‚¬μš©
  • newType : newInstance와 κ°™μœΌλ‚˜, 생성할 ν΄λž˜μŠ€κ°€ μ•„λ‹Œ λ‹€λ₯Έ ν΄λž˜μŠ€μ— νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό μ •μ˜ν•  λ•Œ μ‚¬μš©
    πŸ‘‰ FileStore fs = Files.getFileStore(path)
  • type : getTypeκ³Ό newType의 κ°„κ²°ν•œ 버전
    πŸ‘‰ List<Complaint> litany = Collections.list(legacyLitany)
    ➑️ "Type" 은 νŒ©ν† λ¦¬ λ©”μ„œλ“œκ°€ λ°˜ν™˜ν•  객체의 νƒ€μž…

 

2. μƒμ„±μžμ— λ§€κ°œλ³€μˆ˜κ°€ 많으면 Builder둜 !

  • 점측적 μƒμ„±μž νŒ¨ν„΄
    : 선택 λ§€κ°œλ³€μˆ˜λ₯Ό μ „λΆ€ λ‹€ λ°›λŠ” μƒμ„±μžκΉŒμ§€ λŠ˜λ €κ°€λŠ” 방식
    βž” ν™•μž₯ν•˜κΈ° 어렀움
    λ§€κ°œλ³€μˆ˜ κ°œμˆ˜κ°€ λ§Žμ•„μ§€λ©΄ ν΄λΌμ΄μ–ΈνŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜κ±°λ‚˜ 읽기 어렀움
    ν΄λΌμ΄μ–ΈνŠΈκ°€ μ‹€μˆ˜λ‘œ λ§€κ°œλ³€μˆ˜μ˜ μˆœμ„œλ₯Ό λ°”κΏ” κ±΄λ„€μ€˜λ„ μ»΄νŒŒμΌλŸ¬λŠ” μ•Œμ•„μ±„μ§€ λͺ»ν•¨

  • Java Beans
    : λ§€κ°œλ³€μˆ˜κ°€ μ—†λŠ” μƒμ„±μžλ‘œ 객체λ₯Ό λ§Œλ“  ν›„, setter λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄ μ›ν•˜λŠ” λ§€κ°œλ³€μˆ˜μ˜ κ°’ μ„€μ •
    βž” 일관성이 깨지고 λΆˆλ³€μœΌλ‘œ λ§Œλ“€ 수 μ—†μŒ
    객체 ν•˜λ‚˜λ₯Ό λ§Œλ“œλ €λ©΄ λ©”μ„œλ“œλ₯Ό μ—¬λŸ¬ 개 ν˜ΈμΆœν•΄μ•Όν•¨ 
    객체가 μ™„μ „νžˆ μƒμ„±λ˜κΈ° μ „κΉŒμ§€λŠ” 일관성 λ¬΄λ„ˆμ§
μ—¬κΈ°μ„œ 일관성이 λ¬΄λ„ˆμ§„λ‹€λŠ” 것은...?

πŸ‘‰ 쀑간에 일뢀 κ°’λ§Œ μ„€μ •λœ μƒνƒœμ—μ„œ 객체가 μ‘΄μž¬ν•΄ λΆˆμ™„μ „ν•œ μƒνƒœμž„
πŸ‘‰ λ§Œμ•½ 객체가 μ—¬λŸ¬ ν•„λ“œλ₯Ό κ°€μ Έμ•Ό ν•˜λŠ”λ°, setterλ₯Ό 톡해 ν•„λ“œκ°€ μ°¨λ‘€λ‘œ μ„€μ •λ˜λŠ” λ™μ•ˆ λ‹€λ₯Έ μ½”λ“œμ—μ„œ 이 객체에 μ ‘κ·Όν•  수 μžˆλ‹€λ©΄, 객체가 아직 μ™„μ „νžˆ μ΄ˆκΈ°ν™”λ˜μ§€ μ•Šμ€ μƒνƒœμ—μ„œ μ‚¬μš©λ˜λ―€λ‘œ 였λ₯˜ λ°œμƒ 
πŸ‘‰ setter λ©”μ„œλ“œλ‘œ 인해 객체가 μƒμ„±λœ 이후에도 κ·Έ μƒνƒœκ°€ 변경될 수 있음 

 

πŸ‘‰ μœ„μ™€ 같은 방법이 μžˆμœΌλ‚˜ λΉŒλ” νŒ¨ν„΄μ„ μ‚¬μš©ν•˜λ©΄ νš¨μœ¨μ μ΄λ‹€ !


✳️ Builder νŒ¨ν„΄ 

  • ν•„μš”ν•œ 객체λ₯Ό 직접 λ§Œλ“œλŠ” λŒ€μ‹ , ν•„μˆ˜ λ§€κ°œλ³€μˆ˜λ§ŒμœΌλ‘œ μƒμ„±μž ν˜Ήμ€ 정적 νŒ©ν† λ¦¬λ₯Ό ν˜ΈμΆœν•΄ λΉŒλ” 객체λ₯Ό μ–»μŒ
    κ·Έ ν›„ λΉŒλ” 객체가 μ œκ³΅ν•˜λŠ” μΌμ’…μ˜ setter λ©”μ„œλ“œλ‘œ μ›ν•˜λŠ” 선택 λ§€κ°œλ³€μˆ˜ μ„€μ •ν•˜κ³ ,  λ§€κ°œλ³€μˆ˜κ°€ μ—†λŠ” build λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•΄ ν•„μš”ν•œ 객체λ₯Ό μ–»μŒ
  • builderλŠ” 생성할 클래슀 μ•ˆμ— 정적 멀버 클래슀둜 λ§Œλ“€μ–΄ λ‘ 
class Person {
    // ν•„λ“œλ“€ (λͺ¨λ“  ν•„λ“œλŠ” λΆˆλ³€μœΌλ‘œ μ„€μ •)
    private final String name;
    private final int age;
    private final String address;

    // Person의 private μƒμ„±μž (Builderκ°€ 호좜)
    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.address = builder.address;
    }

    // Builder λ‚΄λΆ€ 클래슀
    public static class Builder {
        private String name;     // ν•„λ“œλ“€ (κΈ°λ³Έκ°’ μ„€μ • κ°€λŠ₯)
        private int age;
        private String address;

        // name ν•„λ“œλ₯Ό μ„€μ •ν•˜λŠ” λ©”μ„œλ“œ (Builder 객체 λ°˜ν™˜)
        public Builder setName(String name) {
            this.name = name;
            return this;  // λ©”μ„œλ“œ 체이닝을 μœ„ν•΄ this λ°˜ν™˜
        }

        // age ν•„λ“œλ₯Ό μ„€μ •ν•˜λŠ” λ©”μ„œλ“œ (Builder 객체 λ°˜ν™˜)
        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        // address ν•„λ“œλ₯Ό μ„€μ •ν•˜λŠ” λ©”μ„œλ“œ (Builder 객체 λ°˜ν™˜)
        public Builder setAddress(String address) {
            this.address = address;
            return this;
        }

        // build() λ©”μ„œλ“œ: μ™„μ„±λœ Person 객체λ₯Ό λ°˜ν™˜
        public Person build() {
            return new Person(this);  // μ™„μ „ν•œ Person 객체 생성
        }
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", address='" + address + "'}";
    }
}
public class Main {
    public static void main(String[] args) {
        // λΉŒλ” νŒ¨ν„΄μ„ 톡해 Person 객체 생성
        Person person = new Person.Builder()
                            .setName("John")
                            .setAge(30)
                            .setAddress("123 Main St")
                            .build();  // μ™„μ„±λœ Person 객체 생성

        // κ²°κ³Ό 좜λ ₯
        System.out.println(person); 
		// Person{name='John', age=30, address='123 Main St'}
    }
}
import lombok.Builder;
import lombok.ToString;

@Builder    // 둬볡의 @Builder μ–΄λ…Έν…Œμ΄μ…˜ 적용
@ToString   // 객체 λ‚΄μš©μ„ 좜λ ₯ν•˜κΈ° μœ„ν•΄ @ToString μ–΄λ…Έν…Œμ΄μ…˜ 적용
class Person {
    private String name;
    private int age;
    private String address;
}

public class Main {
    public static void main(String[] args) {
        // @Builder μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ μƒμ„±λœ λΉŒλ” νŒ¨ν„΄ μ‚¬μš©
        Person person = Person.builder()
                              .name("John")
                              .age(30)
                              .address("123 Main St")
                              .build();

        // κ²°κ³Ό 좜λ ₯
        System.out.println(person);  // Person(name=John, age=30, address=123 Main St)
    }
}

 

✨ Builder νŒ¨ν„΄μ€ κ³„μΈ΅μ μœΌλ‘œ μ„€κ³„λœ ν΄λž˜μŠ€μ™€ ν•¨κ»˜ μ“°κΈ° μ’‹μŒ !

 

  • 좔상 λΉŒλ” 클래슀λ₯Ό 톡해 λ‹€μ–‘ν•œ μ’…λ₯˜μ˜ ν”Όμž 객체λ₯Ό μœ μ—°ν•˜κ²Œ 생성 κ°€λŠ₯ 
public abstract class Pizza{
	public enum Topping {HAM, SAUSAGE, MUSHROOM, ONION, PEPPER}
    final Set<Topping> toppings;
    
    // 좔상 λΉŒλ” 클래슀
	// λΉŒλ” νŒ¨ν„΄μ˜ λ©”μ„œλ“œ 체이닝을 κ°€λŠ₯ν•˜κ²Œ 함
    abstract static class Builder<T extends Builder<T>> {
    	EnumSet<Topping> toppings = EnumSet.noneOf(Topping.class); // μ΄ˆκΈ°μ—λŠ” 아무 토핑도 μ—†λŠ” μƒνƒœ
        
        public T addTopping(Topping topping){
        	toppings.add(Objects.requireNonNull(topping));
            return self(); // 자기 μžμ‹ (ν•˜μœ„ 클래슀의 λΉŒλ”)을 λ°˜ν™˜ν•˜μ—¬ λ©”μ„œλ“œ 체이닝이 κ°€λŠ₯ν•˜λ„λ‘ 함
        }
        
        abstract Pizza build(); // ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ 이 λ©”μ„œλ“œλ₯Ό ꡬ체적으둜 κ΅¬ν˜„ν•΄μ•Όν•¨
        
        // ν•˜μœ„ ν΄λž˜μŠ€λŠ” 이 λ©”μ„œλ“œλ₯Ό overridingν•˜μ—¬ "this"(자기 μžμ‹ )λ₯Ό λ°˜ν™˜ν•˜λ„λ‘ 해야함
        // λ©”μ„œλ“œ 체이닝을 κ°€λŠ₯ν•˜κ²Œ ν•˜κ³ , λΉŒλ” 클래슀의 ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ μžμ‹ μ„ λ°˜ν™˜ν•˜λ„λ‘ κ°•μ œ
        protected abstract T self();
    }
    
    Pizza(Builder<?> builder) {
    	toppings = builder.toppings.clone();
    }
}
public class VeggiePizza extends Pizza {

	public enum Size {SMALL, MEDIUM, LARGE}
    private final Size size;
    
    public static class Builder extends Pizza.Builder<Builder> {
    	private final Size size;
        
        public Builder(Size size){
        	this.size = Objects.requireNonNull(size);
        }
        
        @Override
        public VeggiePizza build() {
            return new VeggiePizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private VeggiePizza(Builder builder) {
        super(builder);  // μƒμœ„ 클래슀 Pizza의 μƒμ„±μž 호좜
        size = builder.size;
    }
}
VeggiePizza pizza = new VeggiePizza.Builder(SMALL)
									.addTopping(SAUSAGE)
                                    .addTopping(PEPPER).build();

 

πŸ‘ μž₯점 

  • 가독성 ν–₯상
  • λΆˆλ³€ 객체 생성
  • ν•„λ“œ 선택적 μ„€μ •
  • ν™•μž₯μ„± 및 μœ μ—°μ„±

 

πŸ‘Ž 단점

  • 객체λ₯Ό λ§Œλ“€λ €λ©΄ 그에 μ•žμ„œ λΉŒλ”λΆ€ν„° λ§Œλ“€μ–΄μ•Ό 함 (λ§€κ°œλ³€μˆ˜κ°€ 4개 이상은 λ˜μ–΄μ•Ό κ°’μ–΄μΉ˜λ₯Ό 함) 
    πŸ‘‰ Lombokμ—μ„œ μ œκ³΅ν•˜λŠ” @Builder μ–΄λ…Έν…Œμ΄μ…˜μœΌλ‘œ λΉŒλ” νŒ¨ν„΄ μ‰½κ²Œ κ΅¬ν˜„ν•΄ ν•΄κ²° !

 

 

3.  private μƒμ„±μžλ‚˜ μ—΄κ±° νƒ€μž…μœΌλ‘œ singletonμž„μ„ 보증 

Singletonμ΄λž€ ?
πŸ‘‰ μΈμŠ€ν„΄μŠ€λ₯Ό 였직 ν•˜λ‚˜λ§Œ 생성할 수 μžˆλŠ” 클래슀

 

✳️ Singleton을 λ§Œλ“œλŠ” 방식 

  • public static final ν•„λ“œ 방식
    πŸ‘‰ ν•΄λ‹Ή ν΄λž˜μŠ€κ°€ μ‹±κΈ€ν„΄μž„μ΄ API에 λͺ…λ°±νžˆ λ“œλŸ¬λ‚˜κ³  간결함 
public class Singleton {
    // 1. μ‹±κΈ€ν„΄ μΈμŠ€ν„΄μŠ€λ₯Ό static final둜 μ„ μ–Έν•˜μ—¬ 클래슀 λ‘œλ“œ μ‹œμ μ— μ΄ˆκΈ°ν™”
    private static final Singleton INSTANCE = new Singleton();

    // 2. private μƒμ„±μžλ‘œ μ™ΈλΆ€μ—μ„œ μΈμŠ€ν„΄μŠ€ν™”ν•  수 없도둝 함
    private Singleton() {
        // private constructor to prevent instantiation
    }

    // 3. 정적 νŒ©ν† λ¦¬ λ©”μ„œλ“œλ₯Ό 톡해 μΈμŠ€ν„΄μŠ€ 제곡
    public static Singleton getInstance() {
        return INSTANCE;
    }

    // μΈμŠ€ν„΄μŠ€κ°€ κ°€μ§€λŠ” λ©”μ„œλ“œ
    public void doSomething() {
        System.out.println("Singleton instance method.");
    }
}

πŸ‘‰ private μƒμ„±μžκ°€ public static final ν•„λ“œλ₯Ό μ΄ˆκΈ°ν™”ν•  λ•Œ λ”± ν•œ 번만 μ΄ˆκΈ°ν™”λ¨

public class SingletonDemo {
    public static void main(String[] args) {
        // μ‹±κΈ€ν„΄ μΈμŠ€ν„΄μŠ€λ₯Ό κ°€μ Έμ˜΄
        Singleton singleton = Singleton.getInstance();

        // μ‹±κΈ€ν„΄ λ©”μ„œλ“œλ₯Ό 호좜
        singleton.doSomething();

        // 두 개의 Singleton μΈμŠ€ν„΄μŠ€κ°€ 동일함을 확인
        Singleton anotherSingleton = Singleton.getInstance();
        System.out.println(singleton == anotherSingleton); // true
    }
}

 

  • 정적 νŒ©ν† λ¦¬ 방식
    πŸ‘‰ Singleton.getInstanceλŠ” 항상 같은 객체의 μ°Έμ‘°λ₯Ό λ°˜ν™˜ 

  • μ—΄κ±° νƒ€μž… 방식
    πŸ‘‰ enum ν™œμš©ν•˜μ—¬ κ°€μž₯ μ•ˆμ „ν•˜κ³  κ°„κ²°ν•œ 방식
public enum Singleton {
    // μœ μΌν•œ μ‹±κΈ€ν„΄ μΈμŠ€ν„΄μŠ€
    INSTANCE;

    // μΈμŠ€ν„΄μŠ€κ°€ κ°€μ§€λŠ” λ©”μ„œλ“œ
    public void doSomething() {
        System.out.println("Singleton instance method.");
    }
}

βž” enum νƒ€μž…μ€ public static final μΈμŠ€ν„΄μŠ€ ν•„λ“œλ₯Ό 기본적으둜 생성