Developer's_til/Effective Java 10

[item 18] 상속보다는 컴포지션을 사용하라

상속은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다. 잘못 사용하면 오류를 내기 쉬운 소프트웨어를 만들게 된다. 일반적인 구체 클래스를 패키지 경계를 넘어, 즉 다른 패키지의 구체 클래스를 상속하는 일은 위험하다. 메서드 호출과 달리 상속은 캡슐화를 깨트린다. 다르게 말하면, 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다. 이러한 이유로 상위 클래스 설계자가 확장을 충분히 고려하고 문서화도 제대로 해두지 않으면 하위 클래스는 상위 클래스의 변화에 발맞춰 수정돼야만 한다. [ HastSet을 사용한 예시 ] public class InstrumentedHastSet extends HastSet { private int addCount = 0; public I..

[item 17] 변경 가능성을 최소화하라

불변 클래스란 간단히 말해 그 인스턴스의 내부 값을 수정할 수 없는 클래스다.불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다. 불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류가 생길 여지도 적고 훨씬 안전하다. [ 불변 클래스의 다섯 가지 규칙 ] 1. 객체의 상태를 변경하는 메서드를 제공하지 않는다. 2. 클래스를 확장할 수 없도록 한다. 상속을 막는 대표적인 방법은 클래스를 final로 선언하는 것이다. 3. 모든 필드를 final로 선언한다. 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건네도 문제없이 동작하게끔 보장하는데도 필요하다. 4. 모든 필드를 private으로 선언한다. 필드가 참조하는 가변 객체를 클라이언트가 직접 접근해..

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

어설프게 설계된 컴포넌트와 잘 설계된 컴포넌트의 가장 큰 차이는 바로 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼느냐다. 잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔히 분리한다. 정보은닉, 혹은 캡슐화라고 하는 이 개념은 소프트웨어 설계의 근간이 되는 원리다. 정보은닉 장점의 대부분은 시스템을 구성하는 컴포넌트들을 서로 독립시켜서 개발, 테스트, 최적화, 적용, 분석, 수정을 개별적으로 할수 있게 해주는 것과 연관되어 있다. [ 정보은닉의 장점 ] 시스템 개발 속도를 높인다. 여러 컴포넌트를 병렬로 개발할 수 있기 때문이다. 시스템 관리 비용을 낮춘다. 각 컴포넌트를 더 빨리 파악하여 디버깅할 수 있다. 성능 최적화에 도움을 준다. 완성된 시스..

[item 14] Comparable을 구현할지 고려하라

Comparable 인터페이스의 유일무이한 메서드인 compareTo를 알아보자. compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 제네릭하다. compareTo 메서드의 일반 규약은 equals의 규약과 비슷하다. 이 객체와 주어진 객체의 순서를 비교한다. 이 객체가 주어진 객체보다 작으면 음의 정수를, 같으면 0을, 크면 양의 정수를 반환한다. 이 객체와 비교할 수 없는 타입의 객체가 주어지면 ClassCastException을 던진다. 다음 설명에서 sgn(표현식) 표기는 수학에서 말하는 부호 함수를 뜻하며 표현식의 값이 음수, 0, 양수일 때, -1, 0, 1을 반환하도록 정의했다. 1. Comparable을 구현한 모든 클래스는 모든 x, y에 대해 sgn(x.compar..

[item 13] 재정의는 주의해서 진행하라

Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 mixin interface지만, 의도한 목적을 제대로 이루지 못했다. 가장 큰 문제는 clone 메서드는 선언된 곳이 Cloneable이 아닌 Object이고, protected라는데 있다. Cloneable 인터페이스는 protected 메서드인 clone의 동작 방식을 결정한다. clone을 호출하면 그 객체의 필드들을 하나하나 복사한 객체를 반환한다. clone 메서드의 일반 규약으로 Object 명세에서 가져온 설명은 다음과 같다. 이 객체의 복사본을 생성해 반환한다. '복사'의 정확한 뜻은 그 객체를 구현한 클래스에 따라 다를 수 있다. 일반적인 의도는 다음과 같다. 어떤 객체 x에 대해 다음 식은 참이다. x.clone() != x..

[item 11] equals를 재정의하려거든 hashCode도 재정의하라

equals를 재정의한 클래스 모두에서 hashCode도 재정의해야한다. 그렇지 않으면 hashCode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으킨다. [ Object 명세에서 발췌한 규약 ] 1. equals 비교에서 사용되는 정보가 변경되지 않았다면, 어플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다. 2. equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다. 3. equals(Object)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 ..

[item 10] equals는 일반 규약을 지켜 재정의하라

equals메서드는 재정의하기 쉬워 보이지만 여러가지 함정이 있어서 자칫하면 끔찍한 결과를 초래한다. 다음에서 열거한 상황 중 하나에 해당한다면 재정의하지 않는 것이 좋다. [ equals를 재정의하지 않는 상황 ] 1. 각 인스턴스가 본질적으로 고유하다 - 동작하는 개체를 표현하는 클래스가 여기에 해당(Thread) 2. 인스턴스의 '논리적 동치성'을 검사할 일이 없다. 3. 상위 클래스에서 재정의한 equals가 하위 클래스에도 딱 들어맞는다. 4. 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다. - 이를 회피하고 싶다면 아래와 같은 메서드를 구현해두자. @Override public boolean equals(Object o) { throw ne..

[item 6] 불필요한 객체 생성을 피하라

똑같은 기능의 객체를 매번생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다. 다음 코드 중 첫번째는 절대 하지말아야 할 극단적인 예로 코드가 실행될 때마다 새로운 String 인스터스를 생성한다. 이를 개선한 버전이 두번째 코드다. 새로운 인스턴스를 만드는 대신 하나의 String 인스턴스를 사용한다. 나아가 이 방식을 사용한다면 같은 가상 머신 안에서 이와 똑같은 문자열 리터럴을 사용하는 모든 코드가 같은 객체를 재사용함이 보장된다. String s = new String("str"); String s = "str"; 생성자 대신 정적 팩터리 메서드를 제공하는 불변 클래스에서는 정적 팩터리 메서드를 사용해 불필요한 객체 생성을 피할 수 있다. 아래 첫번째 생성자는 호출할 때마다 새로운 객체를 ..

[item 3] private 생성자나 열거 타입으로 싱글턴임을 보증하라

✅ 싱글턴이란? 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다. 전형적인 예로는 함수와 같은 무상태 객체나 설계상 유일해야 하는 시스템 컴포넌트가 있다. ✅ 싱글턴 만드는 방식 [ public static final 필드 방식의 싱글턴 ] public class Elvis { public static final Elvis INSTANCE = new Elvis(); private Elvis() { ... } public void leaveTheBuilding() { ... } } private 생성자는 public static final 필드인 Elvis.INSTANCE를 초기화할 때 딱 한 번만 호출된다. 즉, public이나 protected 생성자가 없으므로 Elvis 클래스가 초기화될 때 ..

[Item 1] 생성자 대신 정적 팩토리 메서드.

클래스의 인스턴스를 얻는 일반적인 방법은 public 생성자다. 하지만, 이와 별도로 클래스는 정적 팩토리 메서드를 제공할 수 있다. 정적 팩토리 메서드란 객체 생성의 역할을 하는 클래스 메서드라는 의미를 갖고 있다. 아래 예시 코드를 참고하자면 java.time 패키지에 포함된 LocalTime 클래스의 정적 팩터리 메서드다. // LocalTime.class public static LocalTime of(int hour, int minute) { ChronoField.HOUR_OF_DAY.checkValidValue((long)hour); if (minute == 0) { return HOURS[hour]; } else { ChronoField.MINUTE_OF_HOUR.checkValidValue..