EJ2E Item 24. unchecked 경고를 제거하자

참조: Effective Java 2nd Edition. Item 24: Eliminate unchecked warnings


Set<Lark> exaltation = new HashSet();

다음과 같은 코드는 컴파일 시 unchecked 경고를 발생시킨다. 

Set<Lark> exaltation = new HashSet<Lark>();

이렇게 수정하면 경고가 사라진다. 가능한 모든 unchecked 경고를 없애자. 모든 경고를 제거했다면 코드의 타입 안전성을 확보하는 것이다. 즉 실행시에 ClassCastException을 보지 않을 것이다.

만약 경고를 없앨 수 없지만 타입 안전성을 확신한다면 @SupressWarnings(“unchecked”) 애노테이션을 사용하여 경고를 무시할 수 있다.

SupressWarning 애노테이션을 사용할 때는 항상 최소 단위에 적용하도록 하자. 만약 클래스위에 붙여버리면 @_@.. 메서드에 붙여도 @_@. 메서드 내부에 코드가 많다면 그 중 어디선가 또 다른 타입 안전성 문제가 발생할 수 있다. 메서드 내부에 변수를 만들고 해당 변수에다가 붙이자. 단 한 줄짜리 메서드라면 뭐.. 

SupressWarning 애노테이션을 사용할 때마다 왜 타입 안전성이 보장되는지 주석을 달아두도록 하자. 그래야 다른 사람들도 이해할 수 있다.

요약: 경고는 중요하다. 무시하지 말자. 모든 unchecked 경고는 잠재적으로 런타임시의 ClassCastException에 해당한다. 이 경고들을 없애는데 주력하자. 만약 없앨 수 없지만 타입 안전성을 확신한다면 가장 최소한의 규모에 @SuppressWarning(“unchecked”) 애노테이션을 붙이자. 그리고 그렇게 결정한 이유를 주석으로 남기자.
 

EJ2E Item 22. nonstatic 보다는 static 멤버 클래스를 선호하라

참조: Effective Java 2nd Edition. Item 22: Favor static member class over nonstatic


중첩 클래스(nested class)에는 네 종류가 있다.
– static member class
– nonstatic member class
– anonymous class
– local class

Static Member Class

public class SpringSprout {
   static class Whiteship {
        private String name; 
        …
    }
}

– 다른 클래스 내부에 선언된 클래스로 가장 많이 사용된다.
– 자신을 가지고 있는 클래스의 모든 멤버에 접근할 수 있다.
– 보통 helper 클래스로 사용된다.
– enclosing instance 유무와 상관없이 독립적으로 사용될 수 있다.

Nonstatic Member Class

public class SpringSprout {
   class Whiteship {
        private String name; 
        …
    }
}


– 암묵적으로 자신을 감싸는 클래스의 인스턴스(enclosing instance)와 연관을 맺는다.
– nontatic member class와 enclosing instance와의 연관 관계는 enclosing instance에서 nonstatic member class의 객체를 만들 때 생긴다.
– 이 연관 관계로 인해 nonstatic member class 인스턴에 필요한 공간과 생성 시간이 소비된다.
– 보통 Adapter를 정의할 때 사용된다.
– Map의 keySet, entrySet, values 메서드가 반환하는 콜렉션 뷰들과 Set, List에서 Iterator를 구현할 때 nonstatic member class를 사용한다.

AbstractList 코드

    public Iterator<E> iterator() {
return new Itr();
    }
    private class Itr implements Iterator<E> {
    …
    }

=> enclosing instance에 접근할 필요가 없는 member class는 static으로 선언하자.

Anonymous Class
– 이름도 없고 enclosing class의 멤버도 아니다.
– 표현식을 사용할 수 있는 곳이라면 어디서든 익명 클래스를 사용할 수 있다.
– nonstatic 문맥에서만 enclosing instance를 가진다.
– static 문맥에서는 static 멤버를 가질 수 없다.
– 주로 함수 객체를 만들 때 사용한다.

Local Class
– 익명 클래스랑 거의 같은데 클래스 이름 줄 수 있고 인스턴스 여러 번 만들어 사용할 수 있다.
– 걍 생략;

요약
– 중첩 클래스가 특정 메서드 외부에서도 사용될 피룡가 있거나 메서드 내부에 있기에는 너무 길다면 멤버 클래스로 만든다.
– 만약 멤버 클래스의 인스턴스가 enclosing instance를 참조할 필요가 있다면 nonstatic으로 만든다. 그렇지 않으면 static으로 만든다.
– 클래스가 메서드 내부에 속해 있으며 딱 한번만 사용할거면 익명 클래스로 만들고, 그렇지 않으면 로컬 클래스로 만든다.

EJ2E Item 21. 전략을 표현할 때는 함수 객체를 사용하라

참조: Effective Java 2nd Edition. Item 20: Use function objects to represent strategies


자바는 함수 포인터를 제공하지 않는다. 하지만 객체 레퍼러스를 함수 객체와 비슷하게 사용할 수 있다. 

메서드를 호출하면 해당 객체에 대한 기능을 수행하는 것이고 함수 객체는 다른 객체들에서 해당 기능을 수행하게 해준다.

(사족. 메서드는 특정 객체에 종속적인 것이고, 함수 객체는 독립적으로 여러 다른 객체에서 사용할 수 있는 것으로 구분할 수 있겠다.)

