Effective Java 2판 - 6장 정리 (Annotation)

규칙 30: 상수 대신 enum을 사용해라.

Enumerated type은 고정 개수의 상수들로 값이 구성되는 자료형이다. Javaenum 자료형과 비슷하지만 다른 언어들보다 강력하다. 다른 언어들의 enum은 결국 int값이다.

Enum 자료형은 실질적으로 final로 선언된 것이나 마찬가지여서 안전하다.

Enum 상수에 데이터를 넣으려면 객체 필드를 선언하고 생성자를 통해 받은 데이터를 그 필드에 저장하면 된다.

모든 enum 상수를 선언된 순서대로 저장하는 배열을 반환하는 static values 메서드가 기본으로 정의되어 있음에 주의하자.

Enum 자료형에 상수별 class body 안에서 실제 메서드로 재정의할 수 있다.

public enum Operation {
	PLUS(“+”) {
double apply(double x, double y){return x+y;} 
},
	MINUS(“-“) {
double apply(double x, double y){return x-y;}
};

private final String symbol;
Operation(String symbol) {this.symbol = symbol; }
@Override public String toString() {return symbol;}
	
	abstract double apply(double x, double y);
}

 

 

규칙 31: ordinal 대신 객체 필드를 사용해라.

모든 enum에는 ordinal이라는 메소드가 있는데, enum 자료형 안에서 enum 상수의 위치를 나타내는 정수값을 반환한다. 하지만 이는 상수 순서가 변경하는 순간 관련된 메소드가 모두 깨지기 떄문에 유지보수 관점에서 끔찍하다.

따라서 enum 상수에 연계되는 값을 ordinal을 사용해 표현하지 말고 그런 값이 필요하다면 그 대신 instance field에 저장해라.

 

규칙 32: bit field 대신 EnumSet을 사용해라.

집합을 비트 필드로 나타내면 bitwise 연산을 통해 합집합, 교집합 연산이 효율적이지만 Enum으로도 충분히 가능하다.

 

규칙 33: ordinal을 배열 첨자로 사용하는 대신 EnumMap을 이용하라.

표현해야 하는 관계가 다차원적이라면 EnumMap<.., EnumMap<…>>과 같이 표현하면 된다.

public enum Phase{
	SOLID, LIQUID, GAS;
	Public enum Transition {
		MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
		BOIL(LIQUID, GAS);
		private final Phase src;
		private final Phase dst;
		Transition(Phase src, Phase dst){
			this.src=src;
			this.dst=dst;
		}
		private static final Map<Phase, Map<Phase, Transition>> m = 
			new EnumMap<Phase, Map<Phase, Transition>>(Phase.class);
		static {
			for (Phase p: Phase.values())
				m.put(p, new EnumMap<Phase, Transition>(Phase.class));
			for (Transition trans: Transition.values())
				m.get(trans.src).put(trans.dst, trans);
		}
}
}

 

규칙 34: 확장 가능한 enum을 만들어야 한다면 interface를 이용해라.

계승 가능한 enum 자료형은 만들 수 없지만 interface를 만들고 그 interface를 구현하는 기본 enum 자료형을 만들면 계승 가능 enum 자료형을 흉내낼 수 있다.

 

규칙35: 작명 패턴(Naming pattern) 대신 annotation을 사용해라.

자바 1.5 이전에는 도구나 프레임워크가 특별히 취급해야 하는 프로그램 요소를 구별하기 위해 Naming pattern을 사용했다. 예를 들어 JUnit 프레임워크는 메소드 이름을 test로 시작하는 등.

하지만 철자를 틀리거나 특정한 프로그램 요소에만 적용될 수 있도록 할 수 없다는 점과 같은 큰 문제점이 있다.

이런 문제를 해결하기 위해 나온 것이 annotation이다.

Spring을 해봤다면 annotation에는 익숙할 것이다.

 

어노테이션에 대해서

기본적으로 annotation에 대해서는

https://velog.io/@jkijki12/annotation

 

[Java] 어노테이션이 뭔데??

자바 어노테이션에 대해서 공부하자!!

velog.io

 

정리된 많은 글이 있습니다. 참고해주세요.  

 

 

