hanyugyeong 2023. 10. 15. 22:05
반응형
SMALL

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

1) 이름을 가질 수 있다. 

    //생성자 메서ㄷ,
//    public Order(boolean prime, boolean urgent, Product product, OrderStatus orderStatus) {
//        this.prime = prime;
//        this.urgent = urgent;
//        this.product = product;
//        this.orderStatus = orderStatus;
//    }

    public static Order primeOrder(Product product) {
        Order order = new Order();
        order.prime = true;
        order.product = product;

        return order;
    }

    public static Order urgentOrder(Product product) {
        Order order = new Order();
        order.urgent = true;
        order.product = product;
        return order;
    }

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

/**
 * 이 클래스의 인스턴스는 #getInstance()를 통해 사용한다.
 * @see #getInstance()
 */
public class Settings {

    private boolean useAutoSteering;

    private boolean useABS;

    private Difficulty difficulty;

    private Settings() {}

    private static final Settings SETTINGS = new Settings();

    public static Settings getInstance() {
        return SETTINGS;
    }

}
package me.whiteship.chapter01.item01;

import java.util.EnumSet;
import java.util.Set;

public class Product {

    public static void main(String[] args) {
        Settings settings1 = Settings.getInstance();
        Settings settings2 = Settings.getInstance();

        System.out.println(settings1);
        System.out.println(settings2);

        Boolean.valueOf(false);
        EnumSet.allOf(Difficulty.class);
    }
}
me.whiteship.chapter01.item01.Settings@506e6d5e
me.whiteship.chapter01.item01.Settings@506e6d5e

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

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

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

3,4,5번에 대한 내용은 아래 블로그에 잘 나와있어서 참조 합니다 

https://mimah.tistory.com/entry/Effective-Java-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C-Static-factory-method

 

 

정적 팩토리 메서드 (Static factory method) [Effective Java]

정적 팩토리 메서드 (Static factory method) 정적 팩토리 메서드란 간단히 말해 객체를 생성할 때, 생성자를 쓰지 않고 정적 메서드를 사용하는 것이다. 디자인 패턴의 팩토리 메서드와는 다른 것이며

mimah.tistory.com

단점 

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

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

아래 블로그 참조

https://hudi.blog/effective-java-static-factory-method/

 

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

이펙티브 자바 3판의 아이템 1의 내용을 공부하며 작성한 글이다. 정적 팩터리 메서드는 클래스에 정적 메서드를 정의하여, 생성자 대신 객체를 생성할 수 있게 만드는 기법이다. 장점1. 이름을

hudi.blog

 

p9. 열거 타입은 인스턴스가 하나만 만들어짐을 보장한다. 

-> 열거 타입은 특정한 값으로 제한 할수 있다 (type safety)

public enum OrderStatus {

    PREPARING(0), SHIPPED(1), DELIVERING(2), DELIVERED(3);

    private int number;

    OrderStatus(int number) {
        this.number = number;
    }
}

enum이 없이 상태를 표현하면 읽기는 좋을수 있으나 값이 변경이 됄 수 있다. 방어하는 코드가 들어가야 한다. 검증하는 코드

enum으로 하면 값을 변경할 수 없다. 또한 인스턴스가 하나만 만들어진다. 싱글톤 패턴을 안전하게 사용하는 방법으로 enum을 추천한다. 

 

p9. 플라이웨이트 패턴

-> 객체를 가볍게 만들어 메모리 사용을 줄이는 패턴 

아래 블로그를 참고해서 공부했습니다

https://velog.io/@hoit_98/%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4-Flyweight-%ED%8C%A8%ED%84%B4

 

p.10 인터페이스가 정적 메서드를 가질 수 없다는 제한이 풀렸기 때문에 인스턴스화 불가 동반 클래스를 둘 이유가 별로 없다. 

p11. 서비스 제공자 프레임워크를 만드는 근간이 된다. 

-> 확장 가능한 애플리케이션을 만드는 방법 

p.12 서비스 제공자 인터페이스가 없다면 각 구현체를 인스턴스로 만들 때 리플렉션(아이템 65)을 사용해야 한다. 

 

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

  1) 점층적 생성자 패턴을 즐겨 사용했다. 

