TIL/Java

[Java] Effective Java 3/E 4장 item 15~20

JoJobum 2024. 1. 18.

Item 15: 클래스와 멤버의 접근 권한을 최소화하라

public 으로 나오는 것은 유지 보수, 하위 버전 호환의 책임 존재

외부에서 접근할 수 있는 내용은 최소화하자

public 클래스의 인스턴스 필드는 되도록 public 아니어야 함

public 가변 변수를 가진다면 일반적으로 Thread-Safe 하지 않음

 

Item 16: public 클래스에서는 public 필드가 아닌 접근자 메소드 사용하라

public 필드 바로 접근 X

getter 쓸 것

setter는 정말 필요시에 쓸 것

 

Item 17: 변경 가능성을 최소화하라

  • 객체 상태 변경하는 메소드 제공X
  • 클래스 확장할 수 없도록
  • 모든 필드 final 으로 선언
  • 모든 필드 private 으로 선언
  • 자신 외에는 내부의 가변 컴포넌트에 접근 불가

불변 객체의 장점

  • 읽는 사람이 그대로 받아들일 수 있음
  • 근본적으로 Thread-Safe
    • ⇒ 고로 자유롭게 공유 가능
  • 실패 원자성 제공
    • 실패 원자성: 메서드에서 예외가 발생해도 객체는 유효한 상태여야 한다는 것

단점

  • 값이 다르면 독립적인 객체가 되는데… 값이 많다면??
    1. 다단계 연산 기본 제공
    2. 다단계 연산 속도 높여주는 가변동반 클래스
      • Ex) BigInteger, StringBuilder

합당한 이유없으면 기본적으로 private final

객체 재활용한다고 상태 초기화는 복잡도만 커짐, 성능상 이점X

 

Item 18: 상속보다는 컴포지션을 사용해라

상속보다 Composition 사용할것

왜 why?

상속은 캡슐화 깰 수 있음

  1. 상위 클래스의 구현에 따라 하위 클래스의 동작에 이상이 생길 수 있음
  2. ex) super.add() 이란 식으로 상위 클래스의 구현을 사용해서 하위 클래스의 동작을 정의하는 경우 상위 클래스의 변경에 의해 하위 클래스의 동작에 이상이 생길 수 있음
  3. 새로운 메소드가 생겼을 때 하위 클래스에서 해당 메소드를 재정의하지 않는다면, 하위 클래스 입장에서는 예상하지 못한 동작을 수행할 수 있음
  4. public class CompositionTest extends ExtendTest { public CompositionTest(int id) { super(id); } @Override public int getId() { return super.getId(); } @Override public void setId(int id) { super.setId(id); } } public class ExtendTest { int id; public ExtendTest(int id) { this.id = id; } public void newFun() { System.out.println("new Function"); } public int getId() { return id; } public void setId(int id) { this.id = id; } } public class Main { public static void main(String[] args) throws Exception { CompositionTest compositionTest = new CompositionTest(100); // CompositionTest 에서 정의하지 않았지만 상위 클래스의 메소드로 예상치 못한 동작할 수 있음 compositionTest.newFun(); } }

1, 2 모두 메소드 재정의로 생긴 문제

메소드를 재정의하지 않고 새로운 메소드를 추가하는 방식으로 해도 해결되지 않음

⇒ 기존 클래스를 확장하는 것 대신 새로운 클래스 만들고 private 필드로 기존 클래스의 인스턴스 참조 == Wrapper 클래스

Wrapper 클래스는 단점 거의 없음

callback 프레임워크와 어울리지 않는 점이 단점

자기 자신의 참조를 다른 객체에게 넘겨 다음 호출(콜백) 때 사용하도록 함

내부 객체는 Wrapper Class의 존재를 모르니 자신(내부 객체)의 참조를 넘기고 콜백시 Wrapper가 아닌 내부 객체를 호출하는 문제 있음

 

Item 19: 상속을 고려해 설계하고 문서화하라, 아니라면 상속을 금지하라

상속이 주는 패널티 메소드 재정의 등의 문제를 피하기 위해서는 이러한 문제를 감안한 설계와 문서화가 필요함

⇒ 재정의할 수 있는 메소드들을 내부적으로 어떻게 사용하는지 문서화할 것

상속용 클래스의 생성자는 직간접적으로 재정의 가능 메소드를 호출하면 안됨

public class Super {
    public Super() {
        overrideMe();
    }

    public void overrideMe() {
        System.out.println("super's overrideMe");
    }
}

public class Sub extends Super {
    private final Instant instant;

    public Sub() {
        this.instant = Instant.now();
    }

    @Override
    public void overrideMe() {
        System.out.println("sub override: " + instant);
    }
}

public class Main {
    public static void main(String[] args) throws Exception {
        Sub sub = new Sub();
        sub.overrideMe();
    }
}

Sub sub = new Sub()시

  1. 상위 클래스 Super의 생성자가 먼저 생성됨
  2. Super 생성자의 overrideMe() ⇒ Sub의 overrideMe() 실행 == null 출력
  3. Sub() 생성자가 생성 ⇒ instant 초기화
  4. sub.overrideMe() == 시간 정상 출력

상속용으로 설계되지 않은 클래스를 상속하지 말자

어쩔 수 없다면 클래스 내부에서 재정의 가능 메소드를 사용하지 않게 하고 이를 문서로 남길 것

 

Item 20: 추상 클래스보다는 인터페이스를 우선해라

추상 클래스는 구현체가 반드시 추상 클래스의 하위 클래스가 되어야 한다는 점

반면, 인터페이스의 경우 규칙을 잘 지킨다면 다른 어떤 클래스를 상속했든 같은 타입으로 취급

인터페이스를 더욱 편하게 만들어주자

디폴트 메소드

골격 구현 클래스는 디폴트 메소드 및 다른 메소드들도 구현

골격 구현 클래스를 확장하는 것으로 인터페이스를 구현하는 것이 템플릿 메소드 패턴

⇒ 추상 클래스처럼 구현을 도와주면서 추상클래스가 가지고 있는 제약 회피

인터페이스가 좀 더 유연

반응형

'TIL > Java' 카테고리의 다른 글

[Java] Effective Java 3/E 3장 item 21~25  (0) 2024.01.20
[Java] Effective Java 3/E 3장 item 10~14  (1) 2023.11.14
[JAVA] Java Version 8 vs 17  (0) 2023.10.21
[Java] Effective Java 3/E item 7~9  (1) 2023.06.13
[Java] Effective Java 3/E item 4~6  (0) 2023.05.28

댓글