열거형에 멤버 추가하기

- 불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적는다.

enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }

 

괄호()를 사용하려면, 인스턴스 변수와 생성자를 새로 추가해 줘야 한다.

enum Direction {
	EAST(1), SOUTH(5), WEST(-1), NORTH(10);
	
	private final int value; // 정수를 저장할 필드(인스턴스 변수)를 추가
	Direction (int value) { this.value = value; } // 생성자를 추가

	public int getValue() { return value; }
}

 

- 열거형의 생성자는 묵시적으로 private이므로, 외부에서 객체생성 불가

Direction d = new Direction(1); // 에러, 열거형의 생성자는 외부에서 호출 불가

 

ex)

enum Direction2 {
	EAST(1, ">"), SOUTH(2,"V"), WEST(3, "<"), NORTH(4, "^");
	
	private static final Direction2[] DIR_ARR = Direction2.values();
	private final int value;
	private final String symbol;
	
	Direction2 (int value, String symbol) { // 접근 제어자 private이 생략됨
		this.value = value;
		this.symbol = symbol;
	}
	
	public int getValue()	  { return value;  }
	public String getSymbol() { return symbol; }
	
	public static Direction2 of(int dir) {
		if (dir <1 || dir >4)
			throw new IllegalArgumentException("Invalid value : " + dir);
		
		return DIR_ARR[dir - 1];
	}
	
	// 방향을 회전시키는 메소드. num의 값만큼 90도씩 시계방향으로 회전한다.
	public Direction2 rotate(int num) {
		num = num%4;
		
		if (num<0) num+=4; // num이 음수일 때는 시계 반대방향으로 회전
		
		return DIR_ARR[(value-1+num) % 4];
	}
	
	public String toString() {
		return name()+getSymbol();
	}
	
}
public class Ex12_6 {

	public static void main(String[] args) {
		for(Direction2 d : Direction2.values())
			System.out.printf("%s=%d%n", d.name(), d.getValue());
		
		Direction2 d1 = Direction2.EAST;
		Direction2 d2 = Direction2.of(1);
		
		System.out.printf("d1=%s, %d%n", d1.name(), d1.getValue());
		System.out.printf("d2=%s, %d%n", d2.name(), d2.getValue());
		System.out.println(Direction2.EAST.rotate(1));
		System.out.println(Direction2.EAST.rotate(2));
		System.out.println(Direction2.EAST.rotate(-1));
		System.out.println(Direction2.EAST.rotate(-2));
		

	}

}

 

 

애너테이션이란?

- 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공

 

- 애너테이션의 사용예

@Test // 이 메소드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
	...
}

 

표준 애너테이션

- Java에서 제공하는 애너테이션

(메타 애너테이션은 애너테이션을 만들 때 사용)

 

@Override

- 오버라이딩을 올바르게 했는지 컴파일러가 체크하게 한다.

- 오버라이딩할 때 메소드 이름을 잘못적는 실수를 하는 경우가 많다.

- 오버라이딩 할 때는 메소드 선언부 앞에 @Override를 붙이자.

 

@Deprecated

- 앞으로 사용하지 않을 것을 권장하는 필드나 메소드에 붙인다.

- @Deprecated의 사용 예, Date클래스의 getDate()

- @Deprecated가 붙은 대상이 사용된 코드를 컴파일하면 나타나는 메시지

Note : AnnotationEx2.java uses or overrides a deprecated API.

Note : Recompile with Xlint:deprecation for details.

 

Xlint:deprecation을 추가해서 다시 컴파일하면 다음과 같이 원인이 나온다.

원인 - [deprecation]

 

@FunctionalInterface

- 함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크

- 함수형 인터페이스에는 단 하나의 추상메소드만 가져야 한다는 제약이 있음

 

@FunctionalInterface
public interface Runnable {
	public abstract void run();	// 추상 메소드
}

 

 

@SuppressWarnings

- 컴파일러의 경고메세지가 나타나지 않게 억제한다.

- 괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정

@SuppressWarnings(“unchecked”)	// 지네릭스와 관련된 경고를 억제
ArrayList list = new ArrayList();		// 지네릭 타입을 지정하지 않았음.
list.add(obj);	// 여기서 경고가 발생

 