예시) 

// 코드 2-1 점층적 생성자 패턴 - 확장하기 어렵다! (14~15쪽)
public class NutritionFacts {
    private final int servingSize;  // (mL, 1회 제공량)     필수
    private final int servings;     // (회, 총 n회 제공량)  필수
    private final int calories;     // (1회 제공량당)       선택
    private final int fat;          // (g/1회 제공량)       선택
    private final int sodium;       // (mg/1회 제공량)      선택
    private final int carbohydrate; // (g/1회 제공량)       선택

    public NutritionFacts(int servingSize, int servings) {
        this(servingSize, servings, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories) {
        this(servingSize, servings, calories, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat) {
        this(servingSize, servings, calories, fat, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat, int sodium) {
        this(servingSize, servings, calories, fat, sodium, 0);
    }

    public NutritionFacts(int servingSize, int servings,
                          int calories, int fat, int sodium, int carbohydrate) {
        this.servingSize  = servingSize;
        this.servings     = servings;
        this.calories     = calories;
        this.fat          = fat;
        this.sodium       = sodium;
        this.carbohydrate = carbohydrate;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola =
                new NutritionFacts(10, 10);
    }
    
}

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

코드를 읽을 때 각 값의 의미가 무엇인지 헷갈릴 것이고, 매개변수가 몇 개인지도 주의해서 세어 보아야 할 것이다. 

 

2) 자바 빈즈 패턴을 사용한다. 

package me.whiteship.chapter01.item02.javabeans;

// 코드 2-2 자바빈즈 패턴 - 일관성이 깨지고, 불변으로 만들 수 없다. (16쪽)
public class NutritionFacts {
    // 필드 (기본값이 있다면) 기본값으로 초기화된다.
    private int servingSize  = -1; // 필수; 기본값 없음
    private int servings     = -1; // 필수; 기본값 없음
    private int calories     = 0;
    private int fat          = 0;
    private int sodium       = 0;
    private int carbohydrate = 0;
    private boolean healthy;

    public NutritionFacts() { }

    public void setServingSize(int servingSize) {
        this.servingSize = servingSize;
    }

    public void setServings(int servings) {
        this.servings = servings;
    }

    public void setCalories(int calories) {
        this.calories = calories;
    }

    public void setFat(int fat) {
        this.fat = fat;
    }

    public void setSodium(int sodium) {
        this.sodium = sodium;
    }

    public void setCarbohydrate(int carbohydrate) {
        this.carbohydrate = carbohydrate;
    }

    public void setHealthy(boolean healthy) {
        this.healthy = healthy;
    }

    public static void main(String[] args) {
        NutritionFacts cocaCola = new NutritionFacts();
        cocaCola.setServingSize(240);
        cocaCola.setServings(8);

        cocaCola.setCalories(100);
        cocaCola.setSodium(35);
        cocaCola.setCarbohydrate(27);
    }
}

장점: 객체 생성이 간단해진다. 

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

일관성이 무너지는 문제 때문에 자바빈즈 패턴에서는 클래스를 불변으로 만들 수 없으며 스레드 안정성을 얻으려면 프로그래머가 추가 작업을 해줘야만 한다. 

 

3) 빌더 패턴

점층적 생성자 패턴의 안전성과 자바빈즈 패턴의 가독성을 겸비한 빌더 패턴이다. 

// 코드 2-3 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다. (17~18쪽)
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static void main(String[] args) {
        NutritionFacts cocaCola = new Builder(240, 8)
                .calories(100)
                .sodium(35)
                .carbohydrate(27).build();
    }

    public static class Builder {
        // 필수 매개변수
        private final int servingSize;
        private final int servings;

        // 선택 매개변수 - 기본값으로 초기화한다.
        private int calories      = 0;
        private int fat           = 0;
        private int sodium        = 0;
        private int carbohydrate  = 0;

        public Builder(int servingSize, int servings) {
            this.servingSize = servingSize;
            this.servings    = servings;
        }

        public Builder calories(int val)
        { calories = val;      return this; }
        public Builder fat(int val)
        { fat = val;           return this; }
        public Builder sodium(int val)
        { sodium = val;        return this; }
        public Builder carbohydrate(int val)
        { carbohydrate = val;  return this; }

