Link
Today
Total
10-17 04:29
Archives
관리 메뉴

초보개발자 긍.응.성

(이펙티브 자바 3) 6장 열거 타입과 애너테이션 정리 본문

책 정리/이펙티브 자바 3

(이펙티브 자바 3) 6장 열거 타입과 애너테이션 정리

긍.응.성 2020. 11. 27. 00:45
반응형

열거 타입 (Enum type)

자바 언어의 열거 타입 (enum type)은 Java 1.5 부터 등장하였다. 이전까지는 상수 선언 시 static final을 붙여 사용했었다.

정수 열거 패턴 혹은 문자열 열거 패턴은 타입 안전을 보장할 수 있는 방법이 없으며 표현력도 좋지 않다. 상수의 값이 바뀌면 클라이언트도 반드시 다시 컴파일해야 한다. 디버깅 시에도 의미를 파악하기 힘들며 상수의 개수가 몇개가 존재하는지도 알 수 없다.

열거 타입은 클래스이며 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개한다. 그렇기에 열거 타입 선언으로 만들어진 인스턴스들은 딱 하나씩만 존재하며 불변이다. 클래스이기에 타입 인전성을 제공한다. 열거 타입에는 각자의 이름공간(Namespace)가 있어서 이름이 같은 상수도 공존한다. 열거 타입에는 임의의 메서드나 필드를 추가할 수 있고 인터페이스를 구현할 수 있다. 열거 타입 내에서 공유해야하는 다른 열거 타입에 대하여 별도의 메서드가 동작해야한다면 전략 열거 타입 패턴을 적용하는 방법이 있다.

  • 상수별 메서드 구현
public enum Operation {
	PLUS("+") {
		public double apply(double x, double y) { return x + y; }
	},
	MINUS("-") {
		public double apply(double x, double y) { return x - y; }
	},
	TIMES("*") {
		public double apply(double x, double y) { return x * y; }
	},
	DIVIDE("/") {
		public double apply(double x, double y) { return x / y; }
	};

	private final String symbol;

	Operation(String symbol) { this.symbol = symbol; }
	
	// 상수별 메서드 구현 (constant-specific method implemtation)
	public abstract double apply(double x, double y);
}
  • 문자열에 맞는 열거 타입 가져오기 (fromString)
private static fianl Map<String, Operation> stringToEnum = 
		Stream.of(values()).collect(toMap(Object::toString, e -> e));

public static Optional<Operation> fromString(String symbol) {
	return Optional.ofNullable(stringToEnum.get(symbol));
}

ordinal 메서드 대신 인스턴스 필드를 사용하라

모든 열거 타입은 해당 상수가 그 열거 타입에서 몇 번째 위치인지를 반환하는 ordinal 이라는 메서드를 제공한다. 이를 이용하여 열거 타입 상수의 인덱스롤 활용하지 말자. 순서가 바뀌거나 중간에 다른 상수값이 추가되는 경우 재앙이 시작된다. 이런 경우 인스턴스 필드에 저장하도록 하자. 대부분의 프로그래머들은 ordinal 메서드를 사용할 일은 없다는것을 명심하자.

비트 필드 대신 EnumSet을 이용하라

열거할 수 있는 타입을 한데 모아 집합형태로 사용할때 비트 필드를 이용하는 경우가 있는데, 그럴 필요 없이 EnumSet을 이용하자. java.util 패키지의 EnumSet 클래스는 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해준다. Set 인터페이스를 완벽히 구현하며, 타입 안전하고, 다른 어떤 Set 구현체와도 함께 사용할 수 있다. 또한, 원소의 개수가 64개 이하라면 내부적으로 EnumSet전체를 long 변수 하나로 표현하여 비트 필드에 비견되는 성능을 보여준다.

public class Text {
	public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH }

	public void applyStyles(Set<Style> styles) { ... }
}

EnumSet에서 제공하는 다양한 정적 팩터리을 사용하여 다음과 같이 메서드를 호출할 수 있다.

text.applyStyles(EnumSet.of(Style.BOLD, Style.ITALIC));

EnumSet의 단점으로는 불변 EnumSet을 만들 수 없다는 것이다.

ordinal 인덱싱 대신 EnumMap을 사용하라

열거 타입에 대하여 그룹핑할때 가진 열거 타입 배열의 인덱스를 가져와(ordinal()) 이용하는 경우가 있지만 이는 좋은 방법이 아니다. 대신 EnumMap을 사용하자. 스트림 API를 사용하여 원하는 컬렉션을 열거 타입으로 쉽게 묶어낼수 있다. 다차원 관계일 경우 EnumMap<..., EnumMap<...>>으로 표현하자 (웬만해서는 Enum.ordinal은 사용하지 말자).

확장할 수 있는 열거 타입이 필요하면 인터페이스를 사용하라

열거 타입은 확장할 수 없다. 하지만, 인터페이스를 이용하여 비슷한 효과를 낼 수 있다. 클라이언트가 자신만의 인터페이스를 만들고 기본 열거 타입과 새로운 열거 타입(확장하려는 열거 타입)을 그 인터페이스를 구현하도록 한다. 그리고 연산의 주체로 기존 열거 타입이 사용된 부분을 인터페이스를 사용하도록 수정하자. 그렇게 할 경우 기본 열거 타입과 새로운 열거 타입은 모두 같은 인터페이스를 사용하였기에, 기본 열거 타입의 인스턴스가 쓰이는 모든 곳을 새로 확장한 열거 타입의 인스턴스로도 사용할 수 있다.

명명 패턴보다 애너테이션을 사용하라

JUnit 같은 경우 테스트 메서드의 이름을 test로 시작하게끔 하는 명명패턴을 이용했다. 하지만 명명패턴은 오타를 내면 안된다는 것, 올바른 프로그램 요소에서만 사용되리라 보증할 방법이 없다는 것, 프로그램 요소를 매개변수로 전달할 마땅한 방법이 없다는 단점들이 있다. 이의 해결 방법으로 애너테이션을 사용하라 애너테이션은 코드에 직접적인 영향을 주지는 않지만 단순히 대상에 마킹을 하여 프로그래머에게 더 의미를 보기쉬우며 명확하게 전달하고, 리플렉션 API를 통해 애너테이션에 대하여 특별한 처리를 할 수 있도록 한다.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Test {
}
  • Retention - 유지 범위 (런타임)
  • Target - 선언 위치 (메서드)

@Override 애너테이션을 일관되게 사용하라

재정의한 모든 메서드에 @Override 애너테이션을 달아주자. 예외는 구체 클래스에 상위 클래스의 추상 메서드를 재정의한 경우 뿐이다.

정의하려는 것이 타입이라면 마커 인터페이스를 사용하라

마커 인터페이스 - 아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스

마커 인터페이스와 마커 애너테이션은 각자의 쓰임이 있다. 새로 추가하는 메서드가 없이 단지 타입 정의가 목적이라면 마커 인터페이스를 사용하자. 클래스나 인터페이스 외 프로그램 요소에 마킹해야 하거나, 애너테이션을 적극 활용하는 프레임워크의 일부로 그 마커를 편입시키고자 한다면 마커 애너테이션이 올바른 선택이다.

ELEMENT.TYPE인 마커 애너테이션을 작성하고 있다면, 마커 인터페이스가 낫지는 않을 시 고민해보자.

반응형
Comments