맞는데 왜 틀릴까..?

Java/Effective Java 77

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

클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에 얘기해 주는 것이다. 인터페이스는 오직 이 용도로만 사용해야 한다. 이 지침에 맞지 않는 예로 상수 인터페이스라는 것이 있다. 상수 인터페이스 상수 인터페이스 : 메서드 없이, 상수를 뜻하는 static final 필드로만 구성된 인터페이스 정규화된 이름을 쓰는걸 피하고자 아래와 같은 인터페이스를 구현한다. 이 상수 인터페이스는 인터페이스를 잘못 사용한 예다. 클래스 내부에서 사용하는 상수는 외부 인터페이스가 아니라 내부 구현에 해당한다. 따라서 상수 인터페이스를 구현하는 것은 이 내부 구현을 클래스의 API로 노출하는 행위다. 클래스가 어떤 상수 인터페이스를 사용하든 사용자에게는 아무런 의미가 없으며, 오히..

Java/Effective Java 2023.05.03

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

디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다. 이처럼 자바 8 이후에 자바에도 기존 인터페이스에 메서드를 추가하는 길이 열렸지만 모든 기존 구현체들과 매끄럽게 연동되리라는 보장은 없다. 자바 8 이전의 모든 클래스는 "현재 인터페이스에 새로운 메서드가 추가될 일이 없다"라고 생각해 설계되었기 때문이다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 '삽입' 될 뿐이다. 따라서 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기는 어렵다. 디폴트 메서드로 인한 피해 예시 아파치의 SynchronizedCollection 클래스는 현재에도 removeIf 메서드를 오버라이딩하..

Java/Effective Java 2023.05.03

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

현직에서 개발팀장을 맡아 신입면접까지 진행하는 분과 얘기할 기회가 생겼었다. 자바 개발자를 지향한다고 하니 현직자분이 자바 개발자를 뽑을 때 무조건 하는 질문을 나에게 해주셨다. "자바에서 인터페이스는 왜 사용하나요?" 나는 불현듯 전공 C++ 강의에서 교수님이 해주신 죽음의 다이아몬드 얘기가 생각났다. 교수님은 C++는 다중상속을 허용하기 때문에 죽음의 다이아몬드라는 현상이 발생하고 이 때문에 자바에서는 다중상속을 금지하고 인터페이스라는 개념이 사용된다고 해주셨었다. 마침 이 얘기가 머리에 강하게 박혀서 현직자 님께 이 얘기를 했었던 기억이 있다. 하지만 내가 정확하게 알고 말한 것이 아니니 제대로 알아볼 기회가 필요했는데 마침 좋은 아이템이 있어서 기분 좋게 공부했다. 그러면 자바의 핵심이라고 할 수..

Java/Effective Java 2023.05.03

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

상속을 염두에 두지 않고 설계하고, 상속할 때의 주의점도 문서화해놓지 않은 '외부' 클래스를 상속할 때는 매우 위험하다. 그렇다면 상속을 고려한 설계와 문서화는 정확히 무엇일까? 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다. 앞서 Item 18에서 살펴본 예시 "HashSet을 상속하여 add()를 오버라이딩한 것이 addAll()까지 영향을 준다"를 보면 Item 19의 필요성을 알 수 있을 것이다. 오버라이딩 가능한 메서드는 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지 등 메서드의 내부 동작 방식 (Implementation Requirements)을 문서로 남겨야 한다. 오버라이딩 가능한 메서드 public과 pro..

Java/Effective Java 2023.03.28

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

상속은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다. 잘못 사용하면 오류를 내기 쉬운 소프트웨어를 만들게 된다. 상위 클래스와 하위 클래스를 모두 같은 프로그래머가 통제하는 패키지 안에서라면 상속도 안전한 방법이다. 일반적인 구체 클래스를 패키지 경계를 넘어 다른 패키지의 구체 클래스를 상속하는 일은 위험하다. 상속의 문제점 메서드 호출과 달리 캡슐화를 깨뜨린다. 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다. ->릴리스마다 내부 구현이 달라질 수 있는 상위 클래스 때문에 건드리지 않은 하위 클래스가 오동작할 수 있다. HashSet을 상속받아 set에 추가한 원소의 개수를 저장할 수 있게 하는 클래스 InstrumentedSet을 만들어보자. 3개의 원소..

