chapter 12 지네릭스(Generics)

JMG·2022년 2월 2일
0

자바의 정석

목록 보기
13/13

📖 지네릭스(Generics)

컴파일시 타입을 체크해주는 기능입니다.
이전까지는 타입이 체크가 안되어 runtime error가 발생하던것을 지네릭스를 만들어 complie error로 변경해서 미리 형 변환 에러를 잡기 용이합니다.
객체의 타입 안정성을 높이고 형 변환의 번거로움을 줄여줍니다.(코드의 간결)

//Tv객체만 저장할 수 있는 ArrayList를 생성
ArrayList<Tv> tvList  = new ArrayList<Tv>();

tvList.add(new Tv());
tvList.add(new Audio()); //컴파일에러발생!

//Tv t = (Tv)tvList.get(0); // 형변환 불필요
Tv t = tvList.get(0);

JDK1.5(2000년대초)부터 아래와 같이 지네릭 클래스로 변경된 컬렉션 프레임워크가 등장했습니다.
ArrayList(일반클래스) -> ArrayList(지네릭클래스)

📌 타입변수

클래스를 작성할때, Object타입 대신 타입 변수(E)를 선언해서 사용합니다.

public class ArrayList extends AbstractList {
	private transient Object[] elementData;
	public boolean add(Object o) { }
	public Object get(int index) { }
	. . .
}

//위 클래스를 아래와 같이 변경합니다.

public class ArrayList<E> extends AbstractList<E> {
	private transient E[] elementData;
	public boolean add(E o) { }
	public E get(int index) { }
	. . .
}

객체를 생성할 시, 타입변수(E) 대신 실제 타입을 대입해줍니다.

ArrayList<Tv> tvList = new ArrayList<Tv>();

📌 지네릭스 용어

Box<T> - 지네릭 클래스. 'T의 Box'또는 'T Box'라고 읽는다.
T - 타입변수 또는 타입 매개변수.(T는 타입문자)
Box - 원시타입(일반클래스)

//아래 String은 대입된 타입(매개변수화된 타입)이라고 부른다.
//Box<String>은 지네릭 타입 호출을 한 모습니다.
Box<String> b = new Box<String>(); 

📌 지네릭 타입과 다형성

참조 변수와 생성자의 대입된 타입은 일치해야합니다.
상속관계에 있는 클래스들이라 할지라도 타입을 다르게 적으면 에러가 발생합니다.

class Product { }
class Tv extends Product { }
class Audio extends Product { }

ArrayList<Tv> list = new ArrayList<Tv>();
ArrayList<Product> list = new ArrayList<Tv>(); // 에러발생

//그러나 지네릭 클래스간의 다형성은 용인됩니다.
List<Tv> list = new ArrayList<Tv>();
List<Tv> list = new LinkedList<Tv>();

//참조변수 선언 후 매개변수의 다형성은 허용됩니다.
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());

지네릭 클래스의 활용예
Iterator<E>클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용합니다

//일반클래스
public interface Iterator {
	boolean hasNext();
	Object next();
	void remove();
}

//지네릭 클래스로 변경
public interface Iterator<E> {
	boolean hasNext();
	E next();
	void remove();
}

예제)

import java.util.*;



class Ex12_2 {
	public static void main(String[] args) {
		ArrayList<Student> list = new ArrayList<Student>();
		list.add(new Student("자바왕", 1, 1));
		list.add(new Student("자바짱", 1, 2));
		list.add(new Student("홍길동", 2, 1));

		Iterator<Student> it = list.iterator();
		//Iterator it = list.iterator();
		
		while(it.hasNext()) {
			Student s = it.next();
			//Student s = (Student)it.next();
			System.out.println(s.name);
		}
	}
}

class Student {
	String name = "";
	int ban;
	int no;

	Student(String name, int ban, int no) {
		this.name = name;
		this.ban = ban;
		this.no = no;
	}
}