        public NutritionFacts build() {
            return new NutritionFacts(this);
        }
    }

    private NutritionFacts(Builder builder) {
        servingSize  = builder.servingSize;
        servings     = builder.servings;
        calories     = builder.calories;
        fat          = builder.fat;
        sodium       = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}

 빌더 패턴은 (파이썬과 스칼라에 있는) 명명된 선택적 매개변수를 흉내 낸 것이다. 

빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다. 

lombok을 사용해서 아래와 같이 줄일 수 있다. 

대신 필수값을 설정해 줄 수 없다. 

import lombok.Builder;
@Builder
public class NutritionFacts {
    private final int servingSize;
    private final int servings;
    private final int calories;
    private final int fat;
    private final int sodium;
    private final int carbohydrate;

    public static void main(String[] args) {
        NutritionFacts nutritionFacts = new NutritionFactsBuilder()
            .servingSize(100)
            .servings(10)
            .build();
    }
 }

빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다. 

추상 클래스는 추상 빌더를, 구체 클래스는 구체 빌더를 갖게 한다. 

다음은 피자의 다양한 종류를 표현하는 계층구조의 루트에 놓인 추상 클래스다. 

// 코드 2-4 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴 (19쪽)

// 참고: 여기서 사용한 '시뮬레이트한 셀프 타입(simulated self-type)' 관용구는
// 빌더뿐 아니라 임의의 유동적인 계층구조를 허용한다.

public abstract class Pizza {
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    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(); // 아이템 50 참조
    }
}

 self 타입이 없는 자바를 위한 이 우회 방법을 시뮬레이트한 셀프 타입 관용구라 한다. 

Pizza의 하위 클래스가 2개 있다. 하나는 일반적인 뉴욕 피자이고, 다른 하나는 칼초네(calzone) 피자다. 뉴욕 피자는 크기(size) 매개변수를 필수로 받고, 칼조네 피자는 소스를 안에 넣을지 선택(sauceInside)하는 매개변수를 필수로 받는다. 

// 코드 2-5 뉴욕 피자 - 계층적 빌더를 활용한 하위 클래스 (20쪽)
public class NyPizza extends Pizza {
    public enum Size { SMALL, MEDIUM, LARGE }
    private final Size size;

    public static class Builder extends Pizza.Builder<NyPizza.Builder> {
        private final Size size;

        public Builder(Size size) {
            this.size = Objects.requireNonNull(size);
        }

        @Override public NyPizza build() {
            return new NyPizza(this);
        }

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

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }

    @Override public String toString() {
        return toppings + "로 토핑한 뉴욕 피자";
    }
}
// 코드 2-6 칼초네 피자 - 계층적 빌더를 활용한 하위 클래스 (20~21쪽)
public class Calzone extends Pizza {
    private final boolean sauceInside;

    public static class Builder extends Pizza.Builder<Builder> {
        private boolean sauceInside = false; // 기본값

        public Builder sauceInside() {
            sauceInside = true;
            return this;
        }

        @Override public Calzone build() {
            return new Calzone(this);
        }

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

    private Calzone(Builder builder) {
        super(builder);
        sauceInside = builder.sauceInside;
    }

    @Override public String toString() {
        return String.format("%s로 토핑한 칼초네 피자 (소스는 %s에)",
                toppings, sauceInside ? "안" : "바깥");
    }
}

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

싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 그런데 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워질 수 있다. 타입을 인터페이스로 정의한 다음 그 인터페이스를 구현해서 만든 싱글턴이 아니라면 싱글턴 인스턴스를 가짜(mock) 구현으로 대체할 수 없기 때문이다. 

// 코드 3-1 public static final 필드 방식의 싱글턴 (23쪽)
public class Elvis implements IElvis, Serializable {

    /**
     * 싱글톤 오브젝트
     */
    public static final Elvis INSTANCE = new Elvis();
    private static boolean created;

    private Elvis() {
        if (created) {
            throw new UnsupportedOperationException("can't be created by constructor.");
        }

        created = true;
    }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    public void sing() {
        System.out.println("I'll have a blue~ Christmas without you~");
    }

    // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }

    private Object readResolve() {
        return INSTANCE;
    }

}
public class Concert {