Java/Effective Java 2023.03.25

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

불변 클래스 불변 클래스란 그 인스턴스의 내부 값을 수정할 수 없는 클래스다. 불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다. String, 기본 타입의 박싱 된 클래스 (Wrapper Class), BigInteger, BigDecimal 불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류가 생길 여지도 적고 훨씬 안전하다. 기본타입의 박싱 된 클래스 (Wrapper Class) int num = 42; Integer boxedNum = Integer.valueOf(num);​ 박싱 된 클래스는 기본 타입의 값을 감싸는 객체이기 때문에 객체를 다루는 메서드와 함께 사용해야 하는 경우가 있다. 반면에 기본 타입은 메모리 사용 면에서 더 효율적이기..

Java/Effective Java 2023.03.25

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

인스턴스 필드들을 모아놓을 일 외에는 아무 목적도 없는 퇴보한 클래스를 작성하려 할 때가 있는데, 대표적으로 x, y의 좌표를 표현하는 Point 클래스가 있다. 이런 클래스는 데이터 필드에 직접 접근할 수 있으므로 캡슐화의 이점을 제공하지 못한다. 따라서 필드를 모두 private으로 바꾸고 접근자(getter)를 추가하자. 패키지 바깥에서 접근할 수 있는 클래스라면 getter를 제공함으로써 클래스 내부 표현 방식을 언제든 바꿀 수 있는 유연성을 얻을 수 있다. 단, package-private 클래스 혹은 private 중첩 클래스라면 데이터를 노출한다해도 문제가 없다. -> 오히려 접근자 방식보다 훨씬 깔끔하다. 불변식 보장 public 클래스의 필드가 불변(public final)이라면 직접 노..

Java/Effective Java 2023.03.21

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

잘 설계된 컴포넌트란 클래스 내부 데이터와 내부 구현 정보를 얼마나 잘 숨겼느냐이다. 이런 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔히 분리한다. 즉, 캡슐화(정보 은닉)를 얼마나 잘 구현하였는지가 중요하다. 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다. 캡슐화의 장점 1. 여러 컴포넌트를 병렬로 개발할 수 있어 시스템 개발 속도를 높인다. 2. 각 컴포넌트를 더 빨리 파악하여 디버깅할 수 있고, 다른 컴포넌트로 교체하는 부담도 적어 시스템 관리 비용을 낮춘다. 3. 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화할 수 있으므로 성능 최적화에 도움이 된다. 4. 외부의 의존성이 낮은 컴포넌트라면 소프트웨어 재사용성이 높아진다. 5. 시스템이 완성되지 않은 상태에서도..

Java/Effective Java 2023.03.21

[Effective Java] Item 14. Comparable을 구현할지 고려하라

유일무이한 메서드 compareTo()를 가지고 있는 Comparable 인터페이스에 대해서 알아보자. public interface Comparable { public int compareTo(T o); } compareTo()는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 제네릭 하다. Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻해 객체들의 배열을 정렬할 수 있다. Arrays.sort()는 자동으로 Comparalbe에 구현되어 있는 compareTo()를 호출해서 사용한다. 자바 플랫폼 라이브러리의 모든 값 클래스와 열거 타입은 Comparable을 구현하고 있다. 그러니 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시..

Java/Effective Java 2023.03.21

[Effective Java] Item 13. clone 재정의는 주의해서 진행하라

clone() 메서드는 객체를 복제하는 데 사용된다. 이 메서드를 사용하여 객체를 복제하면 새로운 객체가 만들어지며, 이 객체는 원래 객체와 동일한 속성 값을 가지게 된다. clone() 메서드는 기본적으로 얕은 복사를 수행한다. 객체의 원시 데이터 타입(int, double, boolean 등 객체가 아닌 데이터 타입)의 값을 복사하지만, 참조 타입의 값은 참조만 복사한다. 따라서 객체가 참조하는 다른 객체는 원래 객체와 복제된 객체가 모두 공유한다. 객체의 깊은 복사를 수행하려면 clone() 메서드를 오버라이딩 하고, 재귀적으로 복제하자. clone()의 특징 clone 메서드가 선언된 곳은 Cloneable이 아닌 Object이며 접근 지정자는 protected다. Cloneable을 구현하는 것..

Java/Effective Java 2023.03.20