- 둘 이상의 경고를 동시에 억제하려면 다음과 같이 한다.

@SuppressWarnings({"deprecation", "unchecked", "varargs"})

 

- ‘-Xlint’ 옵션으로 컴파일하면, 경고메세지를 확인할 수 있다.

괄호[]안이 경고의 종류. 아래의 경우 rawtypes

 

 

ex)

import java.util.ArrayList;

class Parent {
	void parentMethod() {}
}
class Child extends Parent {
	@Override
	@Deprecated
	void parentMethod() {} // 조상 메소드의 이름을 잘못 적으면 오류가 발생함.
}

@FunctionalInterface // 함수형 인터페이스는 하나의 추상메소드만 가능
interface Testable {
	void test(); // 추상메소드
//	void check(); // 추상메소드
	
}

class Ex12_7 {
	@SuppressWarnings("deprecation")
	public static void main(String[] args) {
		Child c = new Child();
		c.parentMethod(); // deprecated된 메소드 사용
		
		
	}
}

 

 

 

메타 애너테이션

- 메타 애너테이션은 애너테이션을 위한 애너테이션

 

 

@Target

- 애너테이션을 정의할 때, 적용대상 지정에 사용

@Target({FIELD, TYPE, TYPE_USE})	// 적용대상이 FIELD, TYPE, TYPE_USE
public @interface MyAnnotation { } 	// MyAnnotation을 정의

@MyAnnotation		// 적용대상이 TYPE인 경우
class MyClass {
	@MyAnnotation // 적용대상이 FIELD인 경우
	int i;

	@MyAnnotation // 적용대상이 TYPE_USE인 경우
	MyClass mc;
}

 

 

@Retention

- 애너테이션이 유지(retention)되는 기간을 지정하는데 사용

 

- 컴파일러에 의해 사용되는 애너테이션의 유지 정책은 SOURCE이다.

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}

(오버라이딩 체크는 컴파일러가 컴파일 할 때 함. 그러므로 유지정책이 SOURCE)

 

- 실행시에 사용 가능한 애너테이션의 정책은 RUNTIME이다.

@Documented
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

 

 

@Documented, @Inherited

- javadoc으로 작성한 문서에 포함시키려면 @Documented를 붙인다.

@Documented
@Retention(RetentionPolicy.RUNTIME) 
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

 

- 애너테이션을 자손 클래스에 상속하고자 할 때, @Inherited를 붙인다.

@Inherited	// @SuperAnno가 자손까지 영향 미치게
@interface SuperAnno {}

@SuperAnno
class Parent {}

class Child extends Parent {} // Child에 애너테이션이 붙은 것으로 인식

 

 

 

@Repeatable

- 반복해서 붙일 수 있는 애너테이션을 정의할 때 사용

@Repeatable(ToDos.class) // ToDo애너테이션을 여러 번 반복해서 쓸 수 있게 한다.
@interface ToDo {
	String value();
}

 

- @Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있다.

@ToDo("delete test codes.")
@ToDo("override inherited methods")
class MyClass {
	...
}

 

 

- @Repeatable@ToDo를 하나로 묶을 컨테이너 애너테이션도 정의해야 함

@interface ToDos { // 여러개의 ToDo애너테이션을 담을 컨테이너 애너테이션 ToDos
	ToDo[] value();	// ToDo애너테이션 배열타입의 요소를 선언. 이름이 반드시 value이어야 함
}

 

 

애너테이션 타입 정의하기

- 애너테이션을 직접 만들어 쓸 수 있다.

@interface 애너테이션이름 {
	타입 요소이름(); // 애너테이션의 요소를 선언한다.
	...
}
@interface DateTime {
	String yymmdd();
	String hhmmss();
}

 

 

- 애너테이션의 메소드는 추상 메소드이며, 애너테이션을 적용할 때 지정(순서X)

@interface TestInfo {
	int count();
	String testedBy();
	String[] testTools();
	TestType testType(); // enum TestType { FIRST, FINAL }
	DateTime testDate(); // 자신이 아닌 다른 애너테이션(@DateTime)을 포함할 수 있다.

 

실제 사용할 때)

