Dev Book Review/Effective Java

[Effective Java] Chapter 4: 클래스와 인터페이스

item15. 클래스와 멤버의 접근 권한을 최소화 하라 프로그램 요소의 접근성은 가능한 한 최소한으로 하라. 꼭 필요한 것만 골라 최소한의 public API를 설계하자. 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야한다. public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드를 가져서는 안 된다. public static final 필드가 참조하는 객체가 불변인지 확인하라. Link : https://jyami.tistory.com/77 [Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라 잘 설계된 컴포넌트 : 클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼..

[Effective Java] Chapter 4: 클래스와 인터페이스

728x90

item15. 클래스와 멤버의 접근 권한을 최소화 하라

  • 프로그램 요소의 접근성은 가능한 한 최소한으로 하라.
  • 꼭 필요한 것만 골라 최소한의 public API를 설계하자.
  • 그 외에는 클래스, 인터페이스, 멤버가 의도치 않게 API로 공개되는 일이 없도록 해야한다.
  • public 클래스는 상수용 public static final 필드 외에는 어떠한 public 필드를 가져서는 안 된다.
  • public static final 필드가 참조하는 객체가 불변인지 확인하라.
  • Link : https://jyami.tistory.com/77
 

[Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라

잘 설계된 컴포넌트 : 클래스 내부 데이터와 내부 구현정보를 외부 컴포넌트로부터 얼마나 잘 숨겼는가 오직 API를 통해서만 다른 컴포넌트와 소통한다. [정보은닉, 캡슐화] 1. 정보 은닉의 장점 시스템 개발 속도..

jyami.tistory.com

 

item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

  • public 클래스는 절대 가변 필드를 직접 노출해서는 안 된다.
  • 불변 필드라면 노출해도 덜 위험하지만 완전히 안심할 수는 없다.
  • 하지만 Package-private 클래스나 private 중첩 클래스에서는 종종(불변이든 가변이든) 필드를 노출하는 편이 나을 때도 있다.
  • Link : https://jyami.tistory.com/78
 

[Effective Java] Item16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라

1. public 클래스의 가변 필드 절대 가변 필드를 public으로 노출하면 안된다. 캡슐화의 이점을 제공하지 못한다. API를 수정하지 않고는 내부 표현을 바꿀 수 없다. 불변식을 보장할 수 없다. 외부에서 필드에 접..

jyami.tistory.com

 

item17. 변경 가능성을 최소화하라

  • 클래스가 꼭 필요한 경우가 아니면 불변이어야 한다. (getter가 있다고 해서 setter를 무조껀 만들지는 말자)
  • 무거운 값객체의 경우 성능때문에 어쩔수 없다면, 불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하자.
  • 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자
  • 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
  • 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야한다.
  • Link : https://jyami.tistory.com/79
 

[Effective Java] item17. 변경가능성을 최소화하라

불변 클래스 : 그 인스턴스의 내부 값을 수정할 수 없는 클래스 예 ) String, Wrapper Class, BigInteger, BigDecimal - 설계 구현 사용이 쉽다. 오류 여지가 적고 안전하다. 1. 불변 클래스 5가지 규칙 ㄱ. 객체..

jyami.tistory.com

 

item18. 상속보다는 컴포지션을 사용하라

  • 상속은 강력하지만 캡슐화를 깨진다.
  • 상속은 상위 클래스와 하위 클래스가 순수한 is-a 관계일 때만 사용해야한다.
  • is-a 관계여도 안심할 수만은 없다 : 하위 클래스의 패키지가 상위클래스와 다르고, 상위클래스가 확장을 위해 설계되지 않았다면 문제가 될 수 있다.
  • 상속의 취약점을 피하려면 상속대신 컴포지션과 전달을 사용하자
  • 래퍼 클래스로 구현할 적당한 인터페이스가 있으면 컴포지션을 특히 더 사용하자
  • 래퍼클래스는 해위 클래스보다 견고하고 강력하다.
  • Link : https://jyami.tistory.com/80
 

[Effective Java] Item18. 상속보다는 컴포지션을 사용하라

상속이 안전 할 때 상위 클래스와 하위클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서 사용한다. 확장할 목적으로 설계되었고 문서화도 잘되었다. 상속이 안전하지 않을때 다른 패키

jyami.tistory.com

 