결과)
자바왕
자바짱
홍길동

📌 HashMap<K, V>

여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언합니다.

public class HashMap<K,V> extends AbstractMap<K,V> {
. . .
public V get(Object key) { }
public V put(K key, v value) { }
public V remove(Object key) { }
. . .
}

get(), remove()의 key가 그냥 Object인 이유
내부적으로 쓰이는 hash메서드의 매개변수 타입이 Object이기 때문입니다.

예제)

import java.util.*;



class Ex12_2 {
        public static void main(String[] args) {
                //JDK1.7부터 생성자에 타입지정 생략가능
                HashMap<String, Student> map = new HashMap<>();
                map.put("자바왕", new Student("자바왕", 1, 2, 100, 100, 100));

                System.out.println(map);
                Student s = map.get("자바왕");
                System.out.println(s.name);
        }
}

class Student {
        String name = "";
        int ban;
        int no;
        int kor;
        int eng;
        int math;

        Student(String name, int ban, int no, int kor, int eng, int math) {
                this.name = name;
                this.ban = ban;
                this.no = no;
                this.kor = kor;
                this.eng = eng;
                this.math = math;
        }
}

결과)
{자바왕=Student@5ca881b5}
자바왕

📌 제한된 지네릭 클래스

extends로 대입할 수 있는 타입을 제한할 수 있습니다.

//Fruit의 자손객체만 지네릭 타입으로 제한
class FruitBox<T extends Fruit> {
	ArrayList<T> list = new ArrayList<T>();
	. . .
}

FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> appleBox = new FruitBox<Toy>(); // 에러

//인터페이스인 경우에도 extends를 사용합니다.
interface Eatable { }
class FruitBox<T extends Eatable> { }


//만약 인터페이스와 함께 타입제한을 지정하는 경우 아래와 같이 작성합니다.
class FruitBox<T extends Fruit & Eatable> { }

📌 지네릭스의 제약

  1. 타입 변수에 대입은 인스턴스 별로 다르게 가능하므로 static멤버에는 타입 변수 사용이 불가능합니다.
class Box<T> {
	static T item; // 에러
	static int compate(T t1, T t2) { } // 에서
	. . .
}
  1. 배열 생성할 때 타입 변수 사용이 불가능합니다. 타입변수로 배열 선언은 가능합니다.
class Box<T> {
	T[] itemArr;
	. . .
	T[] toArray() {
		T[] tmpArr = new T[item.Arr.length]; // 에러
		. . .
	}
}

📌 와일드 카드<?>

하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능합니다.
ArrayList<? extends Product> list = new ArrayList();
ArrayList<? extends Product> list = new ArrayList();

타입종류
<? extends T>와일드 카드의 상한 제한. T와 그 자손들만 가능
<? super T>와일드 카드의 하한 제한. T와 그 조상들만 가능
<?>제한없음. 모든 타입이 가능. <? extends Object>와 동일

이중 <? extends T>가 제일 많이 사용됩니다.

//메서드의 매개변수에 와일드를 사용할 수 있습니다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
	String tmp = "";
	for(Fruit f : box.getList()) {
		tmp += f + " ";
	}
	return new Juice(tmp);
}

📌 지네릭 메서드

지네릭 타입이 선언된 메서드를 의미합니다.(타입변수는 메서드 내에서만 유효)

static <T> void sort(List<T> list, Comparator<? super T> c)

클래스의 타입 매개변수와 메서드의 타입 매개변수 는 별개입니다.

class FruitBox<T> {
	. . .
	static <T> void sort(List<T> list, Comparator<? super T> c) {
	. . .
	}
}

메서드를 호출할 때마다 타입을 대입해야합니다.

class Juicer {
	static Juice makeJuice(FruitBox<? extends Fruit> box) {
		String tmp = "";
		for(Fruit f : box.getList()) {
			tmp += f + " ";
		}
		return new Juice(tmp);
	}
}

FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();

System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
System.out.println(Juicer.<Apple>makeJuice(appleBox));

📌 지네릭 타입의 형변환

순수 지네릭 타입간의 형변환은 불가능합니다.
지네릭 타입과 원시 타입간의 형변환은 가능하긴하나 바람직 하지 않습니다.

Box<Object> objBox = null;
Box box = (Box)objBox; // 가능하나 경고발생
objBox = (Box<Object>)box; // 가능하나 경고발생

와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능합니다.

Box<Object> objBox = (Box<Object>)new Box<String>(); // 에러
Box<? extends Object> wBox = (Box<? extends Object>)new Box<String>();
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일
//일반 제너릭 참조변수에 와일드카드 제너릭을 바인딩 할 수 있으나 경고가 발생합니다.
Box<String> sBox = (Box<? extends Object>)wBox;

📌 지네릭 타입의 제거

컴파일시 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣습니다.

  1. 지네릭 타입의 경계(bound)를 제거
class Box<T extends Fruit> {
	void add(T t) {
		. . .
	}
}

class Box {
	void add(Fruit t) {
		. . .
	}
}

//만약 지네릭 제한자가 있다면 해당 클래스로 그렇지 않다면 Object로 형변환됩니다.
//위와 같이 되는 이유는 하위호환성때문에 그러합니다.
//이전 버젼과 같이 호환되게 해야 안정성이 유지됩니다.
  1. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가해줍니다.
T get(int i) {
	return list.get(i);
}

Fruit get(int i) {
	return (Fruit)list.get(i);
}
  1. 와일드 카드가 포함되는 경우, 적절한 타입으로 형변환이 추가됩니다.
static Juice makeJuice(FruitBox<? extends Fruit> box) {
	String tmp = "";
	for(Fruit f : box.getList()) {
		tmp += f + " ";
	}
	return new Juice(tmp);
}

static Juice makeJuice(FruitBox box) {
	String tmp = "";
	Iterator it = box.getList().iterator();
	while(it.hasNext()) {
		tmp += (Fruit)it.next() + " ";
	}
	return new Juice(tmp);
}

📖 열거형(enum)

관련된 상수들을 같이 묶어 놓은 것, Java는 타입에 안전한 열거형을 제공합니다.

class Card {
	static final int CLOVER = 0;
	static final int HEART = 1;
	static final int DIAMOND = 2;
	static final int SPADE = 3;

	static final int TWO = 0;
	static final int THREE = 1;
	static final int FOUR = 2;

	final int kind;
	final int value;
}

//아래와 같이 열거형으로 변경
class Card {
	enum Kind {CLOVER, HEART, DIAMOND, SPADE}
	enum Value {TWO, THREE, FOUR}

	final Kind kind;
	final Value value;
}

비교연산자 이용시 값과 타입 둘다 체크합니다.
다른 타입끼리는 값이 같더라도 비교를 할 수 없습니다.

if(Card.Kind.CLOVER==Card.Value.TWO) //컴파일 에러. 

📌 열거형의 정의와 사용

열거형을 정의하는 방법

enum 열거형 이름 {상수명1, 상수명2, . . .}

열거형 타입의 변수를 선언하고 사용하는 방법

class Unit {
	int x, y; // 유닛의 위치
	Direction dir; // 열거형 인스턴스 변수를 선언(상수 1개만 저장가능)

	void init() {
		dir = Direction.EAST; // 유닛의 방향을 EAST로 초기화
	}
}

열거형 상수의 비교에는 ==와 compareTo()를 사용해야합니다.

if (dir==Direction.EAST) { // true
	x++;
} else if(dir > Direction.WEST) { // 에러발생, 비교연산자 사용불가
	. . .
} else if(dir.compareTo(Direction.WEST) > 0) { // compareTo를 사용해야 가능
	. . .
}

📌 열거형의 조상 - java.lang.Enum 클래스