    private boolean lightsOn;

    private boolean mainStateOpen;

    private IElvis elvis;

    public Concert(IElvis elvis) {
        this.elvis = elvis;
    }

    public void perform() {
        mainStateOpen = true;
        lightsOn = true;
        elvis.sing();
    }

    public boolean isLightsOn() {
        return lightsOn;
    }

    public boolean isMainStateOpen() {
        return mainStateOpen;
    }
}
public class MockElvis implements IElvis {
    @Override
    public void leaveTheBuilding() {

    }

    @Override
    public void sing() {
        System.out.println("You ain't nothin' but a hound dog.");
    }
}
class ConcertTest {

    @Test
    void perform() {
        Concert concert = new Concert(new MockElvis());
        concert.perform();

        assertTrue(concert.isLightsOn());
        assertTrue(concert.isMainStateOpen());
    }

}

싱글턴을 만드는 두 번째 방법에서는 정적 팩터리 메서드를 public static 맴버로 제공한다. 

정적 팩터리 방식의 첫번째 장점은 (마음이 바뀌면) API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다는 점이다. 

public class Elvis implements Singer {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    //public static Elvis getInstance() { return INSTANCE; }
    public static Elvis getInstance() { return new Elvis(); }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다!
    public static void main(String[] args) {
        Elvis elvis = Elvis.getInstance();
        elvis.leaveTheBuilding();

        System.out.println(Elvis.getInstance());
        System.out.println(Elvis.getInstance());
    }

    @Override
    public void sing() {
        System.out.println("my way~~~");
    }
}
me.whiteship.chapter01.item03.staticfactory.Elvis@96532d6
me.whiteship.chapter01.item03.staticfactory.Elvis@3796751b

두번째 장점은 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다는 점이다. 

// 코드 3-2 제네릭 싱글톤 팩토리 (24쪽)
public class MetaElvis<T> {

    private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();

    private MetaElvis() { }

    @SuppressWarnings("unchecked")
    public static <E> MetaElvis<E> getInstance() { return (MetaElvis<E>) INSTANCE; }

    public void say(T t) {
        System.out.println(t);
    }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    public static void main(String[] args) {
        MetaElvis<String> elvis1 = MetaElvis.getInstance();
        MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
        System.out.println(elvis1);
        System.out.println(elvis2);
        elvis1.say("hello");
        elvis2.say(100);
    }

}

세 번째 장점은 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있다는 점이다. 

public class Concert {

    public void start(Supplier<Singer> singerSupplier) {
        Singer singer = singerSupplier.get();
        singer.sing();
    }

    public static void main(String[] args) {
        Concert concert = new Concert();
        concert.start(Elvis::getInstance);
    }
}

이러한 장점들이 굳이 필요하지 않다면 public 필드 방식이 좋다. 

 

싱글턴을 만드는 세 번째 방법은 원소가 하나인 열거 타입을 선언하는 것이다. 

// 열거 타입 방식의 싱글턴 - 바람직한 방법 (25쪽)
public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("기다려 자기야, 지금 나갈께!");
    }

    // 이 메서드는 보통 클래스 바깥(다른 클래스)에 작성해야 한다!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

public 필드 방식과 비슷하지만, 더 간결하고, 추가 노력 없이 직렬화할 수 있고, 심지어 아주 복잡한 직렬화 상황이나 리플렉션 공격에서도 제 2의 인스턴스가 생기는 일을 완벽히 막아준다. 

대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.

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

정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때 

추상 클래스로 만드는 것으로는 인스턴스화를 막을 수 없다. 

package me.whiteship.chapter01.item04;

import org.springframework.context.annotation.AnnotationConfigUtils;

public abstract class UtilityClass {

    public UtilityClass() {
        System.out.println("constructor");
    }

    /**
     * 이 클래스는 인스턴스를 만들 수 없습니다.
     */
//    private UtilityClass() {
//        throw new AssertionError();
//    }

    public static String hello() {
        return "hello";
    }

    public static void main(String[] args) {
        String hello = UtilityClass.hello();

       // UtilityClass utilityClass = new UtilityClass();
       // utilityClass.hello();


    }
}
package me.whiteship.chapter01.item04;