@Target의 경우 해당 어노테이션이 부착될 수 있는 타입을 지정해주는 것입니다. 클래스, 생성자, 메소드, 필드 등으로 지정해줄 수 있습니다. 지정된 Type의 경우 클래스/인터페이스/열거(Enum) 타입에 부착할 수 있음을 말합니다.

 

@Retention의 경우 해당 어노테이션이 언제까지 살아 남아 있을지를 정해주는 것입니다. SOURCE, CLASS, RUNTIME으로 지정해줄 수 있습니다. 소스의 경우 .java 즉 소스코드 까지. 그리고 CLASS의 경우 .class까지 즉 바이트 코드까지, 그리고 .RUNTIME은 런타임까지, 즉 사라지지 않습니다.

 

💡 SOURCE 정책

Getter / Setter 같은 경우 lombok이 바이트 '코드를 생성'해서 넣어주는 것이기 때문에, 굳이 바이트코드에 어노테이션 정보가 들어갈 필요가 없습니다. (왜냐하면 롬복이 코드를 생성해주니까..) 즉 lombok은 source 정책을 사용합니다.

💡RUNTIME 정책

런타임에 어노테이션 정보를 뽑아 쓸수 있다는 의미입니다. 즉, Reflection API 등을 사용하여 어노테이션 정보를 알수가 있다는 의미입니다. 스프링을 예로 들자면, @Controller, @Service, @Autowired 등이 있습니다. 스프링이 올라오는 실행 중인 시점에 컴포넌트 스캔이 가능해야하기 때문에 RUNTIME 정책이 필요합니다. (스프링도 내부적으로 Reflection 등을 활용하여 어노테이션이 붙은 놈들만 가져옵니다. 저희가 구현할 것입니다!)

💡CLASS 정책

사실 이 친구가 왜 필요한건지 싶을 것 같습니다. "아니 Reflection 같은걸로 정보를 얻을수도 없으면서 왜 필요한거지? 바이트에서만 정보를 뽑아올 게 있나..?"

이건 IDE 지원을 위해 필요합니다. intelliJ에선, @NonNull 등이 붙어있는 경우 null 값을 넣게되면 경고로 알려줍니다.

"아니 그러면 SOURCE로 해도 될거 같은데?" 하실 수 있지만, 중요한 점은 Maven/Gradle로 다운받은 라이브러리와 같이 jar 파일에는 소스가 포함되어있지 않습니다. class 파일만 포함되어있죠 (Download Sources 옵션은 논외로 할께요)

즉, class 파일만 존재하는 라이브러리 같은 경우에도 타입체커, IDE 부가기능 등을 사용할수 있으려면 CLASS 정책이 필요하게 됩니다. SOURCE 정책으로 사용한다면 컴파일된 라이브러리의 jar 파일에는 어노테이션 정보가 남아있지 않기 때문입니다.

그 외에도 기본적으로 클래스 로딩시 무언가를 하고 싶은 경우에도 사용될수도 있으니깐요!

 

규칙 36: Override annotation은 일관되게 사용하라.

상위 클래스에 선언된 메소드를 재정의할 때는 반드시 선언부에 Override annotation을 붙여야 한다. non-abstract class에서 abstract method를 재정의할 때는 붙이지 않아도 된다. 물론 붙여도 상관은 없다.

 

규칙37: 자료형을 정의할 때 marker interface를 사용하라.

marker interface란 아무 메소드도 선언하지 않은 interface이다. Serializable interface가 그 예이다. marker annotation과 어떤 차이가 있을까? 크게 두 가지 장점이 있다.

1.     marker interface는 결국 marker class가 만드는 객체들이 구현하는 자료형이라는 점이다. marker annotation은 자료형이 아니다. 따라서 runtime이 아닌 compile time에 오류 검증이 가능하다.

2.     적용 범위를 좀 더 세밀하게 지정할 수 있다. annotation은 어떤 클래스나 인터페이스에도 적용 가능하지만 interface는 특정 interface extends 하도록 선언하기만 하면 된다.

반면 marker annotation의 장점은 더 많은 정보를 추가할 수 있다는 것이다. 또한 더 큰 annotation 기능의 일부라는 장점도 있다.