모든 열거형은 Enum의 자손이며, 아래의 메서드를 상속받습니다.

메서드설명
Class getDclaringClass()열거형의 Class객체를 반환
String name()열거형 상수의 이름을 문자열로 반환
int ordinal()열거형 상수가 정의된 순서를 반환(0부터 시작)
T valueOf(Class enum Type, String name)지정된 열거형에서 name과 일치하는 열거형 상수를 반환

values(), valueOf()는 컴파일러가 자동으로 추가됩니다.

static E valueOf(String name) - 열거형 변수로 반환할때 사용합니다.
static E[] values() - 열거형이 갖고있는 모든 상수를 배열로 반환합니다.

예제)


enum Direct { EAST, SOUTH, WEST, NORTH }

class Ex12_5 {
        public static void main(String [] args) {
                Direct d1 = Direct.EAST;
                Direct d2 = Direct.valueOf("WEST");
                Direct d3 = Enum.valueOf(Direct.class, "EAST");

                System.out.println("d1="+d1); // d1=EAST
                System.out.println("d2="+d2); // d2=WEST
                System.out.println("d3="+d3); // d3=EAST

                System.out.println("d1==d2 ? "+(d1==d2)); // d1==d2 ? false
                System.out.println("d1==d3 ? "+(d1==d3)); // d1==d3 ? true
                System.out.println("d1.equals(d3) ? "+d1.equals(d3)); // d1.equals(d3) ? true
                
	//System.out.println("d2 > d3 ? "+(d2 > d3)); //에러
	//열거형은 비교연산자를 사용할 수 없기 때문에 compareTo()를 사용
                System.out.println("d1.compareTo(d3) ? " + (d1.compareTo(d3))); // d1.compareTo(d3) ? 0
                System.out.println("d1.compareTo(d2) ? " + (d1.compareTo(d2))); // d1.compareTo(d2) ? -2


                switch(d1) {
                        case EAST:		//Direct.EAST라고 쓸수 없다.
                                System.out.println("The direction is EAST");
                                break;
                        case SOUTH:
                                System.out.println("The direction is SOUTH");
                                break;
                        case WEST:
                                System.out.println("The direction is WEST");
                                break;
                        case NORTH:
                                System.out.println("The direction is NORTH");
                                break;

                }
	//The direction is EAST
        }
}

📌열거형에 멤버 추가하기

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

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

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

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

	private final int value;	// 정수를 저장할 필드
	
	//EAST(1), SOUTH(5), ...이 모든 상수들은 사실 생성자를 사용하는 것과 마찬가지 입니다.
	Direction(int value) { //생성자를 추가
		this.value = value;
	}

	public int getValue() {
		return value;
	}
}

열거형의 생성자는 묵시적으로 private이므로, 외부에 객체생성이 불가능 합니다.
Direction d = new Direction(1); // 에러. 열거형의 생성자는 외부에서 호출불가

예제)

enum Direct2 {
        EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^");

        private static final Direct2[] DIR_ARR = Direct2.values();
        private final int value;
        private final String symbol;

        Direct2(int value, String symbol) {
                this.value = value;
                this.symbol = symbol;
        }

        public int getValue() {
                return value;
        }

        public String getSymbol() {
                return symbol;
        }
        public static Direct2 of(int dir) {
                if (dir < 1 || dir > 4) {
                        throw new IllegalArgumentException("Invalid value :" + dir);
                }
                return DIR_ARR[dir - 1];
        }

        // 방향을 회전시키는 메서드. num의 값만큼 90도씩 시계방향으로 회전한다.
        public Direct2 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();
        }
}