public class DefaultUtilityClass extends UtilityClass {

    public static void main(String[] args) {
        DefaultUtilityClass utilityClass = new DefaultUtilityClass();
        utilityClass.hello();
    }
}

constructor

 

 private 생성자를 추가하면 클래스의 인스턴스화를 막을 수 있다. 

package me.whiteship.chapter01.item04;

import org.springframework.context.annotation.AnnotationConfigUtils;

public class UtilityClass {

//    public UtilityClass() {
//        System.out.println("constructor");
//    }

    /**
     * 이 클래스는 인스턴스를 만들 수 없습니다.
     */
    private UtilityClass() {
        throw new AssertionError();
    }

    public static String hello() {
        return "hello";
    }

    public static void main(String[] args) {
        String hello = UtilityClass.hello();

        UtilityClass utilityClass = new UtilityClass();
        utilityClass.hello();


    }
}
Exception in thread "main" java.lang.AssertionError
	at me.whiteship.chapter01.item04.UtilityClass.<init>(UtilityClass.java:15)
	at me.whiteship.chapter01.item04.UtilityClass.main(UtilityClass.java:25)

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

public interface Dictionary {

    boolean contains(String word);

    List<String> closeWordsTo(String typo);
}
public class DefaultDictionary implements Dictionary{

    @Override
    public boolean contains(String word) {
        return false;
    }

    @Override
    public List<String> closeWordsTo(String typo) {
        return null;
    }
}
public class SpellChecker {

    private final Dictionary dictionary = new DefaultDictionary();

    private SpellChecker() {}

    public static final SpellChecker INSTANCE = new SpellChecker();

    public boolean isValid(String word) {
        // TODO 여기 SpellChecker 코드
        return dictionary.contains(word);
    }

    public List<String> suggestions(String typo) {
        // TODO 여기 SpellChecker 코드
        return dictionary.closeWordsTo(typo);
    }
}

Directory를 인터페이스로 만들면 아래와 같이 유동적으로 사용할수있다 의존객체주입

class SpellCheckerTest {

    @Test
    void isValid() {
        SpellChecker spellChecker = new SpellChecker(MockDictionary::new);
        spellChecker.isValid("test");
    }

}
public class MockDictionary implements Dictionary{
    @Override
    public boolean contains(String word) {
        return false;
    }

    @Override
    public List<String> closeWordsTo(String typo) {
        return null;
    }
}

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

똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다. 재사용은 빠르고 세련된다. 

특히 불변 객체는 언제든 재사용할 수 있다. 

//TODO 이 방법은 권장하지 않습니다.
String hello2 = new String("hello");

생성 비용이 아주 비싼 객체도 더러 있다. 이런 "비싼 객체"가 반복해서 필요하다면 캐싱하여 재사용하길 권한다. 

안타깝게도 자신이 만드는 객체가 비싼 객체인지를 매번 명확히 알 수는 없다. 

정규표현식을 표현하는 Pattern 인스턴스를  클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱해두고, 나중에 isRomanNumeral 메서드가 호출될 때마다 이 인스턴스를 재사용한다. 

package me.whiteship.chapter01.item06;
import java.util.regex.Pattern;

// 값비싼 객체를 재사용해 성능을 개선한다. (32쪽)
public class RomanNumerals {
    // 코드 6-1 성능을 훨씬 더 끌어올릴 수 있다!
    static boolean isRomanNumeralSlow(String s) {
        return s.matches("^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
    }

    // 코드 6-2 값비싼 객체를 재사용해 성능을 개선한다.
    private static final Pattern ROMAN = Pattern.compile(
            "^(?=.)M*(C[MD]|D?C{0,3})(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");

    static boolean isRomanNumeralFast(String s) {
        return ROMAN.matcher(s).matches();
    }

    public static void main(String[] args) {
        boolean result = false;
        long start = System.nanoTime();
        for (int j = 0; j < 100; j++) {
            //TODO 성능 차이를 확인하려면 xxxSlow 메서드를 xxxFast 메서드로 바꿔 실행해보자.
            result = isRomanNumeralFast("MCMLXXVI");
        }
        long end = System.nanoTime();
        System.out.println(end - start);
        System.out.println(result);
    }
}
6976400 - slow
1725500 - fast

 

불필요한 객체를 만들어내는 또 다른 예로 오토박싱(auto boxing)을 들 수 있다. 

오토박싱은 프로그래머가 기본 타입과 박싱된 기본 타입을 섞어 쓸 때 자동으로 상호 변환해부는 기술이다. 

아래 블로그 참조 

https://gyoogle.dev/blog/computer-language/Java/Auto%20Boxing%20&%20Unboxing.html

박싱된 기본 타입보다는 기본 타입을 사용하고, 의도치 않은 오토박싱이 숨어들지 않도록 주의하자. 

package me.whiteship.chapter01.item06;

public class Sum {
    private static long sum() {
        // TODO Long을 long으로 변경하여 실행해 보세요.
        Long sum = 0L;
        for (long i = 0; i <= Integer.MAX_VALUE; i++)
            sum += i;
        return sum;
    }