함수 객체들은 Concrete Stratege 역할을 하기 때문에 보통 상태 정보가 없고(stateless), 어디서나 동일하게 동작하기 때문에 불필요한 객체 생성 비용을 줄이기 위해 싱글톤으로 사용되기 좋다.

(사족. 스프링을 사용한다면 굳이 싱글톤 구현하느라 애쓰지 말고 그냥 싱글톤 스코프 빈으로 등록하면 되겠다.)

대표적인 예로 Comparator 인터페이스 구현체들이 있다.

// Strategy interface
public interface Comparator<T> {
    public int compare(T t1, T t2);
}
이 구현체는 종종 익명 클래스로 사용되기도 한다. 하지만 익명 클래스를 사용하면 매번 새로운 인스턴스를 만들게 된다. 반복해서 사용할 꺼라면 차라리 함수 객체를 priate static final field에 저장해 놓고 재사용하는 것이 좋겠다. 그렇게 하면 명시적인 이름을 줄수도 있다.
전략이 여러개를 제공하는 “Host class”에서 외부로 공개할( public static final) strategy의 반환 타입은 Strategey 인터페이스 타입이어야 하고 구체적인 Stategy 구현체들은 굳이 public 일 필요가 없다. 
요약
– 전략 패턴을 구현할 때 함수 객체를 주로 사용한다.
– 자바에서 이 패턴을 구현할 때는 전략 인터페이스를 만들고, 각 전략들은 그 인터페이스의 구현체로 만든다.
– 만약 어떤 전략을 한번만 사용할 거면 익명 클래스로 만들고 반복해서 사용할거면 prviate static member 클래스로 만든 다음 전략 인터페이스의 public sttic final field로 제공해준다.
– 대표적인 클래스 String

EJ2E Item 20. 태그가 있는 클래스 대신 클래스 계층구조를 선호하라.

참조: Effective Java 2nd Edition. Prefer class hierarchies to tageed classes

사용자 삽입 이미지
위와 같은 클래스의 단점:
– enum, switch 문, 태그 필드로 인해 지져분하다.
– 여러 구현체를 하나의 클래스로 합쳐놓았기 때문에 가독성이 떨어진다.
– 불필요한 필드까지 가지고 인스턴스를 만들어야 하기 떄문에 메모리 풋프린트가 증가한다.
– 생성자에서 불필요한 필드까지 초기화하지 않는 이상 필드를 final로 선언할 수 없다.
– 생성자에서 초기화를 잘못했을 때 컴파일 시점에 이것을 알 수 없다.
– 새로운 종류를 추가했을 때 switch문에 case를 추가해야 한다는 것을 기억해야 한다. 안그러면 런타임 에러가 발생한다.
– 인스턴스 데이터 타입이 실제 타입을 알려주지 못한다.
=> 즉 장황하고, 에러가 발생할 여지가 많고, 비효율적이다.

먼저 추상 클래스를 만들어서 일반적인 것들을 이 클래스로 이동시키고,
구체적인 하위 클래스를 정의한다.
해당 하위 클래스에서 추상 매서드를 구현한다.

사용자 삽입 이미지
이렇게 구성하면
– 깨끗하고 간단하다.
– 특정 타입에 관련된 속성은 해다 클래스가 가지게 된다. 따라서 불필요한 필드가 없다.
– 모든 필드를 final로 선언할 수 있다.
– 컴파일 시점에 생성자에서 데이터 필드를 초기화 하는지, 추상 매서드를 구현했는지 확인할 수 있다.

EJ2E Item 19. 인터페이스는 오직 타입을 정의할 때만 사용하라

참조: Effective Java 2nd Edition

어떤 클래스가 인터페이스를 구현할 때 인터페이스는 해당 클래스의 인스턴스를 참조할 수 있는 타입을 제공한다. 즉 해당 클래스 인스턴스를 가지고 고객이 무엇을 할 수 있는지 알려주는 것이다. 이 경우 이외에 다른 의도로 인터페이스를 사용하는 것은 부적절하다.

상수 인터페이스(constant interface) 안티 패턴
– 매서드 없이 상수를 표현한 static final 필드만 있다.
– 그 상수를 사용하는 클래스는 해당 인터페이스를 구현하여 상수 이름을 클래스 이름으로 구분해야 할 필요를 없앤다.(A.C B.C 대신에 A와 B가 구현하는 인터페이스 Z로 C를 올려서 Z.C 형태로 사용하게 하는 건가보네요.)
– 클래스가 내부에서 어떤 상수를 사용하는지는 구체적인 내용에 해당하는데 클래스가 상수 인터페이스를 구현하면 구체적인 내용을 밖으로 노출하게 된다.
– 차후에 해당 상수가 필요없어지더라도 바이너리 호환성을 위해 해당 인터페이스를 그대로 구현하고 있어야 한다.
– 자바 라이브러리 중에 java.io.ObjectStreamConstants는 예외다.
– 상수를 다룰 땐 해당 상수가 속하는 클래스나 인터페이스를 잘 고려해야 하며 열거형이 아닌지 보고 열거형일 경우에는 enum type 사용을 고려하라.

상수를 자주 사용할 때는 static import를 사용할 수도 있겠다.

인터페이스로는 타입을 정의해야지 상수를 표현하지 않아야 한다.