ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective Java] 2장 : 객체 생성과 파괴
    책/Effective Java 3판 2024. 2. 13. 23:27
    반응형

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

    장점

    • 이름을 가질 수 있음
    • 호출될 때마다 인스턴스를 생성하지 않아도 됨
    • 반환 타입의 하위 타입 객체를 반환할 수 있음
    • 입력 매개변수에 따라 다른 클래스를 반환할 수 있음
    • 정적 팩토리 메서드를 작성하는 시점에 반환할 객체의 클래스가 존재하지 않아도 됨

     단점

    • 상속 불가능
    • 프로그래머가 찾기 어려움
    public static void main(String[] args) {
            MyClass myClass = MyClass.getInstance();
    
            MyClass myClass1 = MyClass.getInstance();
    
            myClass.print("hello world");
            myClass1.print("sdfsdf");
    
            System.out.println(myClass.equals(myClass1));
            System.out.println(myClass.hashCode() + ", " + myClass1.hashCode());
        }
    
    public class MyClass {
    
        private static final MyClass INSTANCE = new MyClass();
    
        private MyClass() {
    
        }
    
        public void print(String s) {
            System.out.println(s);
        }
    
        public static MyClass getInstance() {
            return INSTANCE;
        }
    }

     

     

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

    • 생성자에 매개변수가 너무 많으면 필요 없는 부분까지도 고려해서 넣어줘야함

    점층적 생성자 패턴

    • 이를 해결하기 위해 점층적 생성자 패턴 (생성자 오버로딩) 을 사용했지만, 이 역시 필요없는 부분이 생길 수 있음
    public class Board {
        private Integer id;
        private String title;
        private String content;
        private String writer;
        private Integer likeCount;
    
        public Board(Integer id) {
            this(id, "");
        }
    
        public Board(Integer id, String title) {
            this(id, title, "");
        }
    
        public Board(Integer id, String title, String content) {
            this(id, title, content, "");
        }
    
        public Board(Integer id, String title, String content, String writer) {
            this(id, title, content, writer, 0);
        }
    
        public Board(Integer id, String title, String content, String writer, Integer likeCount) {
            this.id = id;
            this.title = title;
            this.content = content;
            this.writer = writer;
            this.likeCount = likeCount;
        }
    }
    

     

    자바 빈즈 방식

    • 빈 객체 만든 뒤 setter → 이 역시 불편하고, setter를 하나라도 빼먹으면 불완전한 상태의 객체를 사용하게 된다.
    • 자바 빈즈 방식(JavaBeans)
    public class Board {
        private Integer id = -1;
        private String title = "";
        private String content = "";
        private String writer = "";
        private Integer likeCount = 0;
    
        public Board() {}
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    
        public void setWriter(String writer) {
            this.writer = writer;
        }
    
        public void setLikeCount(Integer likeCount) {
            this.likeCount = likeCount;
        }
    }
    

     

    빌더(Builder) 패턴

    public class Board {
        private final Integer id;
        private final String title;
        private final String content;
        private final String writer;
        private final Integer likeCount;
        
        public static class Builder {
            // 필수
            private final Integer id;
    
            // 선택
            private String title = "";
            private String content = "";
            private String writer = "";
            private Integer likeCount = 0;
    
            public Builder(Integer id) {
                this.id = id;
            }
            
            public Builder title(String value) {
                title = value;
                return this;
            }
            
            public Builder content(String value) {
                content = value;
                return this;
            }
            
            public Builder writer(String value) {
                writer = value;
                return this;
            }
            
            public Builder likeCount(Integer value) {
                likeCount = value;
                return this;
            }
            
            public Board build() {
                return new Board(this);
            }
        }
        
        private Board(Builder builder) {
            this.id = builder.id;
            this.title = builder.title;
            this.content = builder.content;
            this.writer = builder.writer;
            this.likeCount = builder.likeCount;
        }
    }
    
    Board board = new Board.Builder(1)
                    .title("title")
                    .content("content")
                    .writer("writer")
                    .likeCount(0)
                    .build();
    
    • 객체를 생성하기 위해 빌더를 생성해야함 → 성능이 약간 떨어질 수 있음
    • 그러나 매개변수의 개수가 많거나 계속 많아지는 경우 매우 유용
    • 각 매개변수를 저장하는 메서드에서 유효성 검사를 하고, build() 에서 최종 불변식을 검사할 수 있다.

     

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

    • 싱글톤(Singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스
    • 클래스를 싱글톤으로 만들면 클라이언트 테스트가 어렵다.

    첫번째 방식 - private 생성자, public static final 필드

    public class MyClass {
        public static final MyClass INSTANCE = new MyClass();
        
        private MyClass() {}
    }
    
    • private 생성자는 MyClass를 INSTANCE에 초기화할 때 딱 한번만 사용됨
    • 장점
      • 간결함
      • 누가봐도 싱글턴임이 보임

    두번째 방식 - private 생성자, 정적 팩터리 메서드

    public class MyClass {
        private static final MyClass INSTANCE = new MyClass();
    
        private MyClass() {}
    
        public static MyClass getInstance() {
            return INSTANCE;
        }
    }
    
    • 장점
      • API 손대지 않고도 싱글턴 아니게 바꿀 수 있다.
      • 제네릭 싱글턴으로 바꿀 수 있다.
      • 정적 팩터리 메서드 참조를 공급자(Supplier)로 사용 가능 → MyClass::getInstance

    세번째 방식 - 열거형 사용

    public enum MyClass {
        INSTANCE;
    
        public void print(String s) {
            System.out.println(s);
        }
    }
    
    MyClass m = MyClass.INSTANCE;
    
    m.print("hello world");
    
    • 직렬화에도 끄떡없고, 리플렉션 공격에서도 완벽히 막아줌
    • 약간 사용하기 어색함
    • 상속 같은게 필요한 경우 사용 불가능

     

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

    • 정적 필드, 메서드로만 이루어진 유틸리티 클래스는 굳이 인스턴스가 필요 없다.
    • 간단하게 private 생성자를 만들면 쉽게 인스턴스화를 막을 수 있다.
    • private 생성자는 상속을 막는 효과도 있다 → 모든 생성자는 상위 클래스의 생성자를 호출하는데, 이 때 호출이 불가능하기 때문이다.

     

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

    • 정적 유틸리티 클래스나 싱글턴의 경우 사용하는 자원에 따라 동작 방식이 달라지는 클래스에서는 적합하지 않다.
    // 정적 유틸리티 클래스
    public class SpellChecker {
        private static final Lexicon dictionary = new Lexicon();
        
        private SpellChecker() { }
        
        public static Boolean isValid(String word) {
            return false;
        }
    }
    
    // 싱글턴
    public class SpellChecker {
        private final Lexicon dictionary = new Lexicon();
    
        private SpellChecker() { }
        public static final SpellChecker INSTANCE = new SpellChecker();
        
        public static Boolean isValid(String word) {
            return false;
        }
    }
    
    • 이 경우, dictionary가 다양하게 사용되어야 하는 경우 적합하지 않다.
    • 만약 필드를 final로 하지 않고 수정할 수 있는 메서드를 추가한다면?
      • 멀티스레드 환경에서는 적합하지 않음

    생성자 주입

    public class SpellChecker {
        private final Lexicon dictionary;
    
        public SpellChecker(Lexicon dictionary) {
            this.dictionary = dictionary;
        }
        
        public Boolean isValid(String word) {}
    }
    
    • 인스턴스를 생성할 때 생성자에 필요 자원을 넘겨주는 방식
    • 클래스가 직접 new로 만드는 것 또한 안좋음 → 불변성이 완벽히 깨짐
    • 생성자에 Supplier<? extends dictionary> 를 통해 팩토리 메서드를 받는 것 또한 좋다.
      • 팩토리 메서드로 해당 객체는 싱글톤으로 만들 수 있기 때문 ← 자원 낭비 X

     

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

    • 똑같은 기능 객체를 매번 생성하는 것보다는 당연히 재사용하는 게 좋다.
      • 불변 객체는 언제든 재사용 가능
        • String s = new String(”b”); → 완전히 비효율 - “b” 자체가 String()으로 만들려는 것과 똑같다.
        • String s = “b”; 가 더 나음 → “b” 가 또 나올 경우 이를 재사용
      • 가변 객체 또한 변경만 되지 않는다면 재사용이 가능
    • 오토 박싱은 성능에서는 좋지 않다.
      • 위의 경우 i가 더해질 때마다 매번 새로운 Long 객체를 만들어 더하게 된다.
    • Long sum = 0L; for (long i = 0; i < Integer.MAX_VALUE; i++) sum += i;
    • 그럼 방어적 복사는 하지 말아야하나?
      • 방어적 복사를 안했을 때 피해 >>>>> 불필요 객체 생성 피해
        • 전자는 심각한 오류 초래할 수도, 후자는 그냥 성능만 좀 안좋아짐

     

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

    public class MyStack {
        private Object[] elements;
        private int size = 0;
        private static final int DEFAULT_INITIAL_CAPACITY = 16;
    
        public MyStack() {
            elements = new Object[DEFAULT_INITIAL_CAPACITY];
        }
    
        public void push(Object e) {
            ensureCapacity();
            elements[size++] = e;
        }
    
        public Object pop() {
            if (size == 0) {
                throw new EmptyStackException();
            }
            return elements[--size];
        }
    
        private void ensureCapacity() {
            if (elements.length == size) {
                elements = Arrays.copyOf(elements, 2 * size + 1);
            }
        }
    }
    
    • 위 코드의 문제 : pop할 때 그냥 size— 만 수행한다 → 가비지 컬렉터가 해당 위치에 있는 객체를 수거하지 않는다.
      • 진짜 잘못하면 OutOfMemoryError 나올수도 있음
      • 왜 그런가?
        • 배열 안에 있으니 가비지 컬렉터 입장에서는 활성화된 객체라고 판단
      • 해결
        • null로 초기화해주면 해당 위치의 객체는 연결이 끊김 → 가비지 컬렉터가 수거해감
    public Object pop() { 
    	if (size == 0) { 
    		throw new EmptyStackException(); 
    	} 
    	Object result = elements[--size]; 
    	elements[size] = null; // 핵심 
    	return result; 
    }
    • 그렇다고 매번 null로 초기화해줄 필요는 없음
      • 위 경우 배열을 통해 스택이 직접 메모리를 관리하기 때문에 문제가 발생한 것
      • 즉, 직접 메모리를 관리할 때에만 사용
    • 캐싱의 경우 WeakHashMap을 사용하면 알아서 수거해감
      • 그러나 이 경우에만 유용하다고 한다.

     

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

    • try-finally의 문제점
      • 사용 자원이 많아질수록 코드가 더럽다.
      • close() 자체에서도 예외가 발생할 수도 있는데, 이 경우 디버깅이 매우 어렵다.
    • try-with-resources
      • 해당 자원이 AutoCloseable 인터페이스를 구현해야함
      • try() { } 에서 ( ) 안에 가용 자원을 넣어주면 된다.
      • 여기에서도 catch를 사용할 수 있다.
    static void copy(String src, String dst) throws IOException {
            try (InputStream in = new FileInputStream(src);
                 OutputStream out = new FileOutputStream(dst)) {
                byte[] buf = new byte[10];
                int n;
                while ((n = in.read(buf)) >= 0) {
                    out.write(buf, 0, n);
                }
            }
        }

     

     

     

     

    https://product.kyobobook.co.kr/detail/S000001033066

    반응형
Designed by Tistory.