    public static void main(String[] args) {
        long start = System.nanoTime();
        long x = sum();
        long end = System.nanoTime();
        System.out.println((end - start) / 1_000_000. + " ms.");
        System.out.println(x);
    }
}

캐시 역시 메모리 누수를 일으키는 주범이다. 

운 좋게 캐시 외부에서 키를 참조하는 동안만(값이 아니다) 엔트리가 살아 있는 캐시가 필요한 상황이라면 WeakHashMap을 사용해 캐시를 만들자. 

WeakHashMap 에 대한 자세한 내용은 아래 블로그를 참조

https://bepoz-study-diary.tistory.com/340

 

[Java] 강한참조와 약한참조 그리고 WeakHashMap 에 대해

강한참조와 약한참조 그리고 WeakHashMap 에 대해 강한 참조 new 할당 후 새로운 객체를 만들어 해당객체를 참조하는 방식이다. 참조가 해제되지 않는 이상 GC의 대상이 되지 않는다. 약한 참조 Integer

bepoz-study-diary.tistory.com

백그라운드 스레드를 활용하는 방법도 있다 

@Test
    void backgroundThread() throws InterruptedException {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        PostRepository postRepository = new PostRepository();
        CacheKey key1 = new CacheKey(1);
        postRepository.getPostById(key1);

        Runnable removeOldCache = () -> {
            System.out.println("running removeOldCache task");
            Map<CacheKey, Post> cache = postRepository.getCache();
            Set<CacheKey> cacheKeys = cache.keySet();
            Optional<CacheKey> key = cacheKeys.stream().min(Comparator.comparing(CacheKey::getCreated));
            key.ifPresent((k) -> {
                System.out.println("removing " + k);
                cache.remove(k);
            });
        };

        System.out.println("The time is : " + new Date());

        executor.scheduleAtFixedRate(removeOldCache,
                1, 3, TimeUnit.SECONDS);

        Thread.sleep(20000L);

        executor.shutdown();
    }

메모리 누수의 세 번째 주범은 바로 리스너 혹은 콜백이라 부르는 것이다 클라이언트가 콜백을 등록만 하고 명확히 해지하지 않는다면, 뭔가 치해주지 않는 한 콜백은 계속 쌓여갈 것이다. 이럴 때 콜백을 약한 참조로 저장하면 가비지 컬렉터가 즉시 수거해간다. 

예를 들어 WeakHashMap에 키로 저장하면 된다.

public class ChatRoom {

    private List<WeakReference<User>> users;

    public ChatRoom() {
        this.users = new ArrayList<>();
    }

    public void addUser(User user) {
        this.users.add(new WeakReference<>(user));
    }

    public void sendMessage(String message) {
        users.forEach(wr -> Objects.requireNonNull(wr.get()).receive(message));
    }

    public List<WeakReference<User>> getUsers() {
        return users;
    }
}
package me.whiteship.chapter01.item07.listener;

import org.junit.jupiter.api.Test;

import java.lang.ref.WeakReference;
import java.util.List;

import static org.junit.jupiter.api.Assertions.*;

class ChatRoomTest {

