on
[JAVA] 자바의 정석 기초편 [Ch12] - Always From The Ground Up
[JAVA] 자바의 정석 기초편 [Ch12] - Always From The Ground Up
서문
자바의 정석 기초편 챕터 12편을 기재합니다.
목적은 공부한 내용을 기록하는 것에 있기 때문에 완전한 문장이 아닐 수도 있습니다.
또한 모든 내용을 적은 것은 아닙니다.
참고 자료 자바의 정석 기초편 강좌 : https://www.youtube.com/playlist?list=PLW2UjW795-f6xWA2_MUhEVgPauhGl3xIp
자바의 정석 ( ch.12 )
제네릭스(Generics)
컴파일시 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
- JDK1.5 매우 중요한 기능(런타임에러보단 컴파일시에 알려주는 에러가 더 나음)
객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌
1. 제네릭스 사용 예시 ArrayList tvList = new ArrayList(); tvList.add(new Tv()); // OK tvList.add(new Audio()); // 컴파일 에러, Tv외의 타입이 들어오면 컴파일 시점에서 에러가 남 ----------------------------------------- 2. 컴파일러의 한계 예시 class Ex{ public static void main(String[] args){ ArrayList intList = new ArrayList(); intList.add(10); intList.add(20); intList.add("30"); // String Integer i = (Integer)intList.get(2); // 컴파일 시에는 에러가 안나고 런타임 때 에러 발생, ClassCastException 발생 System.out.println(intList); } } ------------------------------------------ 3. 제네릭스 사용으로 컴파일러에서 체크(코드를 실행하기 전에 에러표시) class Ex2{ public static void main(String[] args){ ArrayList intList = new ArrayList(); intList.add(10); intList.add(20); intList.add("30"); // The method add(Integer) in the type ArrayList is not applicable for the arguments (String) } }
타입 변수
클래스를 작성할 때, Object타입 대신 타입 변수(주로 E or T / 다른 알파벳)를 선언해서 사용
객체를 생성시, 타입 변수 대신 실제 타입을 지정(대입)
타입 변수 대신 실제 타입이 지정되면 형변환 생략가능
1. 타입 변수 기본 예시 public class ArrayList extends AbstractList{ private transient Object[] elementData; public boolean add(Object o){} public Object get(int index){} ... } public class ArrayList extends AbstractList{ private transient E[] elementData; public boolean add(E o){} public E get(int index){} ... } ------------------------------------------------ 2. 타입 변수 예제 class Ex{ public static void main(String[] args){ ArrayList lst = new ArrayList(); lst.add(new Tv()); lst.add(new Audio()); // 컴파일 에러 } }
제네릭스 용어
Box : 제네릭 클래스, 'T의 Box' 또는 'T Box'라고 읽음
T : 타입 변수 또는 타입 매개변수(T는 타입 문자(type))
Box : 원시 타입(raw type)
class Box{} Box b = new Box(); // 생성할 때마다 다른 타입을 넣어줄 수 있음,
제네릭스 타입과 다형성
참조 변수와 생성자의 대입된 타입은 일치해야 함
ArrayList lst = new ArrayList(); // OK ArrayList lst = new ArrayList(); // ERROR , 불일치
제네릭 클래스간의 다형성은 성립(여전히 대입된 타입은 일치해야 함)
List lst = new ArrayList(); // OK 다형성, ArrayList가 List를 구현 List lst = new LinkedList(); // OK 다형성, linkedList가 List를 구현
매개변수의 다형성도 성립
class Product{} class Tv extends Product{} class Audio extends Product{} ArrayList lst = new ArrayList(); lst.add(new Product()); lst.add(new Tv()); // OK lst.add(new Audio()); // OK ----------------------------------- class Product{} class Tv extends Product{} class Audio extends Product{} class Ex{ public static void main(String[] args){ ArrayList productLst = new ArrayList(); ArrayList tvLst = new ArrayList(); // ArrayList tvLst = new ArrayList(); // 에러. 제네릭 대입 타입이 일치하지 않음 // List tvLst = new ArrayList(); // OK, 참조변수 다형성 OK productLst.add(new Tv()); // public boolean add(E e) -> public boolean add(Product e) / product와 그 자손은 다 ok productLst.add(new Audio()); tvLst.add(new Tv()); tvLst.add(new Tv()); printAll(productLst); } public static void printAll(ArrayList lst){ for (Product p : lst){ System.out.println(p); } } } > Tv@442d9b6e > Audio@6996db8
Iterator
클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
1. Iterator와 Iterator 사용 예시 public interface Iterator{ boolean hasNext(); Object next(); void remove(); } Iterator it = lst.iterator(); while(it.hasNext()){ Student s = (Student)it.next(); ... } public interface Iterator{ boolean hasNext(); E next(); void remove(); } Iterator it = lst.iterator(); while(it.hasNext()){ Student s = it.next(); } ------------------------------------ 2. Iterator의 예제 class Ex{ public static void main(String[] args){ ArrayList lst = new ArrayList(); lst.add(new Student("자바",1,1)); lst.add(new Student("자바의 정석 기초편",1,2)); lst.add(new Student("자바의 정석 3편",2,1)); Iterator it = lst.iterator(); while(it.hasNext()){ Student s = 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; } } > 자바 > 자바의 정석 기초편 > 자바의 정석 3편
HashMap
여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
1. HashMap에 대한 구조와 사용 예시 public class HashMap extends AbstractMap{ ... public V get(Object key){} public V put(K key, V value){} public V remove(Object key){} } HashMap map = new HashMap(); // 생성 map.put("자바", new Student("자바", 1,1,100,100,100)); // 저장 ------------------------------ 2. HashMap 예제 class Ex{ public static void main(String[] args){ HashMap m = new HashMap<>(); // JDK1.7부터 new HashMap(); -> new HashMap<>()로 사용해도 가능 m.put("자바", new Student("자바",1,1,100,100,100)); Student s = map.get("자바"); // 키 String을 넣으면 Student가 나온다는 것을 알고 있기 때문에 형변환이 필요없음 System.out.println(m); } } 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; } }
제한된 제네릭 클래스
extends로 대입할 수 있는 타입을 제한
인터페이스인 경우에도 extends을 활용
타입 변수에 대입은 인스턴스 별로 다르게 가능
static멤버에 타입 변수 사용 불가
배열(또는 객체) 생성할 때, 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
1. 제한된 제네릭 사용 예시 class FruitBox{ ArrayList list = new ArrayList(); } FruitBox appleBox = new FruitBox(); // OK FruitBox appleBox = new FruitBox(); // error, Toy는 Fruit의 자손이 아님 -------------------------- 2. 제한된 제네릭 예제 interface Eatable{} class FruitBox extends Box{} class Box{ ArrayList lst = new ArrayList(); // item을 저장할 공간 void add(T item){lst.add(item);} // 추가 T get(int i){return lst.get(i);} // item 꺼냄 int size(){return lst.size();} public String toString(){return lst.toString();} } class Fruit implements Eatable{ public String toString(){return "Fruit";} } class Apple extends Fruit{ public String toString(){return "Apple";}} class Grape extends Fruit{ public String toString(){return "Grape";}} class Toy { public String toString(){return "Toy";}} class Ex{ public static void main(String[] args){ FruitBox fruitBox = new FruitBox(); FruitBox appleBox = new FruitBox(); FruitBox grapeBox = new FruitBox(); // FruitBox grapeBox = new FruitBox(); // error, 타입 불일치 // FruitBox> toyBox = new FruitBox(); // error, Toy는 Fruit의 자손이 아님 fruitBox.add(new Fruit()); fruitBox.add(new Apple()); fruitBox.add(new Grape()); appleBox.add(new Apple()); // appleBox.add(new Grape()); // error grapeBox.add(new Grape()); System.out.println("fruitbox - " + fruitBox); System.out.println("applebox - " + appleBox); System.out.println("grapebox - " + grapeBox); } } > fruitbox - [Fruit, Apple, Grape] > applebox - [Apple] > grapebox - [Grape] -------------------------------------------- 3. 제네릭 타입 변수 대입은 인스턴스 별로 다르게 가능 Box appleBox = new Box(); // apple 객체만 저장가능 Box grapeBox = new Box(); // grape 객체만 저장가능 -------------------------------------------- 4. static멤버에 타입 변수 사용 불가 // 모든 인스턴스에 공통이기 때문에 3번이랑 충돌 class Box{ static T item // error static int compare(T t1, T t2){} // 에러 } -------------------------------------------- 5. 배열(또는 객체) 생성할 때, 타입 변수 사용불가. 타입 변수로 배열 선언은 가능 class Box{ T[] itemArr; // OK, T타입의 배열을 위한 참조변수 ... T[] toArray(){ T[] tmpArr = new T [itemArr.length]; // error, 제네릭 배열 생성불가 -> new 다음에 T가 오면 X } }
와일드 카드
하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능
ArrayList lst = new ArrayList(); // OK ArrayList lst = new ArrayList(); // OK ArrayList lst = new ArrayList(); // 에러, 타입 불일치 : 와일드 카드의 상한 제한, T와 그 자손들만 가능 : 와일드 카드의 하한 제한, T와 그 조상들만 가능 : 제한 없음, 모든 타입이 가능. 와 동일
메서드의 매개변수에 와일드 카드 사용
1. 메서드의 매개변수로 와일드 카드 사용 예시 static Juice makeJuice(FruitBox box){ String tmp = ""; for(Fruit f: box.getList()){ tmp += f + " "; } return new Juice(tmp); } System.out.println(Juice.makeJuice(new FruitBox())); // Fruit 자손들은 다 사용 가능, 원래는 타입을 일치시켜줘야 하지만 와일드카드로 유연한 코드작성이 가능 System.out.println(Juice.makeJuice(new FruitBox())); --------------------------------------------------- 2. 예제 class Fruit{public String toString(){return "Fruit";}} class Apple extends Fruit{public String toString(){return "Apple";}} class Grape extends Fruit{public String toString(){return "Grape";}} class Juice{ String name; Juice(String name){ this.name = name + "Juice"; } public String toString(){return name;} } class Juicer{ static Juice makeJuice(FruitBox box){ String tmp = ""; for(Fruit f : box.getList()){ tmp += f + " "; } return new Juice(tmp); } } class Ex{ public static void main(String[] args){ FruitBox fruitBox = new FruitBox(); FruitBox appleBox = new FruitBox(); FruitBox grapeBox = new FruitBox(); fruitBox.add(new Apple()); fruitBox.add(new Grape()); appleBox.add(new Apple()); appleBox.add(new Apple()); System.out.println(Juicer.makeJuice(fruitBox)); System.out.println(Juicer.makeJuice(appleBox)); } } class FruitBox extends Box{} class Box{ ArrayList lst = new ArrayList(); void add(T item){lst.add(item);} T get(int i){return lst.get(i);} ArrayList getList(){return lst;} int size(){return lst.size();} public String toString(){return lst.toString();} } > Apple Grape Juice > Apple Apple Juice
제네릭 메서드
제네릭타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
static void sort(List lst, Comparator c)
클래스의 타입 매개변수 와 메서드의 타입 매개변수 는 별개
class FruitBox{ ... static void sort(List lst, Comparator c) ... }
메서드를 호출할 때마다 타입을 대입해야(대부분 생략가능)
FruitBox fruitBox = new FruitBox(); FruitBox appleBox = new FruitBox(); System.out.println(Juicer.makeJuice(fruitBox)); // Juicer. static Juice makeJuice(FruitBox box){ String tmp = ""; for (Fruit f : box.getList()) tmp += f + " "; return new Juice(tmp); }
메서드를 호출할 때 타입을 생략할 수 없는 경우, 클래스 이름 생략 불가
System.out.println(makeJuice(fruitBox)); // error, 클래스 이름 생략 불가 System.out.println(this.makeJuice(fruitBox)); // OK System.out.println(Juicer.makeJuice(fruitBox)); // OK
제네릭 타입의 형변환
제네릭타입과 원시 타입간의 형변환은 바람직하지 않음(경고 발생) 권장은 제네릭은 제네릭타입에 맞게, 원시타입은 원시타입에 맞게 사용하자.
Box objBox = null; Box box = (Box)objBox; // ok, 제네릭 타입 -> 원시 타입. 경고 발생 objBox = (Box)box; // ok, 원시 타입 -> 제네릭 타입. 경고발생 Box strBox = null; objBox = (Box)strBox; // error, Box -> Box strBox = (Box)objBox; // error, Box -> Box
와일드 카드가 사용된 제네릭 타입으로는 형변환 가능(생략가능) 와일드카드에서 명확한타입으로 바꿀 땐 형변환 생략불가
Box objBox = (Box)new Box(); // error, 형변환 불가 Box wBox = (Box) new Box(); // OK Box wBox = new Box(); // 위 문장과 동일한 문장, 형변환이 있지만 생략한 것
제네릭 타입의 제거
컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형변환을 넣음 하위 호환성의 이유(안정성)
제네릭 타입의 경계(bound)를 제거 class Box{ void add(T item){} }
class Box{
void add(Fruit item){
}
}
2. 제네릭 타입 제거 후에 타입이 불일치하면 형변환 추가 ```java T get(int i){ return lst.get(i); } Fruit get(int i){ return (Fruit)lst.get(i); }
와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가 static Juice makeJuice(FruitBox 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는 타입에 안전한 열거형을 제공 ```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 num; } class Card{ enum Kind{ CLOVER, HEART, DIAMOND, SPADE }; enum Value{ TWO, THREE, FOUR } final Kind kind; // 타입이 int가 아니라 Kind라는 점을 유의 final Value value; }
열거형의 정의와 사용 열거형을 정의하는 방법 enum 열거형이름 { 상수명1, 상수명2, ... 상수명N }
1. 열거형 타입의 변수를 선언하고 사용하는 방법 enum Direction { EAST, SOUTH, NORTH, WEST }; // 하나하나가 객체 class Unit{ int x,y; Direction dir; // 열거형 인스턴스 변수를 선언 void init(){ dir = Direction.EAST; // 열거형으로 초기화 } }
열거형 상수의 비교에 ==와 compareTo() 사용가능
if(dir == Direction.EAST){ // == 사용 가능 x++; } else if (dir == Direction.NORTH){ y++; } ... } else if(dir.compareTo(Direction.WEST) > 0){ // compareTo 사용가능 ... }
열거형의 조상 - java.lang.Enum
모든 열거형은 Enum의 자손이고, 아래의 메서드를 상속받음
메서드 설명 Class getDeclareClass() 열거형의 Class객체를 반환 String name() 열거형 상수의 이름을 문자형으로 반환 int ordinal() 열거형 상수가 정의된 순서를 반환(0부터 시작) T valueOf(Class enumType, String name) 지정된 열거형에서 name과 일치하는 열거형 상수를 반환
values(), valueOf()는 컴파일러가 자동으로 추가
static E[] values(); static E valueOf(String name); Direction[] dArr = Direction.values(); // Direction d = Direction.valueOf("WEST"); // Direction d = Direction.WEST; for(Direction d : dArr){ System.out.println("%s=%d%n", d.name(), d.ordinal()); }
enum Direction { EAST, SOUTH, WEST, NORTH} // 0,1,2,3 class Ex{ public static void main(String[] args){ Direction d1 = Direction.EAST; Direction d2 = Direction.valueOf("WEST"); Direction d3 = Enum.valueOf(Direction.class, "EAST"); System.out.println("d1 = " + d1); System.out.println("d2 = " + d2); System.out.println("d3 = " + d3); System.out.println("d1 == d2는 ? " + (d1==d2)); System.out.println("d1 == d3는 ? " + (d1==d3)); System.out.println("d2 == d3는 ? " + (d2==d3)); System.out.println("d1.equals(d3)는 ? " + d1.equals(d3)); System.out.println("d1.compareTo(d3)는 ? " + d1.compareTo(d3)); System.out.println("d1.compareTo(d2)는 ? " + d1.compareTo(d2)); switch(d1){ case EAST: System.out.println("방향은 EAST입니다."); break; case SOUTH: System.out.println("방향은 SOUTH입니다."); break; case WEST: System.out.println("방향은 WEST입니다."); break; default: System.out.println("방향은 NORTH입니다."); break; } } } > d1 = EAST > d2 = WEST > d3 = EAST > d1 == d2는 ? false > d1 == d3는 ? true > d2 == d3는 ? false > d1.equals(d3)는 ? true > d1.compareTo(d3)는 ? 0 // 왼쪽과 오른쪽이 같아서 0 > d1.compareTo(d2)는 ? -2 // 오른쪽이 더 크기 때문에 0 - 2 = -2 > 방향은 EAST입니다.
열거형에 멤버 추가하기
불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적음
enum Direction{ EAST(0), 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); // error, 열거형의 생성자는 외부에서 호출 불가
1. 예제 enum Direction{ EAST(1,">"), SOUTH(2,"V"), WEST(3,"<"), NORTH(4,"^"); private static final Direction[] DIR_ARR = Direction.values(); private final int value; private final String symbol; Direction(int value, String symbol){ this.value = value; this.symbol = symbol; } public int getValue(){return value;} public String getSymbol(){return symbol;} public static Direction of(int dir){ if (dir < 1 || dir > 4 ){ throw new IllegalArgumentException("Invalid value : " + dir); } return DIR_ARR[dir - 1]; } public Direction rotate(int num){ num = num % 4; if (num < 0) num += 4; return DIR_ARR[(value-1+num) % 4]; } public String toString(){ return name() + getSymbol(); } } class Ex{ public static void main(String[] args){ for(Direction d : Direction.values()){ System.out.printf("%s = %d%n", d.name(), d.getValue()); } Direction d1 = Direction.EAST; Direction d2 = Direction.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(Direction.EAST.rotate(1)); System.out.println(Direction.EAST.rotate(2)); System.out.println(Direction.EAST.rotate(-1)); System.out.println(Direction.EAST.rotate(-2)); } } > EAST = 1 > SOUTH = 2 > WEST = 3 > NORTH = 4 > d1=EAST, 1 > d2=EAST, 1 > SOUTHV > WEST< > NORTH^ > WEST
애노테이션(Annotation)
주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공
1. 사용 예시 @Test // 테스트의 대상이라는 것을 Junit이라는 테스트 프로그램에 정보를 알려줌(다른 설정이 필요 없이) public void method(){ ... }
표준 애노테이션
JAVA에서 제공하는 애노테이션
애노테이션 설명 @Override 컴파일러에게 오버라이딩하는 메서드라는 것을 알림 @Deprecated 앞으로 사용하지 않는 것을 권장하는 것을 알림 @SuppressWarnings 컴파일러의 특정 경고메시지가 나타나지 않게 해줌 @SafeVarargs 제네릭 타입의 가변인자를 사용(JDK1.7) @FunctionallInterface 함수형 인터페이스라는 것을 알림(JDK1.8) @Native native메서드에서 참조되는 상수 앞에 붙임(JDK1.8) @Target* 애노테이션이 적용가능한 대상을 지정하는데 사용 @Documented* 애노테이션 정보가 javadoc으로 작성된 문서에 포함되게 함 @Inherited* 애노테이션이 자손클래스에 상속됨 @Retention* 애노테이션이 유지되는 범위를 지정하는데 사용 @Repeatable* 애노테이션을 반복해서 적용할 수 있게 함(JDK1.8)
*이 붙은 애노테이션은 메타애노테이션 - 애노테이션을 만들 때 사용
@Override
오버라이딩을 올바르게 했는지, 컴파일러가 체크하게 함
오버라이딩을 할 때, 메서드의 이름을 잘못 적는 것을 막아줌
class Parent{ void parentMethod(){} } class Child extends Parent{ void parentethod(){} // 이름을 잘못 적음 ( 오버라이딩이 아님 ), perantMethod가 호출되는 문제가 발생 } ----------------------------------- class Child extends Parent{ @Override void parentmethod(){} } > MyClass.java:11: error: method does not override or implement a method from a supertype @Override
@Deprecated
앞으로 사용하지 않는 것을 권장하는 필드나 메서드에 사용
@Deprecated가 붙은 코드를 컴파일하면 경고 메시지가 나옴
1. Date클래스에서 getDate() @Deprecated public int getDate(){ return nomalize().getDayOfMonth(); } 사용할 때 발생하는 경고메시지 > Note: MyClass.java uses or overrides a deprecated API. > Note: Recompile with -Xlint:deprecation for details.
@FunctionalInterface
함수형 인터페이스에 붙이면 컴파일러가 올바르게 작성했는지 체크
함수형 인터페이스에는 하나의 추상메서드만 가져야 한다는 제약이 있음
@FunctionalInterface public interface Runnable{ public abstract void run(); // 추상메서드 }
@SuppressWarnings
컴파일러의 경고메시지가 나타나지 않게 억제
괄호()안에 억제하고자하는 경고의 종류를 문자열로 지정
둘 이상을 억제하고자 한다면, @SuppressWarningss({"1","2","3",..."N"})
-Xlint옵션으로 컴파일하면 경고메시지를 확인가능
@SuppressWarnings("unchecked") // 제네릭스와 관련된 경고 억제 ArrayList lst = new ArrayList(); // 제네릭 타입을 지정하지 않음 lst.add(obj); // 여기서 경고 -------------------------------- 2. 예제 class Parent{ void parentMethod(){} } class Child extends Parent{ @Deprecated void parentMm(){} } public class MyClass { @SuppressWarnings("deprecation") public static void main(String args[]) { Child c = new Child(); c.parentMm(); } }
메타 애노테이션(Meta Annotation)
메타 애노테이션은 애노테이션을 만들 때 사용하는 애노테이션
java.lang.annotation패키지에 포함
Meta Annotation 설명 @Target* 애노테이션이 적용가능한 대상을 지정하는데 사용 @Documented* 애노테이션 정보가 javadoc으로 작성된 문서에 포함되게 함 @Inherited* 애노테이션이 자손클래스에 상속됨 @Retention* 애노테이션이 유지되는 범위를 지정하는데 사용 @Repeatable* 애노테이션을 반복해서 적용할 수 있게 함(JDK1.8)
@Target
애노테이션을 정의할 때, 적용대상 지정할 때 사용
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings{ String[] value; ... }
대상타입 의미 ANNOTATION_TYPE 애노테이션 CONSTRUCTOR 생성자 FIELD 필드(멤버변수, ENUM상수) LOCAL_VARIABLE 지역변수 METHOD 메서드 PACKAGE 패키지 PARAMETER 파라미터, 매개변수 TYPE 타입(클래스, 인터페이스, Enum) TYPE_PARAMETER 타입 매개변수(JDK1.8) TYPE_USE 타입이 사용되는 모든 곳(JDK1.8)
@Target({FIELD, TYPE, TYPE_USE}) public @interface MyAnnotation{} @MyAnnotation // TYPE class MyClass{ @MyAnnotation int i; // FIELD @MyAnnotation Myclass mc; // TYPE_USE }
@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를 사용
@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface FunctionalInterface{} ----------------------------------- @Inherited @interface SuperAnno{} @SuperAnno class Parent{} class Child extends Parent{} // Child에 애노테이션이 붙은 것으로 인식, 조상의 애노테이션이 자손에게 상속
@Repeatable
반복해서 붙일 수 있는 애노테이션을 정의할 때 사용
@Repeatable이 붙은 애노테이션은 반복해서 붙일 수 있음
@Repeatable인 @ToDo를 하나로 묶는 컨테이너 애노테이션도 정의해야 함
@Repeatable(ToDos.class) // ToDo 애노테이션을 여러 번 반복해서 사용할 수 있음 @interface ToDo{ String value; } ----------------------------------- @ToDo("delete test codes."); @ToDo("override inherited methods"); class MyClass{ .... } ----------------------------------- @interface ToDos{ // 여러 개의 ToDo애노테이션을 담을 컨테이너 애노테이션 ToDos ToDo[] value(); // ToDo애노테이션을 배열형태로 선언, 이름이 반드시 value이어야 함 }
애노테이션 타입 정의하기
애노테이션을 직접 만들어서 사용할 수 있음
애노테이션의 메서드는 추상 메서드이며, 애노테이션을 적용할 때 지정(순서 x)
1. 애노테이션의 기본 구조 @interface 애노테이션이름 { 타입 요소이름(); // 애노테이션 요소를 선언 ... } ---------------------------- 2. 애노테이션 타입의 예시 @interface DateTime{ String yymmdd(); // 날짜 String hhmmss(); // 시간 } ---------------------------- 3. 예제 @interface TestInfo{ int count(); String testedBy(); String[] testTools(); TestType testType(); // enum TestType { FIRST, FINAL } DateTime testDate(); // 자신이 아닌 다른 애노테이션(@DateTime)을 포함할 수 있음 } @TesetInfo( count=3, testedBy="Kim", testTools={"JUnit", "AutoTester"}, testType=TestType.FIRSE, testDate=@DateTime(yymmdd="211223",hhmmss="204310") ) public class NewClass{ ... }
애노테이션의 요소
적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)
요소가 하나이고, 이름이 value일 때, 요소의 이름 생략가능
요소의 타입이 배열일 때, 괄호{}를 사용해야 함
1. default 사용 @inteface TestInfo{ int count() default 1; } @TestInfo // count 기본 값이 1 public class NewClass{ ... } ----------------------------------- 2. 이름 생략 @interface TestInfo{ String value(); } @TestInfo("Kim") // value = "Kim"과 같은 의미 class NewClass{ ... } ---------------------------------- 3. 배열일 경우 @interface TestInfo{ String[] testTools; } @TestInfo(testTools ={"JUnit", "AutoTester"}) @TestInfo(testTools = {"JUnit"}) @TestInfo(testTools = {}) // 값이 없어도 {}
모든 애노테이션의 조상 - java.lang.annotation.Annotation
Annotation은 모든 애노테이션의 조상이지만 상속은 불가
Annotation은 사실 인터페이스
@interface TestInfo extends Annotation{ // 허용되지 않는 표현 @interface TestInfo{ int count(); String testedBy(); ... } ---------------------------------------- 1. Annotation은 interface package java.lang.annotation; public interface Annotation{ boolean equals(Object obj); // 추상메서드, 구현 x 하지만 사용가능 int hashCode(); String toString(); Class annotationType(); // 애노테이션의 타입을 반환 }
마커 애노테이션
요소가 하나도 정의되지 않은 에노테이션
1. 요소가 하나도 정의되지 않은 annotation @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override{} // 마커 애노테이션, 정의된 요소가 하나도 없음 @Test // 이 메서드가 테스트 대상임을 프로그램에 알려줌 public void method(){ ... } @Deprecated public int getDate(){ return nomalize().getDayOfMonth(); }
애노테이션 요소의 규칙
애노테이션 요소를 선언할 때 아래의 규칙을 반드시 지켜야 함 요소의 타입은 기본형, String, enum, 애노테이션, Class(9장, 설계도객체) 만 허용 괄호()안에 매개변수를 선언할 수 없음 예외를 선언할 수 없음 요소를 타입 매개변수로 정의할 수 없음 ex) 와 같은 것
@interface AnnoTest{ int id = 100; // 상수 OK String major(int i, int j); // 매개변수 선언 불가 String minor() throw Exception; // 예외 불가 ArrayList list(); // 타입 매개변수 불가 }
import java.lang.annotation.*; @Deprecated @SuppressWarnings("1111") @TestInfo(testedBy="aaa", testDate=@DateTime(yymmdd="211223",hhmmss="204310")) class Ex{ public static void main(String[] args){ Class cls = Ex.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(); 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(); } > anno.testedBy() = aaa > anno.testDate().yymmdd = 211223 > anno.testDate().hhmmss = 204310 > testTools = JUnit > > @java.lang.Deprecated(forRemoval=false, since="") > @TestInfo(count=1, testType=FIRST, testTools={"JUnit"}, testedBy="aaa", testDate=@DateTime(yymmdd="211223", hhmmss="204310"))
자바의 정석 기초편 챕터 12에서는 제네릭스, 열거형, 애노테이션에 대한 정의 및 특성과 종류에 대해 배웠습니다.
from http://orangebluestyle.tistory.com/44 by ccl(A) rewrite - 2021-12-31 23:27:50