본문 바로가기

Language/Effective java42

클래스와 인터페이스 상속보다는 컴포지션을 사용하라 상속(클래스가 다른 클래스를 확장하는)은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다. 패키지 경계를 넘어 다른 클래스의 구체 클래스를 상속하는 일은 위험하다. 메서드 호출과 달리 상속은 캡슐화를 깨드린다. 상위클래스가 어떻게 구현되는냐에 따라서 하위 클래스 동작에 이상이 생길 수 있다. 상위 클래스는 릴리즈마다 구현이 바뀔 수 있어 문서화를 해두고 발맞춰 수정해야만 한다. 아래 예제에서 addAll()이 c.size()를 더하고 있는데 add()메서드에서 한번 더 더하기 때문에 2배가 된다. 그렇다고 addCount를 더하는 부분을 지운다고 안심할 수 없다. 나중에 상위클래스(HashSet) 어떻게 바뀔지 모르기 때문이다. public class Instrum.. 2022. 6. 14.
클래스와 인터페이스 변경 가능성을 최소화하라 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다. 불변 클래스는 장점이 많고, 단점은 특정 상황에서의 잠재적 성능 저하뿐이다. 이것을 불변 클래스라고 부르는데 인스턴스의 내부 값을 수정 할 수 없는 클래스이다. 생성된 시점의 상태를 파괴될때까지 가져간다. 불변 클래스의 규칙 1. 객채의 상태를 변경하는 메서드(변경자)를 제공하지 않는다. 2. 클래스를 확장 할 수 없도록 한다. - 하위 클래스에서 부주의하게 혹은 나쁜 의도로 객체의 상태를 만드는 사태를 막아준다. 3. 모든 필드를 final로 선언한다. 4. 모든 필드를 private으로 선언한다. - 외부에서 직접 접근하여 필드를 수정하는 것을 막아준다. public final로 사용한다면 다음 릴리즈때 내부 표현을 바꾸지.. 2022. 6. 12.
클래스와 인터페이스 클래스와 멤버의 접근 권한을 최소화하라 잘 설계된 컴포넌트는 클래스 내부 데이터와 구현 정보를 외부 컴포넌트에 얼마나 잘 숨겼느냐다. 정보를 은닉해서 구현부와 API를 분리시켜 오직 API로만 외부 컴포넌트와 소통하며 서로의 내부 동작 방식에는 전혀 개의치 않는다. 정보 은닉의 장점 1. 시스템 개발 속도를 높인다. 여러 컴포넌트를 병렬로 개발 할 수 있기 때문이다. 2. 시스템 관리 비용을 낮춘다. 각 컴포넌트를 더 빨리 파악하여 디버깅 할 수 있고, 다른 컴포넌트로 교체하는 부담도 적기 때문이다. 3. 정보 은닉가 자체가 성능을 높여주지는 않지만, 성능 최적화에 도움을 준다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 정한 다음, 다른 컴포넌트에 영향을 주지 않고 해당 컴포넌트만 최적화 할 수 .. 2022. 6. 12.
모든 객체의 공통 메서드 Comparable을 구현할지 고려하라 Comparable interface를 통해 인스턴스들에 작연적인 순서를 만들 수 있다. compareTo는 단순 동치성과 순서를 비교 할 수 있다. 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable interface를 구현하자. public interface Comparable { public int compareTo(T o); } compareTo 메서드의 일반 규약 이 객체와 주어진 객체의 순서를 비교한다. · 나보다 작은 객체 음의 정수 · 나보다 같은 객체 0 · 나보다 큰 객체 양의 정수 · 비교 할 수 없는 경우 ClassCastException 규약 1. sgn(x.compareTo(y)) == -sgn(y.x.. 2022. 6. 11.
모든 객체의 공통 메서드 toString을 항상 재정의하라. toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁고, 그 클래스를 사용한 시스템은 디버깅하기 쉽다. 일반 규약 간결하면서 사람이 읽기 쉬운 형태의 유익한 정보를 반환하라. 모든 하위 클래스에서 이 메서드를 재정의하라. 실전에서 toString은 그 객체가 가진 주요 정보 모두를 반환하는 게 좋다. - 스스로를 완벽히 설명하는 문자열이어야 한다. eg. "맨해튼 거주자 전화번호부(총 1487536개)" 포맷을 명시하든 아니든 여러분의 의도는 명확히 밝혀야 한다. - 포맷을 명시하거나 주석을 작성하여 의도를 명백히 밝혀야 한다. /* * 이 전화번호의 문자열 표현을 반환한다. * 이 문자열은 "XXX-YYY-ZZZZ"형태의 12글자로 구성된다. * XXX는 지역코.. 2022. 6. 7.
모든 객체의 공통 메서드 equals를 재정의하려거든 hashCode도 재정의하라. equals를 재정의한 객체의 hashCode재정의 하지 않으면 HashMap, HashSet같은 컬렉션의 원소로 사용 할 때 문제를 일으킨다. hash 일반 규약 1. equals비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다. (단, 애플리케이션을 다시 실행한다면 이 값이 달라져도 상관없다.) 2. equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다. 3. equals(Object)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할.. 2022. 6. 6.
모든 객체의 공통 메서드 INTRO Object는 객체를 만들 수 있는 구체 클래스지만 기본적으로는 상속해서 사용하도록 설계되었다. Object에서 final이 아닌 메서드(equals, hashCode, toString, clone, finalize)는 모두 재정의를 염두에 두고 설계된 것이라 재정의 시 지켜야 하는 일반 규약이 명확히 정의되어 있다.일반 규약에 맞게 재정의 하지 않으면 규약을 준수한다고 가정하는 클래스(HashMap, HashSet 등)를 오작동하게 만들 수 있다. equals는 일반 규약을 지켜 재정의하라. equals 메서드는 재정의하기 쉬워 보이지만 곳곳에 함정이 도사리고 있어서 자칫하면 끔찍한 결과를 초래한다. 재정의 하지 않으면 클래스의 인스턴스는 오직 자기 자신과만 같게 된다. 재정의 하지 않는 상.. 2022. 6. 6.
객체 생성과 파괴 finalizer와 cleaner 사용을 피하라. finalizer는 예측 할 수 없고, 상황에 따라 위험 할 수 있어 일반적으로는 불필요하다. cleaner는 finalizer보다 덜 위험하지만, 여전히 예측하기 어렵고 느리고 일반적으로는 불필요하다. 피해야 하는 이유 1. 언제 실행될지 알 수가 없어서 제때 실행되어야 하는 작업은 절대 할 수 없다. 2. 상태를 영구적으로 수정하는 작업에서는 절대 사용하면 안된다. DB lock해제를 finalizer나 cleaner에게 맡겨놓는다면 분산 시스템 전체가 서서히 멈출 것이다. 3. 심각한 성능 문제도 동반한다. 4. finalizer공격에 노출되어 심각한 보안 문제를 일으킬 수도 있다. 생성자나 직렬화 과정에서 예외가 발생하면, 이 생성되다 만 객체에서.. 2022. 6. 5.
객체 생성과 파괴 불팔요한 객체 생성을 피하라 똑같은 기능의 객체를 매번 생성하기 보다는 하나를 사용해서 재 사용 하는 것이 성능면에서나 메모리 관리면에서 더 좋다. String s = new String("bikini"); // 잘못된 사용 String s = "bikini"; // 옳은 사용 위에 코드처럼 작성하게 되면 반복문일 경우 객체가 계속해서 생성된다. 책에서도 Boolean(String)을 쓰지말고, Boolean.valueOf(String)을 사용하라고 나와 있다. @HotSpotIntrinsicCandidate public static Boolean valueOf(boolean b) { return (b ? TRUE : FALSE); } java코드를 보면 static으로 하나의 메서드만 생성해서 사용하도.. 2022. 6. 1.
객체 생성과 파괴 인스턴스화를 막으려거든 private 생성자를 사용하라. 정적 메서드와 정적 필드만을 담은 클래스를 만들고 싶을 때 private 생성자를 사용해서 사용하지 않도록 명시하자. public class UtilityClass { private UtilityClass() { throw new AssertionError() } } 혹시나 내부에서 사용 할 수도 있기 때문에 AssertionError()를 던져주자. 장점 1. 상속을 막아준다. 2. 사용하지 말라는 표현이 명백하다. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라. 아래 코드들은 final로 인해 유연하지 못하다. 반드시 하나의 객체만을 사용한다는 가정이 없이는 사용하기 어렵고 final제거 한 후 교체하는 방식도 어색하며 오류를 내구 쉽다.. 2022. 6. 1.
객체 생성과 파괴 생성자에 매개변수가 많다면 빌더를 고려하라. 책에서는 빌더 패턴 전 시기에 2가지를 설명하고 있지만 결론은 builder를 사용하라는 것이기 때문에 builder에 대해서만 정리하였다. 빌더 패턴을 쓰면 장점 1. 빌더 패턴은 계층적으로 설계된 클래스와 함께 쓰기에 좋다. return 타입을 지정해서 child class에서 타입을 강제 반환 시킬 수 있다. 2. 가독성이 좋으며 build()에서 유효성 검사를 할 수 있다. private 생성자나 열거 타입으로 싱글턴임을 보증하라. public static 방식 public class Elvis() { public static final Elvis INSTANCE = new Elvis(); // 생성하지 못하도록 막는다. private Elvis() {.. 2022. 5. 22.
객체 생성과 파괴 생성자 대신 정적 팩터리 메서드를 고려하라. 정적 팩토리의 장점 이름을 가질 수 있다. 생성자의 이름으로는 반환될 객체의 특성을 제대로 설명하지 못한다. 반면 정적 팩토리 메서드는 특징을 묘사하게 할 수 있다. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다. 불변 클래스는 인스턴스를 미리 만들어 놓거나 새로 생성한 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. 또한, 인스턴스 통제 클래스를 통해 어느 인스턴스를 살아있게 할지 철저히 통제 할 수 있다. 인스턴스 통제 클래스는 Flyweight Pattern을 기반으로 되어있다. 어떤 인스턴스는 싱글톤으로 만들 수 있고, 다른 것은 인스턴스화 불가로도 만들 수 있다. 반환 타입이 하위 타입 객체를 반환 할 수 있는 능력이 .. 2022. 5. 9.