class Ex12_6 {
        public static void main(String[] args) {
                for(Direct2 d : Direct2.values()) {
                        System.out.printf("%s = %d \n", d.name(), d.getValue());
                }
	//EAST = 1
	//SOUTH = 2
	//WEST = 3
	//NORTH = 4
               
	 Direct2 d1 = Direct2.EAST;
                Direct2 d2 = Direct2.of(1);

                System.out.printf("d1 = %s, %d \n", d1.name(), d1.getValue()); // d1 = EAST, 1
                System.out.printf("d2 = %s, %d \n", d2.name(), d2.getValue()); // d2 = EAST, 1

                System.out.println(Direct2.EAST.rotate(1)); // SOUTHV
                System.out.println(Direct2.EAST.rotate(2)); // WEST<
                System.out.println(Direct2.EAST.rotate(-1)); // NORTH^
                System.out.println(Direct2.EAST.rotate(-2)); // WEST<

        }
}

📖 애너테이션

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

설정파일(.xml)을 소스코드(.java)안에 함께 작성하는 방법을 찾다가 애너테이션이 탄생되었습니다.

/* ~ / - javadoc.exe를 위한 주석(문서화를 위한 특수주석)

애너테이션의 사용예

//@Test는 JUnit단위 테스트 프로그램을 위한 애너테이션입니다.
@Test	// 이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
	. . .
}

표준 애너테이션

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

애너테이션설명
@Override컴파일러에게 오버라이딩 메서드라는 것을 알린다.
@Deprecated앞으로 사용하지 않을것을 권장하는 대상에 붙인다.
@SuppressWarnings컴파일러의 특정 경고메세지가 나타나지 않게 해준다.
@SafeVarargs지네릭스 타입의 가변인자에 사용한다.
@FunctionalInterface함수형 인터페이스라는 것을 알린다.
@Nativenative메서드에서 참조되는 상수 앞에 붙인다.

메타 애너테이션

애너테이션을 만들때 사용합니다.

애너테이션설명
@Target애너테이션이 적용가능한 대상을 지정하는데 사용한다.
@Documented애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다.
@Inherited애너테이션이 자손 클래스에 상속되도록 한다.
@Retention애너테이션이 유지되는 범위를 지정하는데 사용한다.
@Repeatable애너테이션을 반복해서 적용할 수 있게 한다.

📌 표준애너테이션

@Override

오버라이딩을 올바르게 했는지 컴파일러가 체크하게 합니다.
오버라이딩할 때 메서드이름을 잘못적는 실수를 하는 경우가 많습니다.

예시)

class Parent {
	void parentMethod() { }
}

class Child extends Parent {
	//함수명의 대소문자를 틀리는 경우
	void parentmethod() { }
}

//아래와같이 변경해줍니다.
class Child extends Parent {
	@Override
	void parentmethod() { } // 컴파일러가 오류가 발생했다고 전달
}

@Deprecated

앞으로 사용하지 않을 것을 권장하는 필드나 메서드에 붙입니다.

예시)

//Date클래스의 getDate()

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

위 메서드를 사용할 경우 컴파일러에서 경고메세지가 나옵니다.

@FunctionalInterface

함수형 인터페이스에 붙이면, 컴파일러가 올바르게 작성했는지 체크합니다.(람다식에서 자세히..)
함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있습니다.
작성하지 않아도되나 사용할 시 실수를 줄여줍니다.

예시)

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

메서드가 한개가 아닐시 에러가 발생합니다.

@SuppressWarnings

컴파일러의 경고메세지가 나타나지 않게 억제합니다.
괄호()안에 억제하고자 하는 경고의 종류를 문자열로 지정합니다.

예시)

@SuppressWarnings("unchecked")	//지네릭스와 관련된 경고를 억제
ArrayList list = new ArrayList();	//지네릭 타입을 지정하지 않았음
list.add(obj);		

//둘이상의 경고를 동시에 억제하려면 다음과 같이 해야합니다.
@SuppressWarnings({"deprecation", "unchecked", "varargs"})

tip)

eclipse과 같은 ide에서는 컴파일시 자세한 경고문이 안나올 수 있습니다.
CLI환경에서 "javac -Xlint 파일명.java"다음과같이 컴파일하면 자세한 경고문을 볼 수 있습니다.

📌 메타 애너테이션

애너테이션을 만들 때 사용하는 애너테이션입니다.
메타 애너테이션은 java.lang.annotation패키지에 포함됩니다.

@Target

애너테이션을 정의할 때, 적용대상 지정에 사용합니다.

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppreddWarnings {
	String[] value();
}
대상타입의미
ANNOTATION_TYPE애너테이션
CONSTRUCTOR생성자
FIELD필드(멤버변수, enum상수)
LOCAL_VARIABLE지역변수
METHOD메서드
PACKAGE패키지
PARAMETER매개변수
TYPE타입(클래스, 인터페이스, enum)
TYPE_PARAMETER타입 매개변수
TYPE_USE타입이 사용되는 모든곳

예시)

import static.java.lang.annotation.ElementType.*;

@Target({FILED, TYPE, TYPE_USE})
public @interface MyAnnotation {  }	//MyAnnotation을 정의

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

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

@Retention

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

유지 정책의미
SOURCE소스파일에만 존재. 클래스파일에는 존재하지 않음
CLASS클래스 파일에 존재. 실행시에 사용불가. 기본값
RUNTIME클래스 파일에 존재. 실행시에 사용가능

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE) //컴파일시만 사용 
public @interface Override { }

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

@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME) //런타임중에도 사용
public @interface FunctionalInterface { }

@Documented, @Inherited

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

애너테이션을 자손클래스에서 상속하고자 할 때, @Inherited를 붙입니다.
예시)

@Inherited
@interface SuperAnno {  }

@SuperAnno
class Parent {  }

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

@Repeatable

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

예시)

@Repeatable(ToDos.class)
@interface ToDo {
	String value();
}

//@Repeatable인 @ToDo를 하나로 묶을 컨테이너 애너테이션도 정의해야합니다.
@interface ToDos { //여러개의 ToDo애너테이션을 담을 애너테이션 ToDos
	ToDo[] value(); // ToDo애너테이션 배열타입의 요소를 선언. 이름이 반드시 value여야함
}

//@Repeatable이 붙은 애너테이션은 반복해서 붙일 수 있습니다.
@ToDo("delete test codes")
@ToDo("override inherited methods")
class MyClass {
	. . .
}

📌 애너테이션 타입 정의하기

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

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

애너테이션의 메서드는 추상 메서드이며, 애너테이션을 적용할 때 지정합니다.(순서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;
}

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

요소가 하나이고 이름이 value일 때는 요소의 이름을 생략가능합니다.

@interface TestInfo {
	String value();
}

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

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

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

@TestInfo(testTools={"JUnit", "AutoTester"})
public class NewClass {
	. . .
}

📌 모든 애너테이션의 조상

java.lang.annotation.Annotation

Annotation은 모든 애너테이션의 조상이지만 상속은 불가능합니다.

@interface TestInfo extends Annotation { // 에러발생
	...
}

사실 Annotation은 인터페이스입니다.

package java.lang.annotation;

public interface Annotation {
	boolean equals(Object obj);
	int hashCode();
	String to String();
	
	Class<? extends Annotation> annotationType(); //애너테이션의 타입을 반환
}

📌마커애너테이션(Marker Annotation)

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

public @interface Override { }

public @interface Test { }

@Test
public void method() {
	. . .
}

📌애너테이션 요소의 규칙

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

  • 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용됩니다.
  • 예외를 선언할 수 없습니다.
  • 요소를 타입 매개변수로 정의할 수 없습니다.
@interface AnnoTest {
	int id = 100; // 상수는 써도된다
	String major(int i, int j); //에러 매개변수X
	String minor() throws Exception; // 에러 예외X
	ArrayList<T> list(); //에러 타입매개변수X
}
profile
초보개발 블로그입니다~

0개의 댓글