item19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

  • 상속용 클래스를 설계하는건 만만치 않다.
  • 클래스 내부에서 스스로를 어떻게 사용하는지 (자기 사용패턴) 모두 문서로 남겨야한다.
  • 문서화 한 것은 그 클래스가 쓰이는 한 반드시 지킨다.
  • 지키지 않으면 그 내부 구현 방식을 믿고 활용하던 하위클래스를 오동작하게 만들 수 있다.
  • 다른 이가 효율좋은 하위 클래스를 만들 수 있도록 일부 메서드를 protected로 제공할 수도 있다.
  • 클래스를 확장해야 할 명확한 이유가 떠오르지 않으면 상속을 금지하자
  • 상속을 금지하려면 클래스를 final로 선언하거나, 생성자 모두를 외부에서 접근할 수 없게 만들면 된다.
  • Link : https://jyami.tistory.com/81
 

[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

1. 재정의 메서드의 문서화 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야한다. 재정의 가능 메서드 : 호출 메서드의 API 설명에 기술, 호출 순서, 각각의..

jyami.tistory.com

 

item20. 추상 클래스 보다는 인터페이스를 우선하라

  • 다중 구현용 타입으로 인터페이스가 적합하다.
  • 복잡한 인터페이스라면 구현하는 수고를 덜어주는 골격 구현을 함께 제공하는 방법을 고려해보자
  • 골격 구현은 '가능한 한' 인터페이스의 디폴트 메서드로 제공하여 그 인터페이스를 구현한 모든 곳에서 활용하도록 하는 것이 좋다.
  • '가능한 한'의 이유는, 인터페이스에 걸려잇는 구현상의 제약 때문에 골격 구현을 추상 클래스로 제공하는 경우가 더 흔하기 때문이다.
  • Link : https://jyami.tistory.com/82
 

[Effective Java] Item20. 추상 클래스 보다는 인터페이스를 우선하라

자바의 다중 구현 메커니즘 : 둘다 인스턴스 메서드를 구현 형태로 제공할 수 있다 (default method) 인터페이스 : 다중 상속, 같은 타입 취급 추상클래스 : 단일 상속, 하위 클래스 (상하 관계) 1. 인터

jyami.tistory.com

 

item21. 인터페이스는 구현하는 쪽을 생각해 설계하라

  • 디폴트 메서드는 구현 클래스에 대해 아무것도 모른채 합의 없이 무작정 '삽입'될 뿐이다
  • 범용적으로 구현하겠지만, 모든 구현체와 어울리는 것은 아니기 때문이다.
  • 꼭 필요한 경우가 아니라면 디폴트메서드를 기존인터페이스에서 추가하는건 금하자.
  • 새 인터페이스 만들때는 유용하다
  • 인터페이스 설계는 세심한 주의를 기울이자 : 3가지 구현체 만들어보기
  • Link : https://jyami.tistory.com/83
 

[Effective Java] item21. 인터페이스는 구현하는 쪽을 생각해 설계하라

생각할 수 있는 상황에서 불변식을 해치지 않는 디폴트 메서드 작성은 어렵다. 디폴트 메서드는 구현 클래스에 대해 아무 것도 모른채 합의 없이 무작정 '삽입'될 뿐이다. Java8 : 컬렉션 인터페이

jyami.tistory.com

 

item22. 인터페이스는 타입을 정의하는 용도로만 사용하라

  • 인터페이스는 타입을 정의하는 용도로만 사용해야한다. 상수 공개용 수단으로 사용하지 말자ㄴ
  • 상수 인터페이스는 만들지 말자
  • 상수 공개를 원하면, 연관 클래스, 이넘, 인스턴스화 불가 유틸 클래스를 이용하자
  • Link : https://jyami.tistory.com/84
 

[Effective Java] item22. 인터페이스는 타입을 정의하는 용도로만 사용하라

인터페이스의 용도 : 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해준다. 상수인터페이스는 만들지 말자 외부 인터페이스가아닌 내부구현에 해당하며 클래스의 API로 노출하는 행위이다. 상수를..

jyami.tistory.com

 

item23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라

  • 태그달린 클래스를 써야하는 상황은 거의 없다.
  • 새로운 클래스를 작성하는데 태그 필드가 등장한다면 태그를 없애고 계층 구조로 대체하는 방법을 생각해보자.
  • 기존 클래스가 태그 필드를 사용하고 있다면 계층 구조로 리팩터링 하는걸 고민하자
  • Link : https://jyami.tistory.com/85
 

[Effective Java] item23. 태그 달린 클래스보다는 클래스 계층 구조를 활용하라하라

1. 태그 달린 클래스 쓸데없는 코드가 너무 많다 : 열거 타입선언, 태그 필드, switch문 장황하고 오류를 내기 쉽고 비효율적이다. 클래스 계층 구조를 어설프레 흉내낸 것이다. class Figure { enum Shape { RECT..

jyami.tistory.com

 

item24. 멤버 클래스는 되도록 static으로 만들어라

  • 중첩 클래스에는 4가지가 있으며 각각의 쓰임이 다르다
  • 멤버 클래스 : 메서드 밖에서도 사용해야하거나, 메서드 안에 정의하기 너무 길때
  • 비정적 멤버 클래스 : 멤버 클래스의 인스턴스 각각이 바깥 인스턴스를 참조할 때 / 그렇지 않으면 정적 멤버 클래스
  • 익명 클래스 : 중첩 클래스가 한 메서드 안에서만 쓰이면서 그 인스턴스를 생성하는 지점이 단 한곳이고, 해당 타입으로 쓰기에 적합한 클래스나 인터페이스가 있을 때 / 그렇지 않으면 지역 클래스
  • Link : https://jyami.tistory.com/86
 

[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라

1. 중첩클래스란 중첩 클래스(nested class) : 다른 클래스 안에 정의된 클래스 정적 멤버 클래스 (비정적) 멤버 클래스 익명 클래스 지역클래스 정적 멤버 클래스를 제외한 나머지는 내부 클래스라고 한다 (inner..

jyami.tistory.com

 

item25. 톱레벨 클래스는 한 파일에 하나만 담으라

  • 소스파일 하나에는 반드시 톱레벨 클래스(인터페이스)를 하나만 담자
  • 이규칙만 따른다면 컴파일러가 한 클래스에 대한 정의를 여러개 만들어내는 일은 사라진다.
  • 소스 파일을 어떤 순서로 컴파일하든 바이너리 파일이나 프로그램의 동작이 달라지는 일은 결코 일어나지 않는다.
  • Link : https://jyami.tistory.com/87
 

[Effective Java] item25. 톱레벨 클래스는 한 파일에 하나만 담으라

소스파일 하나에 톱레벨 클래스를 여러개 선언하더라도 자바 컴파일러는 불평하지 않는다. 다만 위와 같이 이름이 중복되는 경우 컴파일 에러가 발생하게된다. 컴파일러에 어느 소스파일을 먼저 건네느냐에 따라..

jyami.tistory.com

 

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item25. 톱레벨 클래스는 한 파일에 하나만 담으라

소스파일 하나에 톱레벨 클래스를 여러개 선언하더라도 자바 컴파일러는 불평하지 않는다. 다만 위와 같이 이름이 중복되는 경우 컴파일 에러가 발생하게된다. 컴파일러에 어느 소스파일을 먼저 건네느냐에 따라 동작이 달라지므로 반드시 바로잡아야한다. 해결책 톱레벨 클래스들을 서로 다른 소스 파일로 분리한다 다른 클래스에 딸린 부차적인 클래스라면 정적 멤버 클래스를 사용한다. private으로 선언하여 접근 범위도 최소로 관리하자

[Effective Java] item25. 톱레벨 클래스는 한 파일에 하나만 담으라

728x90

소스파일 하나에 톱레벨 클래스를 여러개 선언하더라도 자바 컴파일러는 불평하지 않는다.

  • 다만 위와 같이 이름이 중복되는 경우 컴파일 에러가 발생하게된다.
  • 컴파일러에 어느 소스파일을 먼저 건네느냐에 따라 동작이 달라지므로 반드시 바로잡아야한다.

 

해결책

  • 톱레벨 클래스들을 서로 다른 소스 파일로 분리한다
  • 다른 클래스에 딸린 부차적인 클래스라면 정적 멤버 클래스를 사용한다.

    private으로 선언하여 접근 범위도 최소로 관리하자

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라

1. 중첩클래스란 중첩 클래스(nested class) : 다른 클래스 안에 정의된 클래스 정적 멤버 클래스 (비정적) 멤버 클래스 익명 클래스 지역클래스 정적 멤버 클래스를 제외한 나머지는 내부 클래스라고 한다 (inner class) 2. 정적 멤버 클래스 일반 클래스와 다른점 다른 클래스 안에 선언된다. 바깥 클래스의 private 멤버에도 접근할 수 있다. 다른 정적 멤버와 똑같은 접근 규칙을 적용받는다. 바깥 클래스와 함께 쓰이는 public 도우미 클래스로 사용된다. public class Calculator{ public enum Operator{ // 열거타입도 정적 멤버 클래스이다 PLUS(),MINUS() } public static class Operator{ // 대부분 이처럼 선언..

[Effective Java] item24. 멤버 클래스는 되도록 static으로 만들어라용도로만 사용하라

728x90

1. 중첩클래스란

중첩 클래스(nested class) : 다른 클래스 안에 정의된 클래스

  • 정적 멤버 클래스
  • (비정적) 멤버 클래스
  • 익명 클래스
  • 지역클래스

정적 멤버 클래스를 제외한 나머지는 내부 클래스라고 한다 (inner class)

 

2. 정적 멤버 클래스

일반 클래스와 다른점

  • 다른 클래스 안에 선언된다.
  • 바깥 클래스의 private 멤버에도 접근할 수 있다.

다른 정적 멤버와 똑같은 접근 규칙을 적용받는다.

바깥 클래스와 함께 쓰이는 public 도우미 클래스로 사용된다.

public class Calculator{
  public enum Operator{ // 열거타입도 정적 멤버 클래스이다
    PLUS(),MINUS()
  }
  public static class Operator{ // 대부분 이처럼 선언

  }
}

 

3. 비정적 멤버 클래스

정적 멤버 클래스와의 차이

  • 구문상 차이 : static이 있고 없고 차이
  • 의미상 차이 : 비정적 멤버 클래스의 인스턴스와 바깥 클래스의 인스턴스와 연결된다.
  • 정규화된 this를 이용해 바깥 인스턴스의 인스턴스 메서드 호출이 가능하다. 클래스명.this

개념상 중첩 클래스의 인스턴스가 바깥 클래스의 인스턴스와 독립적으로 존재할 수 있다면 정적 멤버 클래스로 만들어야한다.

비정적 멤버클래스는 바깥 인스턴스 없이는 생성할 수 없다.

public class NestedNonStaticExample {
    private final String name;

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

    public String getName() {
        // 비정적 멤버 클래스와 바깥 클래스의 관계가 확립되는 부분
        NonStaticClass nonStaticClass = new NonStaticClass("nonStatic : ");
        return nonStaticClass.getNameWithOuter();
    }

    private class NonStaticClass {
        private final String nonStaticName;

        public NonStaticClass(String nonStaticName) {
            this.nonStaticName = nonStaticName;
        }

        public String getNameWithOuter() {
            // 정규화된 this 를 이용해서 바깥 클래스의 인스턴스 메서드를 사용할 수 있다.
            return nonStaticName + NestedNonStaticExample.this.getName();
        }
    }
}

비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계는 멤버 클래스가 인스턴스화될 때 확립되며, 더이상 변경할 수 없다.

드물게 직접 [바깥 인스턴스의 클래스].new MebmerClass(agrs) 를 호출해 수동으로 만든다. 그러나 이 관계정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며, 생성시간도 더 걸린다.

NestedNonStaticExample nestedNonStaticExample = new NestedNonStaticExample("name");
nestedNonStaticExample.new NonStaticPublicClass();

 

비정적 멤버 클래스의 쓰임 : 어댑터의 역할

  • 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용한다.
  • Map 인터페이스의 구현체 : 컬렉션 뷰를 구현할 때 비정적 멤버 클래스를 사용
  • 컬렉션 인터페이스 구현체 : 자신의 반복자 구현

 

멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조껀 static을 붙여서 정적 멤버클래스로 만들자

  • 숨은 외부 참조를 갖게되며, 이 참조를 저장하기 위한 시간과 공간이 소비된다.
  • GC가 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있다.

 

4. 익명 클래스

  • 이름이 없다!
  • 바깥 클래스의 멤버도 아니다.
  • 쓰이는 시점에 선언과 동시에 인스턴스가 만들어진다.
  • 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스 참조가 가능하다
  • 상수 정적변수 (static final) 외에는 정적 변수를 가질 수 없다.
public class AnonymousExample {
    private double x;
    private double y;

    public double operate() {
        Operator operator = new Operator() {
            @Override
            public double plus() {
                System.out.printf("%f + %f = %f\n", x, y, x + y);
                return x + y;
            }
            @Override
            public double minus() {
                System.out.printf("%f - %f = %f\n", x, y, x - y);
                return x - y;
            }
        };
        return operator.plus();
    }
}

interface Operator {
    double plus();
    double minus();
}
 

익명 클래스의 제약사항

  • 선언한 지점에서만 인스턴스를 만들수 있다.
  • Instanced 검사, 클래스 이름이 필요한 검사를 할 수 없다.
  • 여러 인터페이스를 구현할 수 없다. 구현과 동시에 다른클래스 상속도 불가능하다.
  • 익명 클래스 사용 클라이언트는 사용하는 익명 클래스가 상위타입에서 상속한 멤버외에는 호출이 불가능하다
  • 표현식 중간에 등장해 10줄이 넘어가면 가독성이 좋지 않다.

사용 예시

  • 람다(자바7) 등장 이전에는 작은 함수 객체나 처리 객체 구현에 사용되었다 : 람다를 쓰자
  • 정적 팩터리 메서드를 구현할 때 사용되기 도한다.

 

5. 지역클래스

  • 가장 드물게 사용된다.
  • 지역변수를 선언할 수 있는 곳이면 어디서든 선언할 수 있다
  • 지역변수와 유효범위가 같다
  • 멤버 클래스 유사도 : 이름이 있고 반복해서 사용이 가능하다
  • 익명 클래스 유사도 : 비정적 문맥에서 사용할 때만 바깥 인스턴스 참조가 가능하다
  • 익명 클래스 유사도 : 정적 멤버는 가질 수 없고, 가독성을 위해 짧게 작성한다.
public class LocalExample {
    private int number;

    public LocalExample(int number) {
        this.number = number;
    }

    public void foo() {
        // 지역변수처럼 선언해서 사용할 수 있다.
        class LocalClass {
            private String name;

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

            public void print() {
                // 비정적 문맥에선 바깥 인스턴스를 참조 할 수 있다.
                System.out.println(number + name);
            }
        }
        LocalClass localClass = new LocalClass("local");
        localClass.print();
    }
}

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item23. 태그 달린 클래스보다는 클래스 계층 구조를 활용하라하라

1. 태그 달린 클래스 쓸데없는 코드가 너무 많다 : 열거 타입선언, 태그 필드, switch문 장황하고 오류를 내기 쉽고 비효율적이다. 클래스 계층 구조를 어설프레 흉내낸 것이다. class Figure { enum Shape { RECTANGLE, CIRCLE }; // 태그 필드 - 현재 모양을 나타낸다. final Shape shape; // 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다. double length; double width; // 다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다. double radius; // 원용 생성자 Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } // 사각형용 ..

[Effective Java] item23. 태그 달린 클래스보다는 클래스 계층 구조를 활용하라하라

728x90

1. 태그 달린 클래스

  • 쓸데없는 코드가 너무 많다 : 열거 타입선언, 태그 필드, switch문
  • 장황하고 오류를 내기 쉽고 비효율적이다.
  • 클래스 계층 구조를 어설프레 흉내낸 것이다.
class Figure {
    enum Shape { RECTANGLE, CIRCLE };
    // 태그 필드 - 현재 모양을 나타낸다.
    final Shape shape;

    // 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다.
    double length;
    double width;

    // 다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다.
    double radius;

    // 원용 생성자
    Figure(double radius) {
        shape = Shape.CIRCLE;
        this.radius = radius;
    }

    // 사각형용 생성자
    Figure(double length, double width) {
        shape = Shape.RECTANGLE;
        this.length = length;
        this.width = width;
    }

    double area() {
        switch(shape) {
            case RECTANGLE:
                return length * width;
            case CIRCLE:
                return Math.PI * (radius * radius);
            default:
                throw new AssertionError(shape);
        }
    }
}

 

2. 클래스 계층 구조로의 리팩터링

a. root가 될 추상 클래스를 정의하자

b. 태그에 따라 행동이 달라지던 메서드는 추상 메서드로 구현하자 : 그렇지않으면 일반메서드

c. 공통된 필드는 모두 root 클래스로 올리자

d. 구체 클래스에서 추상클래스를 의미에 맞게 정의하자

abstract class Figure {
    abstract double area();
}

class Rectangle extends Figure {
    final double length;
    final double width;

    Rectangle(double length, double width) {
        this.length = length;
        this.width  = width;
    }
    @Override double area() { return length * width; }
}

class Circle extends Figure {
    final double radius;

    Circle(double radius) { this.radius = radius; }

    @Override double area() { return Math.PI * (radius * radius); }
}

class Square extends Rectangle {
    Square(double side) {
        super(side, side);
    }
}

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item22. 인터페이스는 타입을 정의하는 용도로만 사용하라

인터페이스의 용도 : 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해준다. 상수인터페이스는 만들지 말자 외부 인터페이스가아닌 내부구현에 해당하며 클래스의 API로 노출하는 행위이다. 상수를 공개하고 싶을때 연관된 클래스나 인터페이스에서 공개 : Integer.MIN_VALUE Enum 인스턴스화 할수 없는 유틸 클래스 public final class StringConstants{ public static final String BRANK = ""; private StringConstants(){} }

[Effective Java] item22. 인터페이스는 타입을 정의하는 용도로만 사용하라

728x90

인터페이스의 용도 : 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해준다.

 

상수인터페이스는 만들지 말자

  • 외부 인터페이스가아닌 내부구현에 해당하며 클래스의 API로 노출하는 행위이다.
 

상수를 공개하고 싶을때

  • 연관된 클래스나 인터페이스에서 공개 : Integer.MIN_VALUE
  • Enum
  • 인스턴스화 할수 없는 유틸 클래스
  public final class StringConstants{
    public static final String BRANK = "";
    private StringConstants(){}
  }

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item21. 인터페이스는 구현하는 쪽을 생각해 설계하라

생각할 수 있는 상황에서 불변식을 해치지 않는 디폴트 메서드 작성은 어렵다. 디폴트 메서드는 구현 클래스에 대해 아무 것도 모른채 합의 없이 무작정 '삽입'될 뿐이다. Java8 : 컬렉션 인터페이스 다수에 디폴트 메서드 추가 범용적으로 구현되어있지만, 모든 구현체와 어울리는 것은 아니다. 1. SynchronizedCollection 아파치 버전 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능 위임 removeIf : 재정의 되어있지 않았음 (동기화에 대해 모르는 상태) 2. 인터페이스 설계에 주의하자 a. 디폴트 메서드 디폴트 메서드는 (컴파일에 성공하더라도) 기존 구현체에 런타임 오류를 일으킬 수 있다. 꼭 필요한 경우..

[Effective Java] item21. 인터페이스는 구현하는 쪽을 생각해 설계하라

728x90

생각할 수 있는 상황에서 불변식을 해치지 않는 디폴트 메서드 작성은 어렵다.

  • 디폴트 메서드는 구현 클래스에 대해 아무 것도 모른채 합의 없이 무작정 '삽입'될 뿐이다.
  • Java8 : 컬렉션 인터페이스 다수에 디폴트 메서드 추가
  • 범용적으로 구현되어있지만, 모든 구현체와 어울리는 것은 아니다.

 

1. SynchronizedCollection

아파치 버전

  • 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공
  • 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능 위임
  • removeIf : 재정의 되어있지 않았음 (동기화에 대해 모르는 상태)


2. 인터페이스 설계에 주의하자

a. 디폴트 메서드

  • 디폴트 메서드는 (컴파일에 성공하더라도) 기존 구현체에 런타임 오류를 일으킬 수 있다.
  • 꼭 필요한 경우가 아니면 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피하자
  • 새로운 인터페이스를 만들때는 아주 유용한 수단이다
  • 기존 메서드를 제거하거나 수정하는 용도가 아니다.
  • 클라이언트에 영향이 어마어마하게 가기 때문이다.
 
b. 설계 시 주의

디폴트 메서드가 생겼더라도 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야한다.

  • 인터페이스 설계시 검증을 위해 최소 3가지의 구현체를 만들어보자
  • 인터페이스를 릴리즈한 후라도 결함을 수정하는게 가능한 경우도 있지만, 절대 이 가능성에 기대지 말자.

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] Item20. 추상 클래스 보다는 인터페이스를 우선하라

자바의 다중 구현 메커니즘 : 둘다 인스턴스 메서드를 구현 형태로 제공할 수 있다 (default method) 인터페이스 : 다중 상속, 같은 타입 취급 추상클래스 : 단일 상속, 하위 클래스 (상하 관계) 1. 인터페이스의 장점 a. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 인터페이스 : 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다. 추상클래스 : 계층 구조상 확장시킨 클래스의 공통 조상이 되어, 클래스 계층 구조를 생각해야한다. b. 믹스인 정의에 안성맞춤이다. 믹스인 : 클래스가 구현할 수 있는 타입. 믹스인을 구현한 클래스에 원래의 '주된 타입'외에도 선택적 기능을 '혼합'하여 사용한다. 추상클래스 : 기존 클래스에 덧씌울 수 없..

[Effective Java] Item20. 추상 클래스 보다는 인터페이스를 우선하라

728x90

자바의 다중 구현 메커니즘 : 둘다 인스턴스 메서드를 구현 형태로 제공할 수 있다 (default method)

  • 인터페이스 : 다중 상속, 같은 타입 취급
  • 추상클래스 : 단일 상속, 하위 클래스 (상하 관계)

 

1. 인터페이스의 장점

a. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다.

  • 인터페이스 : 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다.
  • 추상클래스 : 계층 구조상 확장시킨 클래스의 공통 조상이 되어, 클래스 계층 구조를 생각해야한다.
 

b. 믹스인 정의에 안성맞춤이다.

믹스인 : 클래스가 구현할 수 있는 타입.
믹스인을 구현한 클래스에 원래의 '주된 타입'외에도 선택적 기능을 '혼합'하여 사용한다.

추상클래스 : 기존 클래스에 덧씌울 수 없다. 클래스가 두 부모를 섬길 수 없는 단일 상속의 특징이 있다.

Comparable, Clonable, Serializable

 

c. 인터페이스로는 계층 구조가 없는 타입 프레임 워크를 만들 수 있다.

public interface SingerSongWriter extends Singer, SongWriter {
    void strum();
    void actSensitive();
}
public abstract class Singer {
    abstract void sing(String s);
}

public abstract class SongWriter {
    abstract void compose(int chartPosition);
}

public abstract class SingerSongWriter {
    abstract void strum();
    abstract void actSensitive();
    abstract void Compose(int chartPosition);
    abstract void sing(String s);
}

추상 클래스로 만들면 다중상속이 불가능해, 새로운 추상클래스를 만들어서 클래스 계층을 표현할 수 밖에 없다.

따라서 이 계층구조를 만들기 위해 많은 조합이 필요하고, 결국엔 고도비만 계층 구조가 만들어진다. (조합 폭발)

 

d. 래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이된다.

타입을 추상클래스로 정의했을 때 : 기능 추가 방법은 상속뿐이다. -> 활용도가 떨어지고 쉽게 깨진다

래퍼클래스의 활용도가 더 높다 : 아이템 18

 

2. 인터페이스의 디폴트 메서드 제약

  • 디폴트 메서드를 제공할 때는 @implSpec 을 붙여 문서화한다.
  • equals와 hashCode는 디폴트 메서드로 정의하면 안된다.
  • 인터페이스는 인스턴스 필드를 가질 수 없다.
  • public이 아닌 정적 멤버도 가질 수 없다.
  • 우리가 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없다.

 

3. 인터페이스와 추상골격 구현 클래스

a. 개념

인터페이스 : 타입 + 디폴트 메서드

골격구현 클래스 : 나머지 메서드들까지 구현

인터페이스 구현에 필요한 대부분의 일이 완료된다 -> 템플릿 메서드 패턴

네이밍 관례 : Abstract[Interface명] : AbstractCollection, AbstractSet, AbstractList, AbstractMap

 

b. 장점

추상 클래스처럼 구현을 도와주는 동시에, 추상클래스로 타입을 정의할 때 따라오는 심각한 제약에서 자유롭다.

 

4. 시뮬레이트한 다중 상속(simulated multiple inheritance)

골격 구현 클래스를 우회적으로 이용하는 방식

인터페이스를 구현한 클래스에서, 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달한다.

public interface Vending {
    void start();
    void chooseProduct();
    void stop();
    void process();
}

public abstract class AbstractVending implements Vending {
    @Override
    public void start() {
        System.out.println("vending start");
    }

    @Override
    public void stop() {
        System.out.println("stop vending");
    }

    @Override
    public void process() {
        start();
        chooseProduct();
        stop();
    }
}

public class VendingManufacturer {
    public void printManufacturerName() {
        System.out.println("Made By JavaBom");
    }
}

public class SnackVending extends VendingManufacturer implements Vending {
    InnerAbstractVending innerAbstractVending = new InnerAbstractVending();

    @Override
    public void start() {
        innerAbstractVending.start();
    }

    @Override
    public void chooseProduct() {
        innerAbstractVending.chooseProduct();
    }

    @Override
    public void stop() {
        innerAbstractVending.stop();
    }

    @Override
    public void process() {
        printManufacturerName();
        innerAbstractVending.process();
    }

    private class InnerAbstractVending extends AbstractVending {

        @Override
        public void chooseProduct() {
            System.out.println("choose product");
            System.out.println("chocolate");
            System.out.println("cracker");
        }
    }
}

 

5. 골격 구현 작성 방법

a. 인터페이스를 잘 살펴 다른 메서드들의 구현에 사용되는 기반 메서드를 선정

b. 기반 메서드들을 사용해 직접 구현할 수 있는 메서드들을 모두 디폴트 메서드로 제공

c. 기반 메서드나 디폴트 메서드로 만들지 못한 메서드가 남아있다면, 이 인터페이스를 구현하는 골격 구현 클래스를 하나 만들어서 작성한다.

d. 골격 구현은 기본적으로 상속이므로, 설계 및 문서화 지침을 모두 따라야한다.

 

6. 단순 구현

  • 골격 구현의 작은 변종
  • 골격 구현처럼 상속을 위해 인터페이스를 구현했으나 추상클래스가 아니다.
  • AbstractMap.SimpleEntry

댓글

Comments

Dev Book Review/Effective Java

[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

1. 재정의 메서드의 문서화 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야한다. 재정의 가능 메서드 : 호출 메서드의 API 설명에 기술, 호출 순서, 각각의 호출 결과 처리 영향 기술 호출할 수 있는 모든 상황을 남긴다. 백그라운드 스레드, 정적 초기화 과정에서 호출이 일어날 수 있는 상황 등 API 문서의 Implementation Requirements : 메서드의 내부 동작 방식 설명 메서드 주석에 @ImplSpec 태그를 붙여주면 자바독 도구가 생성된다. 좋은 API문서란 '어떻게'란 '무엇'을 하는지를 설명해야한다. 대치된다 : 그러나 상속이 캡슐화를 해치기 때문에 어쩔수 없다 - 클래스를 안전하게 상속할 수 있게 해야하기 때문 2. 상속 설..

[Effective Java] item 19. 상속을 고려해 설계하고 문서화하라. 그렇지 않았다면 상속을 금지하라

728x90

1. 재정의 메서드의 문서화

상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야한다.

  • 재정의 가능 메서드 : 호출 메서드의 API 설명에 기술, 호출 순서, 각각의 호출 결과 처리 영향 기술
  • 호출할 수 있는 모든 상황을 남긴다.
    백그라운드 스레드, 정적 초기화 과정에서 호출이 일어날 수 있는 상황 등

API 문서의 Implementation Requirements : 메서드의 내부 동작 방식 설명

  • 메서드 주석에 @ImplSpec 태그를 붙여주면 자바독 도구가 생성된다.

좋은 API문서란 '어떻게'란 '무엇'을 하는지를 설명해야한다.

  • 대치된다 : 그러나 상속이 캡슐화를 해치기 때문에 어쩔수 없다 - 클래스를 안전하게 상속할 수 있게 해야하기 때문

 

2. 상속 설계 : 내부 동작과정 훅 선별

클래스의 내부 동작과정 중간에 끼어들 수 있는 훅(hook)을 잘 선별하여 protected 메서드 형태로 공개해야 할 수도 있다.

ex ) Clear 메서드를 호출할 때 성능을 위해 사용되는 protected removeRange() 메서드

실제 하위 클래스를 만들어 시험해본 뒤, protected 메서드의 내부 노출 정도를 결정한다.
너무 적게 노출해서 상속으로 얻는 이점마저 없애지 않게 주의한다.

상속용 클래스의 시험 방법 : 직접 하위클래스를 만들어본다.

하위클래스 여러개 만들때까지 전혀 쓰지않는 protected 멤버는 private으로 돌린다.

배포전에 반드시 클래스를 만들어 검증하자.

 

3. 생성자에서 재정의 가능 메서드 호출 금지

상속용 클래스의 생성자는 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.

  • 상위 클래스의 생성자가 하위 클래스의 생성자보다 먼저 실행되므로
  • 재정의한 메서드가 하위 클래스의 생성자에서 초기화한 값에 의존하면 의도대로 동작하지 않는다.
  • private, final, static 메서드는 재정의가 불가능하니 생성자에서 안심하고 호출해도 된다.

 

4. Cloneable, Serializable 인터페이스는 상속에서 닫아두자

확장하려는 프로그래머에게 엄청난 부담을 준다.

clone과 readObject 메서드는 생성자와 비슷한 효과를 낸다 (새로운 객체를 만든다.)
따라서 상속용 클래스에서 구현할 때 제약도 생성자와 비슷하다.

clone과 readObject 모두 직접이든 간접이든 재정의 가능 메서드를 호출해서는 안된다.

  • readObject : 하위 클래스가 미처 역직렬화 되기 전에 재정의한 메서드부터 호출한다.
  • clone : 하위 클래스의 clone 메서드가 복제본의 상태를 수정하기 전에 재정의한 메서드를 호출 한다.
    원본 객체에도 피해를 줄 수 있다. (clone이 완벽하지 못해 원본 객체의 참조가 있을 경우)

 

5. Serializable을 구현한 상속용 클래스 주의

readResolve나 writeReplace 메서드를 갖는다면 이 메서드는 private이 아닌 protected로 선언해야한다.

private으로 선언하면 하위 클래스에서 무시된다 : 상속 허용을 위해 내부 구현을 클래스 API로 공개

 

6. 상속에 대한 조언

a. 상속용으로 설계하지 않은 클래스는 상속을 금지한다.

  • 클래스를 final로 선언하자.
  • 모든 생성자를 private이나 package-private으로 선언하고 public 정적 팩터리를 만들어준다.

b. 구현상속보단 인터페이스 상속을 사용하는 것이 더 나은 내안이다.

c. 상속을 허용해야겠다면 재정의 가능 메서드를 사용하지 않게 만들고 문서로 남긴다.

d. 클래스 동작 유지 + 재정의 가능 메서드 사용 코드 제거

  • 재정의 가능 메서드는 자신의 본문 코드를 private 도우미 메서드로 옮긴다.
  • 그리고 이 도우미 메서드를 호출한다.
public add(){
  add();
}
private add(){
  ...
}

댓글

Comments