    @Test
    void charRoom() throws InterruptedException {
        ChatRoom chatRoom = new ChatRoom();
        User user1 = new User();
        User user2 = new User();

        chatRoom.addUser(user1);
        chatRoom.addUser(user2);

        chatRoom.sendMessage("hello");

        user1 = null;

        System.gc();

        Thread.sleep(5000L);

        List<WeakReference<User>> users = chatRoom.getUsers();
        assertTrue(users.size() == 1);
    }

}

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

아래 블로그에 잘 나와있어서 참조합니다 

https://velog.io/@lychee/%EC%9D%B4%ED%8E%99%ED%8B%B0%EB%B8%8C-%EC%9E%90%EB%B0%94-%EC%95%84%EC%9D%B4%ED%85%9C-8.-finalizer%EC%99%80-cleaner-%EC%82%AC%EC%9A%A9%EC%9D%84-%ED%94%BC%ED%95%98%EB%9D%BC

 

[이펙티브 자바] 아이템 8. finalizer와 cleaner 사용을 피하라

자바에서 객체소멸은 가비지컬렉터가 담당하고,비메모리자원회수는 try-with-resources, try-finally로 해결한다.Finalizer는 예측 불가능하고, 위험하며, 대부분 불필요하다.자바에서 제공하는 두 가지

velog.io

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

- try-finally는 더 이상 최선의 방법이 아니다. 

- try-with-resources를 사용하면 코드가 더 짧고 분명해진다. 

 

전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다. 예외가 발생하거나 메서드에서 반환되는 경우를 포함해서 말이다. 

 // 코드 9-2 자원이 둘 이상이면 try-finally 방식은 너무 지저분하다! (47쪽)
    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();
        }
    }

try-with-resources를 사용하라 

   static String firstLineOfFile(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(
                new FileReader(path))) {
            return br.readLine();
        }
    }
// 코드 9-4 복수의 자원을 처리하는 try-with-resources - 짧고 매혹적이다! (49쪽)
    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);
        }
    }

예외를 잡아먹지 않는다. 

try-finally로 했을 경우 

public class BadBufferedReader extends BufferedReader {
    public BadBufferedReader(Reader in, int sz) {
        super(in, sz);
    }

    public BadBufferedReader(Reader in) {
        super(in);
    }

    @Override
    public String readLine() throws IOException {
        throw new CharConversionException();
    }

    @Override
    public void close() throws IOException {
        throw new StreamCorruptedException();
    }
}
  static String firstLineOfFile(String path) throws IOException {
        BufferedReader br = new BadBufferedReader(new FileReader(path));
//        try(BufferedReader br = new BadBufferedReader(new FileReader(path))) {
//            return br.readLine();
//        }
        try{
            return br.readLine();
        }finally {
            br.close();
        }
    }
Exception in thread "main" java.io.StreamCorruptedException
	at me.whiteship.chapter01.item09.suppress.BadBufferedReader.close(BadBufferedReader.java:21)
	at me.whiteship.chapter01.item09.suppress.TopLine.firstLineOfFile(TopLine.java:17)
	at me.whiteship.chapter01.item09.suppress.TopLine.main(TopLine.java:22)

try-with-resources를 사용하라 

package me.whiteship.chapter01.item09.suppress;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class TopLine {
    // 코드 9-1 try-finally - 더 이상 자원을 회수하는 최선의 방책이 아니다! (47쪽)
    static String firstLineOfFile(String path) throws IOException {
        try(BufferedReader br = new BadBufferedReader(new FileReader(path))) {
            return br.readLine();
        }
    }

    public static void main(String[] args) throws IOException {
        System.out.println(firstLineOfFile("pom.xml"));
    }
}
Exception in thread "main" java.io.CharConversionException
	at me.whiteship.chapter01.item09.suppress.BadBufferedReader.readLine(BadBufferedReader.java:16)
	at me.whiteship.chapter01.item09.suppress.TopLine.firstLineOfFile(TopLine.java:11)
	at me.whiteship.chapter01.item09.suppress.TopLine.main(TopLine.java:16)
	Suppressed: java.io.StreamCorruptedException
		at me.whiteship.chapter01.item09.suppress.BadBufferedReader.close(BadBufferedReader.java:21)
		at me.whiteship.chapter01.item09.suppress.TopLine.firstLineOfFile(TopLine.java:10)
		... 1 more
반응형
LIST