Book/Effective Java
[Item 19] 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라
상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다. 앞서 item 18에서 HashSet 클래스를 상속하여 add를 재정의 한 것이 addAll 까지 영향을 준다는 사실을 알 수 없었다. 이러한 문제점을 문서화하여 주석처리만 해줘도 add 메서드로 내부 구현이 되있구나 라는 것을 알 수 있다. 좋은 문서화는 무엇일까? 문서화를 좋게 작성 하려면 어떻게가 아닌 무엇을 하는지를 설명해야한다. 즉, 내부 구현 방식을 설명해야 한다. 효율적인 하위 클래스를 만들려면? 클래스 내부 동작 과정 중간에 끼어들 수 있는 훅(hook)을 잘 선변하여 protected 메서드 형태로 공개해야 할 수 도 있다. java.util.AbstractList 의 removeRange 메서..
[Item 18] 상속보다는 컴포지션을 사용하라
코드를 재사용하는데 상속은 강력한 수단이다. 하지만 항상 재사용하는데에 있어 좋은 것은 아니다. 같은 패키지 내에서 상위 클래스와 하위 클래스를 모두 통제한다면 좋지만 다른 패키지 내에서 상속을 한다면 관리가 쉽지 않을 것이다. 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다. 상위 클래스를 확장을 고려하지 않고 설계하면 하위 클래스에서 에러가 날 수 있기에 충분히 고려해서 설계하자. HashSet을 잘못 상속한 코드 예 @Getter @NoArgsConstructor @AllArgsConstructor public class InstrumentedHashSet extends HashSet { private int addCount = 0; @Override public boolean add(E e) { ad..
[Item 17] 변경 가능성을 최소화 하라
불변 클래스란 그 인스턴스의 내부 값을 수정할 수 없는 클래스다. 불변 인스턴스가 가지고 있는 정보는 고정되어 객체가 파괴되는 순간까지 달라지지 않는다. 자바 라이브러리에도 다양한 불변 클래스가 있다. String 기본 타입의 박싱된 클래스 BigInteger BigDecimal 가변 클래스보다 설계와 구현이 쉬우며 오류가 생길 여지도 적고 안전하다. 불변 클래스를 만드는 다섯가지 규칙 객체의 상태를 변경하는 메서드를 제공하지 않는다 setter() 메서드를 예를 들 수 있겠다. 클래스를 확장할 수 없도록 한다. final 키워드를 사용한다. 모든 필드를 final로 선언한다. 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건네도 문제없이 동작하게끔 보장하는 데 필요 모든 필드를 private 으로 ..
[Item 16] public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라
객체 지향 프로그램은 public 클래스에 public 필드를 사용하는 것은 위험한 일이다. private 으로 변경하고 이를 제공 할 접근자를 추가한다. 접근자는 getter() 이다. 아래 코드는 lombok으로 대체 했다. @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class Point { // public 필드 말고 private 필드를 사용하고 접근자를 추가하여 사용하자 private int x; private int y; } 접근자를 제공함으로써 클래스 내부 표현 방식을 언제들 바꿀수 있는 유연성을 제공한다. package-private(default) 클래스 혹은 private 중첩 클래스라면 데이터 필드를 노출할 때가 좋을..
[Item 15] 클래스와 멤버의 접근 권한을 최소화하라
어설프게 설계한 컴포넌트와 잘 설계된 컴포넌트의 차이점은 내부 데이터를 얼마나 잘 숨겼냐에 따른다. 캡슐화 혹은 정보 은닉이 설계의 기반이되는 원리이다. 캡슐화의 장점 여러 컴포넌트를 병렬로 개발이 가능하므로 시스템 개발 속도를 높인다. 각 컴포넌트를 빠르게 파악할 수 있고 시스템 교체하는 부담도 적기 때문에 시스템 관리 비용을 낮춘다. 캡슐화 자체만으로 성능을 높여주지 않지만 각 컴포넌트에 영향을 주지 않고 해당 컴포넌트만을 최적화 할 수 있기 때문에 성능 최적화에 도움을 준다. 소프트웨어 재사용을 높인다. 개발 난이도를 낮춰준다. 캡슐화의 접근성은 접근제어자로 정해진다. 기본원칙 모든 클래스와 멤버의 접근성을 가능한 한 좁혀야 한다. 패키지 외부에서 쓸 이유가 없으면 package-private으로 ..
[Item 14] Comparable을 구현할지 고려하라
CompareTo는 동치성 비교에 순서까지 비교할 수 있으며 제네릭 하다. Comparable을 구현했다는 것은 해당 클래스의 순서가 있음을 뜻한다. Arrays의 클래스는 Comparable을 구현하여 아래와 같이 손쉽게 정렬할 수 있다. Arrays.sort(arr); 검색, 극단값 계산, 자동 정렬되는 컬렉션 관리도 쉽게 할 수 있다. 자바의 라이브러리의 모든 값 클래스와 열거타입이 Comparable을 구현했다. 알파벳, 숫자, 연대 같이 순서가 명확한 값 클래스를 작성한다면 반드시 Comparable을 구현하자. comparTo 메서드 규약 이 객체와 주어진 객체의 순서를 비교한다. 이 객체가 주어진 객체보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 반환한다. 객체와 비교할 수 없는 타..
[ITEM 13] clone 재정의는 주의해서 진행해라
Cloneable 인터페이스 Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 인터페이스다. public class Person implements Cloneable { //..... } 아쉽게도 Cloneable 인터페이스는 의도한 목적을 제대로 이루지 못했다. 왜냐하면 Cloneable 인터페이스에는 아무런 정의가 되지 않은 빈통 인터페이스다. public interface Cloneable { } clone 메서드가 정의 된 곳은 Object 클래스에 protected로 정의 되어 있다. @HotSpotIntrinsicCandidate protected native Object clone() throws CloneNotSupportedException; 그러면 Cloneable 인터페이스..
[ITEM 12] toString을 항상 재정의하라
Object의 기본 toString 메서드가 해당 클래스의 적합한 문자열을 반환하는 경우는 거의 없다. 이 메서드는 단순히 클래스이름@16진수로표현한해시코드 를 반환 할 뿐이다. toString의 일반 규약 ‘간결하면서 사람이 읽기 쉬운 형태의 유익한 정보' 를 반환해야 한다. ‘모든 하위 클래스에서 이 메서드를 재정의하라' 객체가 가지고 있는 주요 정보들을 모두 반환하는 것이 좋고 toString을 잘 구현한 클래스는 사용하기에 쉽고 디버깅하기 쉽다. toString을 구현할 때면 반환값의 포맷을 문서화할지 정해야 한다. 포맷을 명시하면 그 객체는 표준적이고, 명확하고, 사람이 읽을 수 있게 된다. 정적 팩터리나 생성자를 함께 제공하면 좋다. 단점 명확성을 얻을 수 있고 코드를 수정해야하는 유연성을 잃..
[ITEM 11] equals를 재정의하려거든 hashCode도 재정의하라
equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다. 그렇지 않으면 equals 재정의 규약을 어기게 되어 문제를 일으킨다. 규약 equals 비교에서 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 한다. equals 메서드가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다. equals 메서드가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없다. 단, 다른 객체에 대해서는 다른 값을 반환해야 해시테이블의 성능이 좋아진다. hashCode 재정의를 잘못했을 때 크게 문제가 되는것은 ‘논리적으로 ..
[ITEM 10] equals는 일반 규약을 지켜 재정의하라
equals() 메서드를 재정의 하지 않고 그냥 두면 해당 클래스의 인스턴스는 오직 자기 자신하고만 같게 된다. 재정의하지 않는 것을 권장 각 인스턴스가 본질적으로 고유 Thread 클래스의 equals() 가 좋은 예 인스턴스의 ‘논리적 동치성(logical equality)’을 검사할 일이 없다. 상위 클래스에서 재정의한 equals가 하위 클래스에도 맞는다. 예) 컬렉션 프레임워크의 구현체들은 추상클래스의 equals를 그대로 사용한다. equals() 를 호출할 일이 없다. 재정의해야 할 때는 언제일까? 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의 되지 않았을 때 값 클래스들이 해당됨 Integer String ….. 등 재정의를 하지 않아도 될 때 싱글톤이 보장되면 equals..
[Book] ITEM 9) try-finally 대신 try-with-resource 를 사용하라
🤢 try-finally 사용 public String fileRead1(String file)throws IOException { BufferedReader br = null; try{ br = new BufferedReader(new FileReader(file)); return br.readLine(); } catch(Exception e) { e.printStackTrace(); }finally{ br.close(); } return file; } 예외를 잡는데 여러줄의 코드가 작성되었습니다. 이러한 경우 예외가 발생하면 스택 트레이스에 추적이 어려울 것입니다. 이 방법의 대안점은 try-with-resource 를 사용하는 것입니다. 🤢 try-with-resource public String ..
[Book] ITEM 8) finalizer 와 cleaner는 피하라
finalizer 는 Object의 finalizer() 메서드를 말합니다. gc 의 대상이 되는 인스턴스가 있으면 finalizer() 메서드가 작동이 되어야하는데 finalizer() 는 동작의 예측이 불가능합니다. 예제를 보면서 확인 해 봅시다. Finalizer 클래스가 있고 오버라이드 한 finalize() 메서드와 hello() , hello2() 메서드가 있습니다. public class Finalizer { @Override protected void finalize()throws Throwable{ System.out.println("finalize 동작"); } public void hello() { System.out.println("hello 동작"); } } Main 클래스가 있고..
[Book] ITEM 7) 더이상 쓰지 않는 객체 레퍼런스는 없애자
자바에서 가비지 컬렉터는 필요 없는 메모리를 관리해 주는 역할을 해줍니다. 하지만 가비지 컬렉터에게 모든 메모리 관리를 맡기면 안됩니다. 예를 보겠습니다. 😱 Stack 의 메모리 누수 Stack 클래스의 코드를 살펴보겠습니다 public class StackSample { private Object[] elements; private int size = 0; private static final intDEFAULT_INITIAL_CAPACITY= 16; public StackSample() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } public void push(Object e) { ensureCapacity(); elements[size++]= e;..
[Book] ITEM 6) 불필요한 객체는 만들지 말자
기능적으로 동일한 객체를 새로 만드는 대신 객체 하나를 재사용하는 것이 적절합니다. 🥺 문자열 객체 문자열 객체 를 예로 들어봅시다. String name = "name"; String name1 = "name"; String name2 = new String("name"); // 불필요한 객체 예시 System.out.println(name == name1); // true System.out.println(name1 == name2); // false System.out.println(name1.equals(name2)); // true 저렇게 new 를 써서 객체를 생성하면 다른 객체가 생성되어 같지 않습니다. 하지만 new 를 쓰지 않고 객체를 생성 JVM 내부에서 동일한 리터럴값을 재사용하기 때..
[Book] ITEM 5) 리소스를 엮을 때는 의존성 주입을 선호하라
Repository 로 예를 들어 보겠습니다. InterfaceRepository 인터페이스가 save() 라는 기능을 가지고 있습니다. public interface InterfaceRepository { T save(); } 이를 구현한 MemoryBookRepository 클래스와 JdbcBookRepository 클래스를 생성합니다. public class MemoryBookRepository implements InterfaceRepository { @Override public Book save() { return null; } } public class JdbcBookRepository implements InterfaceRepository { @Override public Book save..