@TestInfo (
	Count=3, testedBy="Kim",
	testTools={"JUnit", "AutoTester"},
	testType=TestType.FIRST,
	testDate=@DateTime(yymmdd="160101", hhmmss="235959")
)
public class NewClass { ... }

 

 

애너테이션의 요소

- 적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)

@interface TestInfo {
	int count() default 1;	// 기본값을 1로 지정
}

@TestInfo	// @TestInfo(count=1)과 동일
public class NewClass { ... }
 

- 요소가 하나이고 이름이 value일 때는 요소의 이름 생략가능

@interface TestInfo {
	String value();
}

@TestInfo("passed")	// @TestInfo(value="passed")와 동일
class NewClass { ... }

 

- 요소의 타입이 배열인 경우, 괄호{}를 사용해야 한다.

@interface TestInfo {
	String[] testTools();
}

@TestInfo(testTools={"JUnit", "AutoTester"}) // 값이 2개 이상일 때
@TestInfo(testTools="JUnit") // 값이 1개일 때
@TestInfo(testTools={})	// 값이 없을 때는 괄호{}가 반드시 필요

 

 

 

모든 애너테이션의 조상

- Annotation은 모든 애너테이션의 조상이지만 상속은 불가

- Annotation은 인터페이스이다.

package java.lang.annotation;

public interface Annotation {	
	boolean equals(Object obj);
	int hashCode();
	String toString();

	Class<? extends Annotation> annotationType(); // 애너테이션의 타입을 반환
}

(모든 에너테이션은 추상메소드를 구현하지 않아도 사용가능)

 

 

마커 애너테이션 Marker Annotation

- 요소가 하나도 정의되지 않은 애너테이션

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {} // 마커 애너테이션, 정의된 요소가 하나도 없다.

 

 

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Test {}	//  마커 애너테이션, 정의된 요소가 하나도 없다.
@Test // 이 메소드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
	...
}

 

 

@Deprecated
public int getDate() {
	return normalize().getDayOfMonth();
}

 

 

애너테이션 요소의 규칙

- 애너테이션의 요소를 선언할 때 아래의 규칙을 반드시 지켜야 한다.

요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용됨

괄호()안에 매개변수를 선언할 수 없다.

예외를 선언할 수 없다.

요소를 타입매개변수(<T>)로 정의할 수 없다.

 

- 아래의 코드에서 잘못된 부분은 무엇인지 생각해보자.

@interface AnnoTest {
	int id = 100; // 상수 OK
	String major(int a, int b); // 에러, 매개변수X
	String minor() throws Exception; // 에러, 예외선언X
	ArrayList<T> list(); // 에러, 타입매개변수 정의X
}

 

 

 

ex)

 

import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Deprecated
@SuppressWarnings("1111") // 유효하지 않은 애너테이션은 무시된다.
@TestInfo(testedBy="aaa", testDate=@DateTime(yymmdd="160101",hhmmss="235959"))
public class Ex12_8 {

	public static void main(String[] args) {
			// Ex12_8의 Class객체를 얻는다.
			Class<Ex12_8> cls = Ex12_8.class;
			
			TestInfo anno = cls.getAnnotation(TestInfo.class);
			System.out.println("anno.testedBy()="+anno.testedBy());
			System.out.println("anno.testDate().yymmdd()="+anno.testDate().yymmdd());
			System.out.println("anno.testDate().hhmmss()="+anno.testDate().hhmmss());
			
			for(String str : anno.testTools()) 
					System.out.println("testTools="+str);
			
			System.out.println();
			
			// Ex12_8에 적용된 모든 애너테이션을 가져온다.
			Annotation[] annoArr = cls.getAnnotations();
			
			for(Annotation a : annoArr)
					System.out.println(a);

	}

}

@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정
@interface TestInfo {
		int count()		default 1;
		String testedBy();
		String[] testTools()	default "JUnit";
		TestType testType()		default TestType.FIRST;
		DateTime testDate();
}

@Retention(RetentionPolicy.RUNTIME) // 실행 시에 사용가능하도록 지정
@interface DateTime {
		String yymmdd();
		String hhmmss();
}

enum TestType { FIRST, FINAL }

출처 - 유튜브 남궁성의 정석코딩 [자바의 정석 - 기초편] 

+ Recent posts