-
🌑 지네릭스 (Generics)
-
✔️ Generics
-
✔️ Generic class
-
✔️ Generic class의 객체 생성 & 사용
-
✔️ 제한된 Generic class
-
✔️ 와일드 카드 ( WildCard )
-
✔️ Generic 메서드
-
✔️ Generic 타입의 형변환
-
✔️ Generic 타입의 제거
-
🌒 열거형 (enums)
-
✔️ 열거형 (enum) 이란?
-
✔️ enum의 정의 & 사용
-
✔️ enum에 멤버 추가하기
-
✔️ enum에 추상 (abstract) 메서드 추가하기
-
✔️ enum의 이해
-
🌓 애너테이션 (annotation)
-
✔️ 애너테이션이란 ?
-
✔️ 표준 애너테이션
-
✔️ 메타 애너테이션 (Meta Annotation)
-
✔️ 애너테이션 타입 정의
-
✔️ 애너테이션 요소
-
✔️ java.lang.anotation.Annotation
-
✔️ 마커 애너테이션 (Marker Annotation)
-
✔️ class 객체
-
▼ Study📋
🌑 지네릭스 (Generics)
✔️ Generics
🔹 Generics 란 ?
- 다양한 타입의 객체들을 다루는 메서드나 Collection class에
컴파일 시의 타입체크(compile-time type check)를 해주는 기능
➠ 객체의 타입 안정성을 제공 - 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다
➠ Collection class는 보통 한 종류의 객체를 담는 경우가 많기 때문에
매번 타입체크를 하고 형변환하는 것은 비효율적 !
✔️ Generic class
🔹 Generic class의 선언
class Box<T> { // Generic 타입 T를 선언
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
🔻 ' T ' ➠ ' 타입 변수 (type variable) '
🔻 ' E ' ➠ ' Element (요소) ' ( ex. ArrayList<E> )
Box<String> b = new Box<String>();
b.setItem(new Object()); // Error!
b.setItem("ABC"); // OK
String item = b.getTime(); // 형변환 필요 X
🔻 ' T ' 대신 실제 타입 (' String ') 지정
- Generics가 도입되기 이전의 코드와 호환성을 유지하기 위해 Generics를 사용하지 않은 코드를 허용하는 것 !
➠ Generics class를 사용할 때는 반드시 타입을 지정해주자
Box b = new Box();
// Box<Object> b = new Box<Object>(); // 타입 지정시 경고 발생 X
b.setItem(new Object()); // 경고 발생
b.setItem("ABC"); // 경고 발생
🔹 Generics의 용어
class Box<T> {}
- Box<T> ➠ Generic class. ' T의 Box ' 또는 ' T Box ' 라고 읽는다
- T ➠ 타입 변수 또는 타입 매개변수. (T는 타입 문자)
- Box ➠ 원시 타입 (raw type)
Box<String> b = new Box<String>();
- ' Generic 타입 호출 ' ➠ 타입 매개변수에 타입을 지정하는 것
- 지정된 타입 ' String ' ➠ ' 매개변수화된 타입 (parameterized type) '
➠ ' 대입된 타입 '
🔹 Generics의 제한
- Generic class의 객체를 생성할 때, 객체별로 다른 타입을 지정하자
Box<Apple> appleBox = new Box<Apple>();
Box<Grape> grapeBox = new Box<Grape>();
- static 멤버는 intance 변수를 참조할 수 없듯이 타입 변수 T를 사용 불가능 !
➠ ' T ' 는 instance 변수로 간주
➠ static 멤버는 대입된 타입의 종류에 관계없이 동일한 것이어야 한다
class Box<T> {
static T item; // Error!
static int compare(T t1, T t2) { ... } // Error
}
- Generic 배열 타입의 참조변수는 선언 가능
Generic 타입의 배열은 생성 불가능 !
class Box<T> {
T[] itemArr; // OK
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // Error
...
return tmpArr;
}
...
}
🔻 new 연산자나 instanceof 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 하는데,
Generic 타입의 배열을 생성하게 되면 알 수가 없어 T를 피연산자로 사용할 수 없다 !
🔻 Generic 타입의 배열을 생성해야만 한다면 new 연산자 대신
' newInstance() ' 와 같이 동적으로 객체를 생성하는 메서드를 이용하거나
Object 배열을 생성해서 복사한 다음에 ' T[ ] '로 형변환하는 방법을 사용
✔️ Generic class의 객체 생성 & 사용
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
...
}
🔹 Generic class의 객체를 생성하고 사용할 때 알아둬야 할 점
- 참조변수와 생성자에 대입된 타입이 일치해야 한다
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<Grape>(); // Error
- Apple class가 Fruit class의 자손이라 가정
➠ 대입된 타입이 다른 것은 허용 X
➠ 두 Generic class의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다
Box<Fruit> appleBox = new Box<Apple>(); // Error
Box<Apple> appleBox = new FruitBox<Apple>(); // OK. 다형성
- 타입 생략 가능 ( JDK1.7 ~ )
Box<Apple> appleBox = new Box<>();
- 대입된 타입과 다른 타입의 객체는 추가 불가능 !
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple()); // OK
appleBox.add(new Grape()); // Error!
- 단, super class 타입으로 생성된 객체의 메서드의 매개변수로는 sub class도 될 수 있다
Box<Fruit> fruitBox= new Box<Fruit>();
fruitBox.add(new Fruit()); // OK
fruitBox.add(new Apple()); // OK
✔️ 제한된 Generic class
FruitBox<Toy> fruitBox = new FruitBox<Toy>();
fruitBox.add(new Toy)); // OK
🔹 특정 sub class만 타입으로 지정 가능
- Generic 타입에 ' extends ' 사용
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
...
}
- 매개변수의 타입 T는 매개변수화된 타입(' Fruit ')의 자손 타입도 될 수 있다
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> fruitBox = new FruitBox<Toy>(); // Error!
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK
- class가 아니라 interface를 구현해야 한다는 제약이 필요할 때도 ' implements ' 가 아닌 ' extends ' 사용
class FruitBox<T extends Fruit & Eatable> { ... }
🔻 Fruit class의 sub class이면서 Eatable interface도 구현한 class만
타입 매개변수 ' T ' 에 대입되게 한다면 ' & ' 기호로 연결
✔️ 와일드 카드 ( WildCard )
🔹 와일드 카드가 필요한 이유
- static 메서드에는 타입 매개변수 T를 매개변수에 사용 X
➠ Generics를 적용하지 않던가, 타입 매개변수 대신 특정 타입('<Fruit>')을 지정해줘야 한다
➠ ' FruitBox<Apple> ' 타입의 객체는 maketJuice() 메서드의 매개변수가 될 수 없다
class Jucier {
static Juice makeJuice(FruitBox<Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
- 여러 가지 타입의 매개변수를 갖는 makeJuice()를 만들어야 하는데
Generic 타입이 다른 것만으로는 Overloading 성립 X
FruitBox<Apple> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.makeJuice(new FruitBox<Fruit>()));
System.out.println(Juicer.makeJuice(appleBox)); // Error!
System.out.println(Juicer.makeJuice(grapeBox)); // Error!
🔹 와일드 카드 ' ? '
- <? extends T> ➠ T와 그 sub class들만 가능
- <? super T> ➠ T와 그 super class들만 가능
- <?> ➠ 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
- ' & ' 사용 불가능
class Jucier {
static Juice makeJuice(FruitBox<? extends Object> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " "; // Error! Fruit이 아닐 수 있다
return new Juice(tmp);
}
}
🔻 ' box ' 의 요소가 Fruit class의 자손이라는 보장이 X
➠ ' box ' 에 저장된 요소를 Fruit 타입의 참조변수로 못받는다 !
class FruitBox<T extends Fruit> extends Box<T> {}
➠ 그러나 Generic class ' FruitBox ' 를 제한했기 때문에 문제없이 Compile 된다 !
System.out.println(Juicer.makeJuice(new FruitBox<Apple>())); // OK
🔻 모든 종류의 FruitBox가 매개변수로 가능해진다 !
- Comparator<? super T>
➠ Comparator의 타입 매개변수로 T class와 그 super class가 가능하다는 의미
static void sort(List<T> list, Comparator<T> c)
🔻 새로운 sub class가 생길 때마다 그에 맞는 Comparator<T>를 구현한 class를 반복해서 생성해야 한다
➠ 코드의 중복 · 비효율적인 작업
static <T> void sort(List<T> list, Comparator<? super T> c)
🔻 타입 매개변수에 하한 제한의 와일드 카드를 적용하면 문제 해결 !
class FruitComp implements Comparator<Fruit> {
public int compare(Fruit t1, Fruit t2) {
return t1.weight - t2.weight;
}
}
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
🔻 Apple class와 Grape class의 super class인 Fruit class를 타입 매개변수로 하는 Comparator를
구현한 ' FruitComp ' class만 만들면, List<Apple>과 List<Grape>를 모두 정렬 가능
✔️ Generic 메서드
🔹 Generic 메서드 선언
- 메서드의 선언부에 Generic 타입이 선언된 메서드 ➠ Generic 메서드
ex) Collections.sort()
- Generic 메서드에 선언된 타입 매개변수 T ≠ Generic class에 선언된 타입 매개변수T
static <T> void sort(List<T> list, Comparator<? super T> c)
🔻 static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드엔 Generic 타입 선언 가능 !
- Generic 메서드로 변환
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
⭣ ⭣
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { ... }
🔻 이렇게 선언된 Generic 메서드를 호출할 때는 아래와 같이 타입 변수에 타입을 대입해야 한다
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> fruitBox = new FruitBox<Apple>();
...
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
// System.out.println(<Fruit>makeJuice(fruitBox)); // Error!
System.out.println(Juicer.<Apple>makeJuice(appleBox));
// System.out.println(Juicer.makeJuice(appleBox));
🔻 대입된 타입은 생략 가능
➠ 같은 class 내에 있는 멤버들끼리는 ' this. ' 이나 ' class 이름. ' 을 생략하고
메서드 이름만으로 호출 가능
➠ 단, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 class 이름은 생략 불가능
- Generic 메서드는 매개변수의 타입이 복잡할 때 유용하다 !
public static void printAll(ArrayList<? extends Product> list,
ArrayList<? extends Product> list2) {
for(Unit u : list) {
System.out.println(u);
}
}
⭣ ⭣
public static <? extends Product> void printAll(ArrayList<T> list,
ArrayList<T> list2) {
for(Unit u : list) {
System.out.println(u);
}
}
- 복잡하게 선언된 Generic 메서드
public static <T extends Comparable<? super T>> void sort(List<T> list)
- <T extends Comparable> ➠ ' T ' 는 Comparable을 구현한 class이어야 한다
- Comparable<? super T>
➠ Comparable은 ' T ' 또는 그 super class의 타입을 비교하는 interface이어야 한다 - List<T> ➠ 타입 T를 요소로 하는 List를 매개변수로 허용한다
✔️ Generic 타입의 형변환
🔹 Generic 타입 ⇄ 원시 타입 (raw type)
- Generic 타입과 Non-Generic 타입간의 형변환은 항상 가능하지만, 경고만 발생
Box box = null;
Box<Object> objBox = null;
box = (Box)objBox;
objBox = (Box<Object>)box;
🔹 대입된 타입 (매개변수화된 타입) ⇄ 다른 Generic 타입
- 대입된 타입과 다른 Generic 타입 간에 형변환은 불가능
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // Error!
strBox = (Box<String>)objBox; // Error!
// Box<Object> objBox = (Box<Object>)new Box<String>();
Box<Object> objBox = new Box<String>(); // Error!
🔻 ' Box<Object> ' ⇆ ' Box<String> '
➠ 대입된 타입이 Object이더라도 형변환은 불가능 !
- 가능한 경우
// Box<? extends Object> xBox = (Box<Object>)new Box<String>();
Box<? extends Object> xBox = new Box<String>();
🔻 ' Box<String> ' ➙ ' Box<? extends Object> '
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK
FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
🔻 매개변수에 다형성 적용 ( ' FruitBox<Apple> ' ➙ ' FruitBox<? extends Fruit> ' )
➠ 반대로의 형변환도 가능하지만, 확인되지 않은 형변환이라는 경고 발생
FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (Fruit<Apple>)box; // 경고
🔹 WildCard (' ? ') 가 사용된 Generic 타입
Optional<?> EMPTY = new Optional<>();
// ➙ Optional<?> EMPTY = new Optional<Object>();
// ➙ Optional<? extends Object> EMPTY = new Optional<Object>();
Optional<?> EMPTY = new Optional<?>(); // Error!
🔻 <?> ➠ <? extends Object>
🔻 <> 안에 생략된 타입은 ' ? ' 가 아닌 ' Object ' 이다
- Optional<Object>가 아닌 Optional<?>로 한 이유
➠ Optional<T>로 형변환이가능하기 때문이다 !
Optional<?> wopt = new Optional<Object>();
Optional<Object> oopt = new Optional<Object>();
Optional<String> sopt = (Optional<String>)wopt; // OK
Optional<String> sopt = (Optional<String>)oopt; // Error
- ' Optional<Object> ' ➙ ' Optional<String> ' 같은 직접적인 형변환은 불가능
➠ WildCard가 포함된 Generic 타입으로 형변환하면 가능 !
' Optional<Object> ' ➙ ' Optional<?> ' ➙ ' Optional<T> ' ( OK, 미확정 타입으로의 형변환 경고 발생 ) - WildCard가 사용된 Generic 타입끼리의 형변환
➠ WildCard는 확정된 타입이 아니므로 Compiler가 미확정 타입으로 형변환하는 것이라고 경고 !
✔️ Generic 타입의 제거
🔹 Compiler의 Generic 타입 제거 (이전 소스 코드와의 호환성 때문에)
- Generic 타입의 경계(bound) 제거
' <T extends Fruit> ' ➠ T는 Fruit으로 치환 / ' <T> ' ➠ T는 Object로 치환 - Generic 타입을 제거한 후에 타입이 일치하지 않으면, 형변환 추가
T get(int i) { return list.get(i); }
⭣ ⭣
Fruit get(int i) { return (Fruit)list.get(i); }
🔻 List의 get() 메서드는 Object타입을 반환하므로 형변환이 필요하다 !
🌒 열거형 (enums)
✔️ 열거형 (enum) 이란?
🔹 열거형 (enum)
- 서로 관련된 상수를 편리하게 선언하기 위해 추가된 것 (JDK1.5 ~)
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, TREEE, FOUR }
final Kind kind;
final Value num;
}
- C언어의 enum과 달리 enum이 갖는 값뿐만 아니라 타입도 관리
if(Card.CLOVER == Card.TWO) // true지만 false이어야 의미상 맞다
if(Card.Kind.CLOVER == Card.Value.Two) // Compile Error! : 타입이 다르다
- 상수의 값이 바뀌면, 해당 상수를 참조하는 모든 소스를 다시 Compile 해야 한다
➠ 하지만 enum 상수를 사용하면, 기존의 소스를 다시 Compile 할 필요 X
✔️ enum의 정의 & 사용
🔹 enum을 정의하는 방법
- enum 이름은 class처럼 첫 글자만 대문자로 하고 나머지는 소문자로 작성
- enum 상수의 이름은 모두 대문자로 작성 (여러 단어를 연결할 경우 ' _ ' 로 연결)
// 동서남북 4방향을 상수로 정의하는 enum Direction
enum Direction { EAST, SOUTH, WEST, NORTH }
🔹 enum에 정의된 상수를 사용하는 방법
- ' enum이름 . 상수명 ' ➠ class의 static 변수를 참조하는 것과 동일
class Unit {
int x, y; // Unit의 위치
Direction dir; // enum을 instance 변수로 선언
void init() {
dir = Direction.EAST; // Unit의 방향을 EAST로 초기화
}
}
- switch문의 조건식에도 enum 사용 가능
➠ case문에 enum의 이름은 적지 않고 상수의 이름만 적어야 한다 !
🔹 enum 상수간의 비교
- ' == ' 사용 가능 ➠ equals() 메서드를 사용하지 않아도 비교 가능한 만큼 빠른 성능을 제공 !
- ' < ' , ' > ' 와 같은 비교연산자는 사용 불가능 ➠ 대신 compareTo() 메서드 사용
🔹 모든 enum의 super class - java.lang.Enum
- Compiler가 자동으로 추가해주는 메서드들
- values() ➠ enum의 모든 상수를 배열에 담아 반환
- ordinal() ➠ enum 상수가 정의된 순서(0부터 시작)를 정수로 반환
- valuesOf() ➠ enum 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있다
- values() ➠ enum의 모든 상수를 배열에 담아 반환
Direction d = Direction.valueOf("WEST");
System.out.println(d);
System.out.println(Direction.WEST == Direction.valueOf("WEST"));
WEST
true
- java.lang.Enum
https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html
Enum (Java Platform SE 8 )
Returns the enum constant of the specified enum type with the specified name. The name must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.) Note that for a particular enum typ
docs.oracle.com
✔️ enum에 멤버 추가하기
🔹 enum 상수에 원하는 값 지정
- Enum class에 정의된 ordinal() 메서드가 enum 상수가 정의된 순서를 반환하지만,
이 값을 enum 상수의 값으로는 사용하지 말자 - enum 상수에 원하는 값을 지정해주고 싶다면,
지정된 값을 저장할 수 있는 intance 변수와 생성자를 새로 추가해야 한다 ! - 하나의 enum 상수에 여러 값도 지정 가능 !
➠ 단, 그에 맞게 instance 변수와 생성자 등을 새로 추가해줘야 한다
enum Direction {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^"); // 끝에 ';' 추가
private final int value; // 정수를 저장할 필드(instance 변수 추가)
private final String symbol; // 기호를 저장할 필드(instance 변수 추가)
Direction(int value, String symbol) { // 생성자 추가
this.value = value;
this.symbol = symbol
}
public int getValue() { return value; }
public String getSymbol() { return symbol; }
}
🔻 enum 상수를 모두 정의한 다음에 다른 멤버를 추가해야 한다 !
🔻 enum의 instance 변수는 반드시 final이어야 하는 제약은 없지만,
' value ' 는 enum 상수의 값을 저장하기 위한 것이므로 final로 선언한 것이다
🔻 enum의 생성자는 제어자가 묵시적으로 private !
➠ 외부에서 호출해서는 안되기 때문에 !
✔️ enum에 추상 (abstract) 메서드 추가하기
🔹 enum에 정의된 추상 메서드를 각 상수가 구현하는 예제
- 사실 enum에 추상 메서드를 선언할 일은 많지 않다
// 거리에 따라 요금을 계산하는 방식이 각 운송 수단마다 다르기 때문에, 추상 메서드 필요
enum Transportation {
Bus(100) { // Anonymous class
int fare(int distance) {
return distance*BASIC_FARE;
}
},
TRAIN(150) { int fare(int distance) { return distance*BASIC_FARE; }},
SHIP(100) { int fare(int distance) { return distance*BASIC_FARE; }},
AIPLANE(300) { int fare(int distance) { return distance*BASIC_FARE; }};
abstract int fare(int distance); // 거리에 따른 요금을 계산하는 추상 메서드
protected final int BASIC_FARE; // protected로 해야 각 상수에서 접근 가능
Transportation(int basicFare) {
BASIC_FARE = basicFare;
}
public int getBasicFare() { return BASIC_FARE; }
}
🔻 추상 메서드를 정의하고, 각 상수마다 ' 익명 클래스(Anonymous class) ' 처럼
메서드를 재정의하게 해서 각 상수마다 다른 역할을 하는 메서드를 갖게 하는 원리
✔️ enum의 이해
🔹 enum의 핵심
- enum 상수를 단순히 정수로 치부하지 말고 객체지향적으로 객체화해서 관리하자 !
➠ enum 상수는 일종의 객체 !
🔹 enum의 내부적인 개념
enum Direction { EAST, SOUTH, WEST, NORTH }
- enum 상수 하나하나가 자신의 instance를 생성하여 public static final 필드로 공개한다
➠ class로 정의하면 아래와 같이 작성한 것이다
class Direction {
public static final Direction EAST = new Direction("EAST");
...
}
🔻 결국 enum 상수의 값은 객체의 주소(변하지 않는 값)이기 때문에, ' == ' 로 비교가 가능한 것이다
- 모든 enum은 추상 class ' Enum '의 자손 ! ( ' MyEnum ' ➠ Enum을 흉내내어 작성한 class )
abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {
static int id = 0; // 객체에 붙일 일련번호 (0부터 시작)
int ordinal;
String name = "";
public int ordinal() { return ordinal; }
MyEnum(String name) {
this.name = name;
ordinal = id++; // 객체를 생성할 때마다 id의 값을 증가시킨다
}
public int compareTo(T t) {
return ordinal - t.ordinal();
}
}
🔻 ' <T extends MyEnum<T>> ' 로 선언
➠ 타입 T가 MyEnum<T>의 자손이어야 한다는 것을 compiler가 확인
➠ 따라서, 타입 T에 ordinal() 메서드가 정의되어 있는 것이 분명해진다
➠ 형변환 없이도 ' t.ordinal() ' 에서 에러가 발생하지 않는 것이다 !
🔻 만약 ' <T> ' 로 선언되었다면, 타입 T에 ordinal() 메서드가 정의되어 있는지 확인을 못해 에러 발생 !
- enum에 추상 메서드를 추가하면, 각 enum 상수 모두 추상 메서드를 구현해줘야 하는 이유
enum Direction {
EAST("EAST") { // 익명 class
Point move(Point p) { ... }
};
SOUTH("SOUTH") {
Point move(Point p) { ... }
};
...
abstract Point move(Point p);
}
⭣ ⭣
abstract class Direction extends MyEnum {
public static final Direction EAST = new Direction("EAST") { // Anonymous class
Point move(Point p) { ... }
};
public static final Direction SOUTH = new Direction("SOUTH") {
Point move(Point p) { ... }
};
...
abstract Point move(Point p);
}
🔻 추상 메서드를 새로 추가하면, class 앞에도 ' abstract ' 를 붙여줘야 하고,
각 static 상수들도 추상 메서드를 구현해줘야 하는 것과 같은 이유이다
🌓 애너테이션 (annotation)
✔️ 애너테이션이란 ?
🔹 애너테이션 (annotation)
- 주석, 주해, 메모를 의미
- 주석처럼 프로그램 자체에는 아무런 영향 X
- 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것 !
ex) 특정 메서드만 테스트하기를 원한다면 ' @Test ' 라는 애너테이션을 해당 메서드 앞에 붙이면 된다
➠ 테스트 프로그램을 제외한 다른 프로그램에게는 아무 의미 없는 정보이다
✔️ 표준 애너테이션
@Override
- 메서드 앞에만 붙일 수 있는 애너테이션
➠ super class의 메서드를 Overriding 하는 것이라는 걸 Compiler에게 알려주는 기능 - Overriding 할 super class의 메서드 이름을 잘못 적은 경우 에러메시지를 출력해준다
@Deprecated
- 기존 코드와의 호환성 문제로 함부로 삭제할 수는 없으나,
다른 것으로 대체되었으니 더 이상 사용하지 않은 것을 권하는 의미 (강제성 X)
➠ java 버전을 업그레이드 하는 경우에 주로 사용 - ' -Xlint:deprecation ' 옵션을 붙여 Compile 하면 오류에 대한 내용을 더 자세하게 보여준다
@FunctionalInterface
- ' 함수형 인터페이스(functional interface) ' 를 올바르게 선언했는지 확인하고,
잘못된 경우 에러를 발생시키는 기능 - 함수형 인터페이스(functional interface)
➠ 추상 메서드가 하나뿐이어야 한다는 제약이 있다.
@SuppressWarnings
- Compiler가 보여주는 경고메시지가 나타나지 않게 억제해주는 기능
➠ 묵인해야하는 경고가 발생하는 대상에 붙여 중요한 경고를 놓치지 않기 위해 사용한다 - 주로 묵인하게 되는 경고 메시지 종류
➠ " deprecation " ~ '@Deprecated' 가 붙은 대상을 사용해서 발생하는 경고
➠ " unchecked " ~ Generics로 타입을 지정하지 않았을 때 발생하는 경고
➠ " rawtypes " ~ Generics를 사용하지 않아서 발생하는 경고
➠ " varargs " ~ 가변인자의 타입이 Generic 타입일 때 발생하는 경고
@SuppressWarnings("unchecked") // Generics에 관련된 경고 억제
ArrayList list = new ArrayList(); // Generic 타입을 지정하지 않았다
list.add(obj); // 여기서 경고 억제
@SuppressWarnings("deprecation") // deprecation 관련 경고를 억제
public static void main(String args[]) {
...
@SuppressWarnings("unchecked") // 지네릭스 관련 경고를 억제
ArrayList<Integer> list = new ArrayList();
}
🔻 위처럼 두 종류 이상의 경고를 main 같은 메서드에 붙여서 한 번에 억제한 경우엔
나중에 추가된 코드에서 발생할 수도 있는 경고까지 억제될 수 있으므로 바람직하지 않다
➠ 해당 대상에만 애너테이션을 붙이자 !
- ' -Xlint ' 옵션으로 Compile 해서 경고의 내용 중 대괄호 [ ] 안에 있는 것이 경고 메시지의 종류이다
➠ 새로운 버전에서 경고가 발생할 수 있기에 새로 추가된 경고 메시지를 억제하려면
경고 메시지의 종류를 알아야 한다
@SafeVarargs
- static이나 final이 붙은 메서드와 생성자에만 붙일 수 있다
➠ 즉, Override 될 수 있는 메서드에는 사용 불가능 ! - refiable 타입 ➙ Compile 후에도 제거되지 않는 타입
non-refiable 타입 ➙ Compile 후에 제거되는 타입
➠ 대부분의 Generics 타입처럼 Compile 후에 제거되는 타입들이 있다 - 메서드에 선언된 가변인자(varargs)의 타입이 non-reifiable 타입일 경우,
해당 메서드를 선언 · 호출하는 부분에서 "unchecked" 경고가 발생
➠ 메서드를 선언하는 부분에만 ' @SafeVarargs '를 붙이면 호출하는 부분에서 발생하는 경고도 억제
@SafeVarargs // 'unchecked' 경고 억제
@SuppressWarnings("varargs") // 'varargs'경고 억제
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); // 경고 발생
}
🔻 메서드에 선언된 ' T ' ➙ ' Object[ ] ' (Compile 과정)
➠ ' Object[ ] ' 에는 모든 타입의 객체가 들어올 수 있어 위험하다고 판단해 경고가 발생하는 것
➠ 하지만, asList() 메서드가 호출되는 부분은 Compiler가 체크해서
타입 T가 아닌 다른 타입이 들어가지 못하게 하므로 문제가 없는 코드이다 !
➠ 이때 메서드 앞에 ' @SafeVarargs ' 를 붙여서
해당 메서드의 가변인자는 타입 안정성이 있다고 전달해주는 것이다
🔻 ' @SafeVarargs ' 는 "varargs" 경고는 억제하지 못하기 때문에, ' @SuppressWarning ' 를 같이 붙인다
➠ 가능하면 이 두 애너테이션은 항상 같이 사용하자
✔️ 메타 애너테이션 (Meta Annotation)
🔹 Meta Annotation 이란 ?
- 애너테이션을 위한 애너테이션, 즉 애너테이션에 붙이는 애너테이션이다 !
➠ 애너테이션의 적용대상(target)이나 유지기간(retention) 등을 지정하는데 사용
@Target
- 애너테이션이 적용가능한 대상을 지정하는데 사용
import static java.long.annotation.ElementType.*;
@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation { }
⭣ ⭣ ( ' @Target ' 으로 지정해줘서 아래처럼 애너테이션을 적용 가능한 것이다 )
@MyAnnotation // TYPE
class MyClass {
@MyAnnotation // FIELD
int i;
@MyAnnotation // TYPE_USE
MyClass mc
}
- ' @Target ' 으로 지정할 수 있는 애너테이션 적용대상의 종류
대상 타입 | 의미 |
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(class, interface, enum) |
TYPE_PARAMETER | 타입 매개변수 (JDK1.8~) |
TYPE_USE | 타입이 사용되는 모든 곳 (JDK1.8~) |
🔻 ' FIELD ' 는 기본형에, ' TYPE_USE ' 는 참조형에 사용된다
@Retetion
- 애너테이션이 유지(retention)되는 기간을 지정하는데 사용
- 애너테이션의 유지 정책(retention policy)의 종류
유지 정책 | 의미 |
SOURCE | src 파일에만 존재. class 파일에는 존재 X |
CLASS | class 파일에 존재. 실행 시에 사용 불가. 기본값 |
RUNTIME | class 파일에 존재. 런타임(실행 시)에 사용 가능 (by reflection) |
🔻 리플랙션 (reflection) ?
- 클래스 객체를 통해 필드/메서드/생성자를 접근 제어자와
상관 없이 사용할 수 있도록 해주는 자바 API - 프레임워크 또는 라이브러리 개발 시 사용된다
🔻 ' SOURCE ' ➠ Compiler를 직접 작성할 것이 아니면 필요 X
🔻 ' RUNTIME '
➠ 실행 시에 ' 리플렉션(reflection) ' 을 통해 class 파일에 저장된
애너테이션의 정보를 읽어서 처리 가능하게 한다
ex) ' @FunctionInterface ' 은 Compiler가 체크해주는 애너테이션이지만,
실행 시에도 사용되므로 유지 정책이 ' RUNTIME ' 이다
➠ 지역 변수에 붙은 애너테이션은 Compiler만 인식할 수 있어,
' RUNTIME ' 인 애너테이션을 지역변수에 붙여도 실행 시에는 인식 불가능
🔻 ' CLASS '
➠ Compiler가 애너테이션의 정보를 class 파일에 저장할 수 있게 한다
➠ 하지만, class 파일이 JVM에 로딩될 때는 애너테이션의 정보가 무시되어
실행 시에 애너테이션에 대한 정보를 얻을 수 없다
➠ 이것이 ' CLASS ' 가 잘 사용되지 않는 이유
@Documented
- 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다
- ' @Override ' 와 ' @SuppressWarnings ' 를 제외한 표준 애너테이션에는 이 메타 애너테이션이 붙어 있다
@Inherited
- 애너테이션이 sub class에 상속되도록 한다
➠ ' @Inherited '이 붙은 애너테이션을 super class에 붙이면,
sub class도 이 애너테이션이 붙은 것과 같이 인식
@Repeatable
- 보통은 하나의 대상에 한 종류의 애너테이션을 붙이는데,
이 메타 애너테이션이 붙은 애너테이션은 여러 번 붙일 수 있다
@ToDo("delete test codes")
@ToDo("override inherited method")
class MyClass {
...
}
- 같은 이름의 애너테이션이 여러 개가 하나의 대상에 적용될 수 있어,
이 애너테이션들을 하나도 묶어서 다룰 수 있는 컨테이너 애너테이션도 추가로 정의해야 한다 !
@interface ToDos { // 여러 개의 ToDo 애너테이션을 담을 컨테이너 애너테이션 ToDos
ToDo[] value(); // ToDo 애너테이션 배열타입의 요소를 선언. 이름이 반드시 value이어야 한다!
}
@Repeatable(ToDos.class) // 괄호 안에 컨테이너 애너테이션을 지정
@interface ToDo {
String value();
}
@Native
- 네이티브 메서드(native method)에 의해 참조되는 ' 상수 필드 (constant field) ' 에 붙이는 애너테이션
➠ Native method ➙ JVM이 설치된 OS의 메서드
➠ Native 메서드를 호출할 때 실제로 호출되는 것은 OS의 메서드이다
➠ Java에 정의된 Native 메서드와 OS의 메서드를 연결해주는 작업을
수행하는 것이 ' JNI (Java Native Interface) ' ! - Object class의 메서드들은 대부분 Native method이다
✔️ 애너테이션 타입 정의
🔹 새로운 애너테이션을 정의하는 방법
- ' @ ' 기호를 붙이는 것을 제외하면 interface를 정의하는 것과 동일
@interface annotationName {
타입 요소이름(); // 애너테이션의 요소를 선언
...
}
- ' @Override ' ➠ 애너테이션
' Override ' ➠ 애너테이션의 타입
✔️ 애너테이션 요소
🔹 애너테이션의 요소
- 애너테이션 내에 선언된 메서드
➠ ' 애너테이션의 요소(element) '
➠ 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가진다
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType { FIRST, FINAL }
DateTime testDate(); // 자신이 아닌 다른 애너테이션(@DateTime) 포함 가능
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
- 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해야 한다 (순서 상관 X)
@TestInfo (
count = 3, testedBy = "Shin",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = "230717", hhmmss = "034245");
}
- 애너테이션의 각 요소는 default 값 지정 가능
➠ default 값이 여러 개인 경우(요소의 타입이 배열)엔 괄호{ } 사용
@interface TestInfo {
int count() default 1; // 기본값을 1로 지정
}
@TestInfo // @TestInfo(count = 1)과 동일
🔻 default 값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 defaut 값이 사용된다
- 애너테이션의 요소가 오직 하나뿐이고 이름이 value인 경우,
애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적는 것이 가능
@interface SuppressWarnings {
String[] value();
}
// @SuppressWarnings(value = {"deprecation", "unchecked"})
@SuppressWarnings({"deprecation", "unchecked"})
- 요소의 타입이 배열인 경우
@interface TestInfo {
String[] testTools();
}
@Test(testTools = {"JUnit", "AutoTester"}) // 값이 여러 개인 경우
@Test(testTools = "JUnit") // 값이 하나일 때는 괄호 생략 가능
@Test(testTools = {}) // 값이 없을 때는 괄호{}가 반드시 필요
🔹 애너테이션의 요소를 선언할 때 지켜야 하는 규칙
- 요소의 타입은 기본형, String, enum, Annotation, Class만 허용 O
- ()안에 매개변수를 선언 X
- 예외를 선언 X
- 요소를 타입 매개변수로 정의 X
@interface AnnoTest {
int id = 100; // OK. 상수선언. static final int id = 100;
String major(int i, int j); // Error! 매개변수 선언 불가능
String minor() throws Exception; // Error! 예외 선언 불가능
ArrayList<T> list(); // Error! 요소의 타입에 타입 매개변수 사용불가
✔️ java.lang.anotation.Annotation
🔹 Annotation
- 모든 애너테이션의 조상
➠ 하지만, 애너테이션은 상속 허용 X
➠ 명시적으로 Annotation을 조상으로 지정 불가능 - 애너테이션이 아니라 일반적인 interface로 정의되어 있다
➠ equals() · hashCode() · toString()과 같은 메서드가 Annotation interface 내에 정의되어 있어,
모든 애너테이션 객체에 대해 이러한 메서드를 호출하는 것이 가능하다
Class<AnnotationTest> cls = AnnotationTest.class;
Annotation[] annoArr = AnnotationTest.class.getAnnotations();
for(Annotation a : annoArr) {
System.out.println(a.toString());
System.out.println(a.hashCode());
System.out.println(a.equals());
System.out.println(a.annotationType());
}
🔻 AnnotationTest class에 적용된 모든 애너테이션에 대해 위와 같은 메서드들을 호출하였다
✔️ 마커 애너테이션 (Marker Annotation)
🔹 정의된 요소가 하나도 없는 애너테이션
- 값을 지정할 필요가 없는 경우, 애너테이션의 요소를 하나도 정의하지 않는다
( Serializable · Cloneable interface 같은 형태 )
✔️ class 객체
🔹 class 객체 & 관련 메서드
- 모든 class 파일은 Classloader에 의해 메모리에 올라갈 때 생성되는 class에 대한 정보가 담긴 객체
➠ 애너테이션의 정보도 포함 - ' className . class ' 의 형식으로 class 객체를 참조
- getAnnotation()
➠ 해당 class 객체로부터 얻어오고자 하는 애너테이션을 매개변수로 지정하여
해당 애너테이션에 대한 정보를 얻어오는 메서드 - getAnnotations() ➠ 모든 애너테이션을 배열로 받아오는 메서드
- class 정보를 제공하는 메서드들
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Class (Java Platform SE 8 )
Determines if the specified Class object represents a primitive type. There are nine predefined Class objects to represent the eight primitive types and void. These are created by the Java Virtual Machine, and have the same names as the primitive types tha
docs.oracle.com
▼ Study📋
☑️ enum 상수는 일종의 객체이며, enum 상수마다 다른 역할을 하도록 하고 싶다면 추상 메서드를 생성해주고 각 enum 상수마다 역할에 맞는 추상 메서드를 구현해주면 된다
☑️ 혹시라도 발생할 에러를 쉽게 해결할 수 있도록 컴파일 에러를 발생시켜주는 Annotation을 자주 활용하자
☑️ ' @Deprecated ' 는 java 버전을 업그레이드하는 경우에 주로 사용
☑️ ' @SuppressWarnings ' 는 경고를 억제하고자 하는 대상에만 붙여 경고의 억제범위를 최소화하자
☑️ (Meta Annotation 이용) Annotation을 만들어서도 많이 쓴다
☑️ 클래스의 메소드, 타입, 변수들에 접근할 수 있도록 해주는 자바 API이며, Class 객체를 통해서 사용
🌑 지네릭스 (Generics)
✔️ Generics
🔹 Generics 란 ?
- 다양한 타입의 객체들을 다루는 메서드나 Collection class에
컴파일 시의 타입체크(compile-time type check)를 해주는 기능
➠ 객체의 타입 안정성을 제공 - 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다
➠ Collection class는 보통 한 종류의 객체를 담는 경우가 많기 때문에
매번 타입체크를 하고 형변환하는 것은 비효율적 !
✔️ Generic class
🔹 Generic class의 선언
class Box<T> { // Generic 타입 T를 선언
T item;
void setItem(T item) { this.item = item; }
T getItem() { return item; }
}
🔻 ' T ' ➠ ' 타입 변수 (type variable) '
🔻 ' E ' ➠ ' Element (요소) ' ( ex. ArrayList<E> )
Box<String> b = new Box<String>();
b.setItem(new Object()); // Error!
b.setItem("ABC"); // OK
String item = b.getTime(); // 형변환 필요 X
🔻 ' T ' 대신 실제 타입 (' String ') 지정
- Generics가 도입되기 이전의 코드와 호환성을 유지하기 위해 Generics를 사용하지 않은 코드를 허용하는 것 !
➠ Generics class를 사용할 때는 반드시 타입을 지정해주자
Box b = new Box();
// Box<Object> b = new Box<Object>(); // 타입 지정시 경고 발생 X
b.setItem(new Object()); // 경고 발생
b.setItem("ABC"); // 경고 발생
🔹 Generics의 용어
class Box<T> {}
- Box<T> ➠ Generic class. ' T의 Box ' 또는 ' T Box ' 라고 읽는다
- T ➠ 타입 변수 또는 타입 매개변수. (T는 타입 문자)
- Box ➠ 원시 타입 (raw type)
Box<String> b = new Box<String>();
- ' Generic 타입 호출 ' ➠ 타입 매개변수에 타입을 지정하는 것
- 지정된 타입 ' String ' ➠ ' 매개변수화된 타입 (parameterized type) '
➠ ' 대입된 타입 '
🔹 Generics의 제한
- Generic class의 객체를 생성할 때, 객체별로 다른 타입을 지정하자
Box<Apple> appleBox = new Box<Apple>();
Box<Grape> grapeBox = new Box<Grape>();
- static 멤버는 intance 변수를 참조할 수 없듯이 타입 변수 T를 사용 불가능 !
➠ ' T ' 는 instance 변수로 간주
➠ static 멤버는 대입된 타입의 종류에 관계없이 동일한 것이어야 한다
class Box<T> {
static T item; // Error!
static int compare(T t1, T t2) { ... } // Error
}
- Generic 배열 타입의 참조변수는 선언 가능
Generic 타입의 배열은 생성 불가능 !
class Box<T> {
T[] itemArr; // OK
...
T[] toArray() {
T[] tmpArr = new T[itemArr.length]; // Error
...
return tmpArr;
}
...
}
🔻 new 연산자나 instanceof 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야 하는데,
Generic 타입의 배열을 생성하게 되면 알 수가 없어 T를 피연산자로 사용할 수 없다 !
🔻 Generic 타입의 배열을 생성해야만 한다면 new 연산자 대신
' newInstance() ' 와 같이 동적으로 객체를 생성하는 메서드를 이용하거나
Object 배열을 생성해서 복사한 다음에 ' T[ ] '로 형변환하는 방법을 사용
✔️ Generic class의 객체 생성 & 사용
class Box<T> {
ArrayList<T> list = new ArrayList<T>();
void add(T item) { list.add(item); }
...
}
🔹 Generic class의 객체를 생성하고 사용할 때 알아둬야 할 점
- 참조변수와 생성자에 대입된 타입이 일치해야 한다
Box<Apple> appleBox = new Box<Apple>(); // OK
Box<Apple> appleBox = new Box<Grape>(); // Error
- Apple class가 Fruit class의 자손이라 가정
➠ 대입된 타입이 다른 것은 허용 X
➠ 두 Generic class의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다
Box<Fruit> appleBox = new Box<Apple>(); // Error
Box<Apple> appleBox = new FruitBox<Apple>(); // OK. 다형성
- 타입 생략 가능 ( JDK1.7 ~ )
Box<Apple> appleBox = new Box<>();
- 대입된 타입과 다른 타입의 객체는 추가 불가능 !
Box<Apple> appleBox = new Box<Apple>();
appleBox.add(new Apple()); // OK
appleBox.add(new Grape()); // Error!
- 단, super class 타입으로 생성된 객체의 메서드의 매개변수로는 sub class도 될 수 있다
Box<Fruit> fruitBox= new Box<Fruit>();
fruitBox.add(new Fruit()); // OK
fruitBox.add(new Apple()); // OK
✔️ 제한된 Generic class
FruitBox<Toy> fruitBox = new FruitBox<Toy>();
fruitBox.add(new Toy)); // OK
🔹 특정 sub class만 타입으로 지정 가능
- Generic 타입에 ' extends ' 사용
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
...
}
- 매개변수의 타입 T는 매개변수화된 타입(' Fruit ')의 자손 타입도 될 수 있다
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Toy> fruitBox = new FruitBox<Toy>(); // Error!
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
fruitBox.add(new Apple()); // OK
- class가 아니라 interface를 구현해야 한다는 제약이 필요할 때도 ' implements ' 가 아닌 ' extends ' 사용
class FruitBox<T extends Fruit & Eatable> { ... }
🔻 Fruit class의 sub class이면서 Eatable interface도 구현한 class만
타입 매개변수 ' T ' 에 대입되게 한다면 ' & ' 기호로 연결
✔️ 와일드 카드 ( WildCard )
🔹 와일드 카드가 필요한 이유
- static 메서드에는 타입 매개변수 T를 매개변수에 사용 X
➠ Generics를 적용하지 않던가, 타입 매개변수 대신 특정 타입('<Fruit>')을 지정해줘야 한다
➠ ' FruitBox<Apple> ' 타입의 객체는 maketJuice() 메서드의 매개변수가 될 수 없다
class Jucier {
static Juice makeJuice(FruitBox<Fruit> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
}
- 여러 가지 타입의 매개변수를 갖는 makeJuice()를 만들어야 하는데
Generic 타입이 다른 것만으로는 Overloading 성립 X
FruitBox<Apple> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.makeJuice(new FruitBox<Fruit>()));
System.out.println(Juicer.makeJuice(appleBox)); // Error!
System.out.println(Juicer.makeJuice(grapeBox)); // Error!
🔹 와일드 카드 ' ? '
- <? extends T> ➠ T와 그 sub class들만 가능
- <? super T> ➠ T와 그 super class들만 가능
- <?> ➠ 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
- ' & ' 사용 불가능
class Jucier {
static Juice makeJuice(FruitBox<? extends Object> box) {
String tmp = "";
for(Fruit f : box.getList()) tmp += f + " "; // Error! Fruit이 아닐 수 있다
return new Juice(tmp);
}
}
🔻 ' box ' 의 요소가 Fruit class의 자손이라는 보장이 X
➠ ' box ' 에 저장된 요소를 Fruit 타입의 참조변수로 못받는다 !
class FruitBox<T extends Fruit> extends Box<T> {}
➠ 그러나 Generic class ' FruitBox ' 를 제한했기 때문에 문제없이 Compile 된다 !
System.out.println(Juicer.makeJuice(new FruitBox<Apple>())); // OK
🔻 모든 종류의 FruitBox가 매개변수로 가능해진다 !
- Comparator<? super T>
➠ Comparator의 타입 매개변수로 T class와 그 super class가 가능하다는 의미
static void sort(List<T> list, Comparator<T> c)
🔻 새로운 sub class가 생길 때마다 그에 맞는 Comparator<T>를 구현한 class를 반복해서 생성해야 한다
➠ 코드의 중복 · 비효율적인 작업
static <T> void sort(List<T> list, Comparator<? super T> c)
🔻 타입 매개변수에 하한 제한의 와일드 카드를 적용하면 문제 해결 !
class FruitComp implements Comparator<Fruit> {
public int compare(Fruit t1, Fruit t2) {
return t1.weight - t2.weight;
}
}
Collections.sort(appleBox.getList(), new FruitComp());
Collections.sort(grapeBox.getList(), new FruitComp());
🔻 Apple class와 Grape class의 super class인 Fruit class를 타입 매개변수로 하는 Comparator를
구현한 ' FruitComp ' class만 만들면, List<Apple>과 List<Grape>를 모두 정렬 가능
✔️ Generic 메서드
🔹 Generic 메서드 선언
- 메서드의 선언부에 Generic 타입이 선언된 메서드 ➠ Generic 메서드
ex) Collections.sort()
- Generic 메서드에 선언된 타입 매개변수 T ≠ Generic class에 선언된 타입 매개변수T
static <T> void sort(List<T> list, Comparator<? super T> c)
🔻 static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드엔 Generic 타입 선언 가능 !
- Generic 메서드로 변환
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
⭣ ⭣
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { ... }
🔻 이렇게 선언된 Generic 메서드를 호출할 때는 아래와 같이 타입 변수에 타입을 대입해야 한다
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> fruitBox = new FruitBox<Apple>();
...
System.out.println(Juicer.<Fruit>makeJuice(fruitBox));
// System.out.println(<Fruit>makeJuice(fruitBox)); // Error!
System.out.println(Juicer.<Apple>makeJuice(appleBox));
// System.out.println(Juicer.makeJuice(appleBox));
🔻 대입된 타입은 생략 가능
➠ 같은 class 내에 있는 멤버들끼리는 ' this. ' 이나 ' class 이름. ' 을 생략하고
메서드 이름만으로 호출 가능
➠ 단, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 class 이름은 생략 불가능
- Generic 메서드는 매개변수의 타입이 복잡할 때 유용하다 !
public static void printAll(ArrayList<? extends Product> list,
ArrayList<? extends Product> list2) {
for(Unit u : list) {
System.out.println(u);
}
}
⭣ ⭣
public static <? extends Product> void printAll(ArrayList<T> list,
ArrayList<T> list2) {
for(Unit u : list) {
System.out.println(u);
}
}
- 복잡하게 선언된 Generic 메서드
public static <T extends Comparable<? super T>> void sort(List<T> list)
- <T extends Comparable> ➠ ' T ' 는 Comparable을 구현한 class이어야 한다
- Comparable<? super T>
➠ Comparable은 ' T ' 또는 그 super class의 타입을 비교하는 interface이어야 한다 - List<T> ➠ 타입 T를 요소로 하는 List를 매개변수로 허용한다
✔️ Generic 타입의 형변환
🔹 Generic 타입 ⇄ 원시 타입 (raw type)
- Generic 타입과 Non-Generic 타입간의 형변환은 항상 가능하지만, 경고만 발생
Box box = null;
Box<Object> objBox = null;
box = (Box)objBox;
objBox = (Box<Object>)box;
🔹 대입된 타입 (매개변수화된 타입) ⇄ 다른 Generic 타입
- 대입된 타입과 다른 Generic 타입 간에 형변환은 불가능
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // Error!
strBox = (Box<String>)objBox; // Error!
// Box<Object> objBox = (Box<Object>)new Box<String>();
Box<Object> objBox = new Box<String>(); // Error!
🔻 ' Box<Object> ' ⇆ ' Box<String> '
➠ 대입된 타입이 Object이더라도 형변환은 불가능 !
- 가능한 경우
// Box<? extends Object> xBox = (Box<Object>)new Box<String>();
Box<? extends Object> xBox = new Box<String>();
🔻 ' Box<String> ' ➙ ' Box<? extends Object> '
static Juice makeJuice(FruitBox<? extends Fruit> box) { ... }
FruitBox<? extends Fruit> box = new FruitBox<Fruit>(); // OK
FruitBox<? extends Fruit> box = new FruitBox<Apple>(); // OK
🔻 매개변수에 다형성 적용 ( ' FruitBox<Apple> ' ➙ ' FruitBox<? extends Fruit> ' )
➠ 반대로의 형변환도 가능하지만, 확인되지 않은 형변환이라는 경고 발생
FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (Fruit<Apple>)box; // 경고
🔹 WildCard (' ? ') 가 사용된 Generic 타입
Optional<?> EMPTY = new Optional<>();
// ➙ Optional<?> EMPTY = new Optional<Object>();
// ➙ Optional<? extends Object> EMPTY = new Optional<Object>();
Optional<?> EMPTY = new Optional<?>(); // Error!
🔻 <?> ➠ <? extends Object>
🔻 <> 안에 생략된 타입은 ' ? ' 가 아닌 ' Object ' 이다
- Optional<Object>가 아닌 Optional<?>로 한 이유
➠ Optional<T>로 형변환이가능하기 때문이다 !
Optional<?> wopt = new Optional<Object>();
Optional<Object> oopt = new Optional<Object>();
Optional<String> sopt = (Optional<String>)wopt; // OK
Optional<String> sopt = (Optional<String>)oopt; // Error
- ' Optional<Object> ' ➙ ' Optional<String> ' 같은 직접적인 형변환은 불가능
➠ WildCard가 포함된 Generic 타입으로 형변환하면 가능 !
' Optional<Object> ' ➙ ' Optional<?> ' ➙ ' Optional<T> ' ( OK, 미확정 타입으로의 형변환 경고 발생 ) - WildCard가 사용된 Generic 타입끼리의 형변환
➠ WildCard는 확정된 타입이 아니므로 Compiler가 미확정 타입으로 형변환하는 것이라고 경고 !
✔️ Generic 타입의 제거
🔹 Compiler의 Generic 타입 제거 (이전 소스 코드와의 호환성 때문에)
- Generic 타입의 경계(bound) 제거
' <T extends Fruit> ' ➠ T는 Fruit으로 치환 / ' <T> ' ➠ T는 Object로 치환 - Generic 타입을 제거한 후에 타입이 일치하지 않으면, 형변환 추가
T get(int i) { return list.get(i); }
⭣ ⭣
Fruit get(int i) { return (Fruit)list.get(i); }
🔻 List의 get() 메서드는 Object타입을 반환하므로 형변환이 필요하다 !
🌒 열거형 (enums)
✔️ 열거형 (enum) 이란?
🔹 열거형 (enum)
- 서로 관련된 상수를 편리하게 선언하기 위해 추가된 것 (JDK1.5 ~)
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, TREEE, FOUR }
final Kind kind;
final Value num;
}
- C언어의 enum과 달리 enum이 갖는 값뿐만 아니라 타입도 관리
if(Card.CLOVER == Card.TWO) // true지만 false이어야 의미상 맞다
if(Card.Kind.CLOVER == Card.Value.Two) // Compile Error! : 타입이 다르다
- 상수의 값이 바뀌면, 해당 상수를 참조하는 모든 소스를 다시 Compile 해야 한다
➠ 하지만 enum 상수를 사용하면, 기존의 소스를 다시 Compile 할 필요 X
✔️ enum의 정의 & 사용
🔹 enum을 정의하는 방법
- enum 이름은 class처럼 첫 글자만 대문자로 하고 나머지는 소문자로 작성
- enum 상수의 이름은 모두 대문자로 작성 (여러 단어를 연결할 경우 ' _ ' 로 연결)
// 동서남북 4방향을 상수로 정의하는 enum Direction
enum Direction { EAST, SOUTH, WEST, NORTH }
🔹 enum에 정의된 상수를 사용하는 방법
- ' enum이름 . 상수명 ' ➠ class의 static 변수를 참조하는 것과 동일
class Unit {
int x, y; // Unit의 위치
Direction dir; // enum을 instance 변수로 선언
void init() {
dir = Direction.EAST; // Unit의 방향을 EAST로 초기화
}
}
- switch문의 조건식에도 enum 사용 가능
➠ case문에 enum의 이름은 적지 않고 상수의 이름만 적어야 한다 !
🔹 enum 상수간의 비교
- ' == ' 사용 가능 ➠ equals() 메서드를 사용하지 않아도 비교 가능한 만큼 빠른 성능을 제공 !
- ' < ' , ' > ' 와 같은 비교연산자는 사용 불가능 ➠ 대신 compareTo() 메서드 사용
🔹 모든 enum의 super class - java.lang.Enum
- Compiler가 자동으로 추가해주는 메서드들
- values() ➠ enum의 모든 상수를 배열에 담아 반환
- ordinal() ➠ enum 상수가 정의된 순서(0부터 시작)를 정수로 반환
- valuesOf() ➠ enum 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있다
- values() ➠ enum의 모든 상수를 배열에 담아 반환
Direction d = Direction.valueOf("WEST");
System.out.println(d);
System.out.println(Direction.WEST == Direction.valueOf("WEST"));
WEST
true
- java.lang.Enum
https://docs.oracle.com/javase/8/docs/api/java/lang/Enum.html
Enum (Java Platform SE 8 )
Returns the enum constant of the specified enum type with the specified name. The name must match exactly an identifier used to declare an enum constant in this type. (Extraneous whitespace characters are not permitted.) Note that for a particular enum typ
docs.oracle.com
✔️ enum에 멤버 추가하기
🔹 enum 상수에 원하는 값 지정
- Enum class에 정의된 ordinal() 메서드가 enum 상수가 정의된 순서를 반환하지만,
이 값을 enum 상수의 값으로는 사용하지 말자 - enum 상수에 원하는 값을 지정해주고 싶다면,
지정된 값을 저장할 수 있는 intance 변수와 생성자를 새로 추가해야 한다 ! - 하나의 enum 상수에 여러 값도 지정 가능 !
➠ 단, 그에 맞게 instance 변수와 생성자 등을 새로 추가해줘야 한다
enum Direction {
EAST(1, ">"), SOUTH(2, "V"), WEST(3, "<"), NORTH(4, "^"); // 끝에 ';' 추가
private final int value; // 정수를 저장할 필드(instance 변수 추가)
private final String symbol; // 기호를 저장할 필드(instance 변수 추가)
Direction(int value, String symbol) { // 생성자 추가
this.value = value;
this.symbol = symbol
}
public int getValue() { return value; }
public String getSymbol() { return symbol; }
}
🔻 enum 상수를 모두 정의한 다음에 다른 멤버를 추가해야 한다 !
🔻 enum의 instance 변수는 반드시 final이어야 하는 제약은 없지만,
' value ' 는 enum 상수의 값을 저장하기 위한 것이므로 final로 선언한 것이다
🔻 enum의 생성자는 제어자가 묵시적으로 private !
➠ 외부에서 호출해서는 안되기 때문에 !
✔️ enum에 추상 (abstract) 메서드 추가하기
🔹 enum에 정의된 추상 메서드를 각 상수가 구현하는 예제
- 사실 enum에 추상 메서드를 선언할 일은 많지 않다
// 거리에 따라 요금을 계산하는 방식이 각 운송 수단마다 다르기 때문에, 추상 메서드 필요
enum Transportation {
Bus(100) { // Anonymous class
int fare(int distance) {
return distance*BASIC_FARE;
}
},
TRAIN(150) { int fare(int distance) { return distance*BASIC_FARE; }},
SHIP(100) { int fare(int distance) { return distance*BASIC_FARE; }},
AIPLANE(300) { int fare(int distance) { return distance*BASIC_FARE; }};
abstract int fare(int distance); // 거리에 따른 요금을 계산하는 추상 메서드
protected final int BASIC_FARE; // protected로 해야 각 상수에서 접근 가능
Transportation(int basicFare) {
BASIC_FARE = basicFare;
}
public int getBasicFare() { return BASIC_FARE; }
}
🔻 추상 메서드를 정의하고, 각 상수마다 ' 익명 클래스(Anonymous class) ' 처럼
메서드를 재정의하게 해서 각 상수마다 다른 역할을 하는 메서드를 갖게 하는 원리
✔️ enum의 이해
🔹 enum의 핵심
- enum 상수를 단순히 정수로 치부하지 말고 객체지향적으로 객체화해서 관리하자 !
➠ enum 상수는 일종의 객체 !
🔹 enum의 내부적인 개념
enum Direction { EAST, SOUTH, WEST, NORTH }
- enum 상수 하나하나가 자신의 instance를 생성하여 public static final 필드로 공개한다
➠ class로 정의하면 아래와 같이 작성한 것이다
class Direction {
public static final Direction EAST = new Direction("EAST");
...
}
🔻 결국 enum 상수의 값은 객체의 주소(변하지 않는 값)이기 때문에, ' == ' 로 비교가 가능한 것이다
- 모든 enum은 추상 class ' Enum '의 자손 ! ( ' MyEnum ' ➠ Enum을 흉내내어 작성한 class )
abstract class MyEnum<T extends MyEnum<T>> implements Comparable<T> {
static int id = 0; // 객체에 붙일 일련번호 (0부터 시작)
int ordinal;
String name = "";
public int ordinal() { return ordinal; }
MyEnum(String name) {
this.name = name;
ordinal = id++; // 객체를 생성할 때마다 id의 값을 증가시킨다
}
public int compareTo(T t) {
return ordinal - t.ordinal();
}
}
🔻 ' <T extends MyEnum<T>> ' 로 선언
➠ 타입 T가 MyEnum<T>의 자손이어야 한다는 것을 compiler가 확인
➠ 따라서, 타입 T에 ordinal() 메서드가 정의되어 있는 것이 분명해진다
➠ 형변환 없이도 ' t.ordinal() ' 에서 에러가 발생하지 않는 것이다 !
🔻 만약 ' <T> ' 로 선언되었다면, 타입 T에 ordinal() 메서드가 정의되어 있는지 확인을 못해 에러 발생 !
- enum에 추상 메서드를 추가하면, 각 enum 상수 모두 추상 메서드를 구현해줘야 하는 이유
enum Direction {
EAST("EAST") { // 익명 class
Point move(Point p) { ... }
};
SOUTH("SOUTH") {
Point move(Point p) { ... }
};
...
abstract Point move(Point p);
}
⭣ ⭣
abstract class Direction extends MyEnum {
public static final Direction EAST = new Direction("EAST") { // Anonymous class
Point move(Point p) { ... }
};
public static final Direction SOUTH = new Direction("SOUTH") {
Point move(Point p) { ... }
};
...
abstract Point move(Point p);
}
🔻 추상 메서드를 새로 추가하면, class 앞에도 ' abstract ' 를 붙여줘야 하고,
각 static 상수들도 추상 메서드를 구현해줘야 하는 것과 같은 이유이다
🌓 애너테이션 (annotation)
✔️ 애너테이션이란 ?
🔹 애너테이션 (annotation)
- 주석, 주해, 메모를 의미
- 주석처럼 프로그램 자체에는 아무런 영향 X
- 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것 !
ex) 특정 메서드만 테스트하기를 원한다면 ' @Test ' 라는 애너테이션을 해당 메서드 앞에 붙이면 된다
➠ 테스트 프로그램을 제외한 다른 프로그램에게는 아무 의미 없는 정보이다
✔️ 표준 애너테이션
@Override
- 메서드 앞에만 붙일 수 있는 애너테이션
➠ super class의 메서드를 Overriding 하는 것이라는 걸 Compiler에게 알려주는 기능 - Overriding 할 super class의 메서드 이름을 잘못 적은 경우 에러메시지를 출력해준다
@Deprecated
- 기존 코드와의 호환성 문제로 함부로 삭제할 수는 없으나,
다른 것으로 대체되었으니 더 이상 사용하지 않은 것을 권하는 의미 (강제성 X)
➠ java 버전을 업그레이드 하는 경우에 주로 사용 - ' -Xlint:deprecation ' 옵션을 붙여 Compile 하면 오류에 대한 내용을 더 자세하게 보여준다
@FunctionalInterface
- ' 함수형 인터페이스(functional interface) ' 를 올바르게 선언했는지 확인하고,
잘못된 경우 에러를 발생시키는 기능 - 함수형 인터페이스(functional interface)
➠ 추상 메서드가 하나뿐이어야 한다는 제약이 있다.
@SuppressWarnings
- Compiler가 보여주는 경고메시지가 나타나지 않게 억제해주는 기능
➠ 묵인해야하는 경고가 발생하는 대상에 붙여 중요한 경고를 놓치지 않기 위해 사용한다 - 주로 묵인하게 되는 경고 메시지 종류
➠ " deprecation " ~ '@Deprecated' 가 붙은 대상을 사용해서 발생하는 경고
➠ " unchecked " ~ Generics로 타입을 지정하지 않았을 때 발생하는 경고
➠ " rawtypes " ~ Generics를 사용하지 않아서 발생하는 경고
➠ " varargs " ~ 가변인자의 타입이 Generic 타입일 때 발생하는 경고
@SuppressWarnings("unchecked") // Generics에 관련된 경고 억제
ArrayList list = new ArrayList(); // Generic 타입을 지정하지 않았다
list.add(obj); // 여기서 경고 억제
@SuppressWarnings("deprecation") // deprecation 관련 경고를 억제
public static void main(String args[]) {
...
@SuppressWarnings("unchecked") // 지네릭스 관련 경고를 억제
ArrayList<Integer> list = new ArrayList();
}
🔻 위처럼 두 종류 이상의 경고를 main 같은 메서드에 붙여서 한 번에 억제한 경우엔
나중에 추가된 코드에서 발생할 수도 있는 경고까지 억제될 수 있으므로 바람직하지 않다
➠ 해당 대상에만 애너테이션을 붙이자 !
- ' -Xlint ' 옵션으로 Compile 해서 경고의 내용 중 대괄호 [ ] 안에 있는 것이 경고 메시지의 종류이다
➠ 새로운 버전에서 경고가 발생할 수 있기에 새로 추가된 경고 메시지를 억제하려면
경고 메시지의 종류를 알아야 한다
@SafeVarargs
- static이나 final이 붙은 메서드와 생성자에만 붙일 수 있다
➠ 즉, Override 될 수 있는 메서드에는 사용 불가능 ! - refiable 타입 ➙ Compile 후에도 제거되지 않는 타입
non-refiable 타입 ➙ Compile 후에 제거되는 타입
➠ 대부분의 Generics 타입처럼 Compile 후에 제거되는 타입들이 있다 - 메서드에 선언된 가변인자(varargs)의 타입이 non-reifiable 타입일 경우,
해당 메서드를 선언 · 호출하는 부분에서 "unchecked" 경고가 발생
➠ 메서드를 선언하는 부분에만 ' @SafeVarargs '를 붙이면 호출하는 부분에서 발생하는 경고도 억제
@SafeVarargs // 'unchecked' 경고 억제
@SuppressWarnings("varargs") // 'varargs'경고 억제
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a); // 경고 발생
}
🔻 메서드에 선언된 ' T ' ➙ ' Object[ ] ' (Compile 과정)
➠ ' Object[ ] ' 에는 모든 타입의 객체가 들어올 수 있어 위험하다고 판단해 경고가 발생하는 것
➠ 하지만, asList() 메서드가 호출되는 부분은 Compiler가 체크해서
타입 T가 아닌 다른 타입이 들어가지 못하게 하므로 문제가 없는 코드이다 !
➠ 이때 메서드 앞에 ' @SafeVarargs ' 를 붙여서
해당 메서드의 가변인자는 타입 안정성이 있다고 전달해주는 것이다
🔻 ' @SafeVarargs ' 는 "varargs" 경고는 억제하지 못하기 때문에, ' @SuppressWarning ' 를 같이 붙인다
➠ 가능하면 이 두 애너테이션은 항상 같이 사용하자
✔️ 메타 애너테이션 (Meta Annotation)
🔹 Meta Annotation 이란 ?
- 애너테이션을 위한 애너테이션, 즉 애너테이션에 붙이는 애너테이션이다 !
➠ 애너테이션의 적용대상(target)이나 유지기간(retention) 등을 지정하는데 사용
@Target
- 애너테이션이 적용가능한 대상을 지정하는데 사용
import static java.long.annotation.ElementType.*;
@Target({FIELD, TYPE, TYPE_USE})
public @interface MyAnnotation { }
⭣ ⭣ ( ' @Target ' 으로 지정해줘서 아래처럼 애너테이션을 적용 가능한 것이다 )
@MyAnnotation // TYPE
class MyClass {
@MyAnnotation // FIELD
int i;
@MyAnnotation // TYPE_USE
MyClass mc
}
- ' @Target ' 으로 지정할 수 있는 애너테이션 적용대상의 종류
대상 타입 | 의미 |
ANNOTATION_TYPE | 애너테이션 |
CONSTRUCTOR | 생성자 |
FIELD | 필드(멤버변수, enum상수) |
LOCAL_VARIABLE | 지역변수 |
METHOD | 메서드 |
PACKAGE | 패키지 |
PARAMETER | 매개변수 |
TYPE | 타입(class, interface, enum) |
TYPE_PARAMETER | 타입 매개변수 (JDK1.8~) |
TYPE_USE | 타입이 사용되는 모든 곳 (JDK1.8~) |
🔻 ' FIELD ' 는 기본형에, ' TYPE_USE ' 는 참조형에 사용된다
@Retetion
- 애너테이션이 유지(retention)되는 기간을 지정하는데 사용
- 애너테이션의 유지 정책(retention policy)의 종류
유지 정책 | 의미 |
SOURCE | src 파일에만 존재. class 파일에는 존재 X |
CLASS | class 파일에 존재. 실행 시에 사용 불가. 기본값 |
RUNTIME | class 파일에 존재. 런타임(실행 시)에 사용 가능 (by reflection) |
🔻 리플랙션 (reflection) ?
- 클래스 객체를 통해 필드/메서드/생성자를 접근 제어자와
상관 없이 사용할 수 있도록 해주는 자바 API - 프레임워크 또는 라이브러리 개발 시 사용된다
🔻 ' SOURCE ' ➠ Compiler를 직접 작성할 것이 아니면 필요 X
🔻 ' RUNTIME '
➠ 실행 시에 ' 리플렉션(reflection) ' 을 통해 class 파일에 저장된
애너테이션의 정보를 읽어서 처리 가능하게 한다
ex) ' @FunctionInterface ' 은 Compiler가 체크해주는 애너테이션이지만,
실행 시에도 사용되므로 유지 정책이 ' RUNTIME ' 이다
➠ 지역 변수에 붙은 애너테이션은 Compiler만 인식할 수 있어,
' RUNTIME ' 인 애너테이션을 지역변수에 붙여도 실행 시에는 인식 불가능
🔻 ' CLASS '
➠ Compiler가 애너테이션의 정보를 class 파일에 저장할 수 있게 한다
➠ 하지만, class 파일이 JVM에 로딩될 때는 애너테이션의 정보가 무시되어
실행 시에 애너테이션에 대한 정보를 얻을 수 없다
➠ 이것이 ' CLASS ' 가 잘 사용되지 않는 이유
@Documented
- 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다
- ' @Override ' 와 ' @SuppressWarnings ' 를 제외한 표준 애너테이션에는 이 메타 애너테이션이 붙어 있다
@Inherited
- 애너테이션이 sub class에 상속되도록 한다
➠ ' @Inherited '이 붙은 애너테이션을 super class에 붙이면,
sub class도 이 애너테이션이 붙은 것과 같이 인식
@Repeatable
- 보통은 하나의 대상에 한 종류의 애너테이션을 붙이는데,
이 메타 애너테이션이 붙은 애너테이션은 여러 번 붙일 수 있다
@ToDo("delete test codes")
@ToDo("override inherited method")
class MyClass {
...
}
- 같은 이름의 애너테이션이 여러 개가 하나의 대상에 적용될 수 있어,
이 애너테이션들을 하나도 묶어서 다룰 수 있는 컨테이너 애너테이션도 추가로 정의해야 한다 !
@interface ToDos { // 여러 개의 ToDo 애너테이션을 담을 컨테이너 애너테이션 ToDos
ToDo[] value(); // ToDo 애너테이션 배열타입의 요소를 선언. 이름이 반드시 value이어야 한다!
}
@Repeatable(ToDos.class) // 괄호 안에 컨테이너 애너테이션을 지정
@interface ToDo {
String value();
}
@Native
- 네이티브 메서드(native method)에 의해 참조되는 ' 상수 필드 (constant field) ' 에 붙이는 애너테이션
➠ Native method ➙ JVM이 설치된 OS의 메서드
➠ Native 메서드를 호출할 때 실제로 호출되는 것은 OS의 메서드이다
➠ Java에 정의된 Native 메서드와 OS의 메서드를 연결해주는 작업을
수행하는 것이 ' JNI (Java Native Interface) ' ! - Object class의 메서드들은 대부분 Native method이다
✔️ 애너테이션 타입 정의
🔹 새로운 애너테이션을 정의하는 방법
- ' @ ' 기호를 붙이는 것을 제외하면 interface를 정의하는 것과 동일
@interface annotationName {
타입 요소이름(); // 애너테이션의 요소를 선언
...
}
- ' @Override ' ➠ 애너테이션
' Override ' ➠ 애너테이션의 타입
✔️ 애너테이션 요소
🔹 애너테이션의 요소
- 애너테이션 내에 선언된 메서드
➠ ' 애너테이션의 요소(element) '
➠ 반환값이 있고 매개변수는 없는 추상 메서드의 형태를 가진다
@interface TestInfo {
int count();
String testedBy();
String[] testTools();
TestType testType(); // enum TestType { FIRST, FINAL }
DateTime testDate(); // 자신이 아닌 다른 애너테이션(@DateTime) 포함 가능
}
@interface DateTime {
String yymmdd();
String hhmmss();
}
- 애너테이션을 적용할 때 이 요소들의 값을 빠짐없이 지정해야 한다 (순서 상관 X)
@TestInfo (
count = 3, testedBy = "Shin",
testTools = {"JUnit", "AutoTester"},
testType = TestType.FIRST,
testDate = @DateTime(yymmdd = "230717", hhmmss = "034245");
}
- 애너테이션의 각 요소는 default 값 지정 가능
➠ default 값이 여러 개인 경우(요소의 타입이 배열)엔 괄호{ } 사용
@interface TestInfo {
int count() default 1; // 기본값을 1로 지정
}
@TestInfo // @TestInfo(count = 1)과 동일
🔻 default 값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 defaut 값이 사용된다
- 애너테이션의 요소가 오직 하나뿐이고 이름이 value인 경우,
애너테이션을 적용할 때 요소의 이름을 생략하고 값만 적는 것이 가능
@interface SuppressWarnings {
String[] value();
}
// @SuppressWarnings(value = {"deprecation", "unchecked"})
@SuppressWarnings({"deprecation", "unchecked"})
- 요소의 타입이 배열인 경우
@interface TestInfo {
String[] testTools();
}
@Test(testTools = {"JUnit", "AutoTester"}) // 값이 여러 개인 경우
@Test(testTools = "JUnit") // 값이 하나일 때는 괄호 생략 가능
@Test(testTools = {}) // 값이 없을 때는 괄호{}가 반드시 필요
🔹 애너테이션의 요소를 선언할 때 지켜야 하는 규칙
- 요소의 타입은 기본형, String, enum, Annotation, Class만 허용 O
- ()안에 매개변수를 선언 X
- 예외를 선언 X
- 요소를 타입 매개변수로 정의 X
@interface AnnoTest {
int id = 100; // OK. 상수선언. static final int id = 100;
String major(int i, int j); // Error! 매개변수 선언 불가능
String minor() throws Exception; // Error! 예외 선언 불가능
ArrayList<T> list(); // Error! 요소의 타입에 타입 매개변수 사용불가
✔️ java.lang.anotation.Annotation
🔹 Annotation
- 모든 애너테이션의 조상
➠ 하지만, 애너테이션은 상속 허용 X
➠ 명시적으로 Annotation을 조상으로 지정 불가능 - 애너테이션이 아니라 일반적인 interface로 정의되어 있다
➠ equals() · hashCode() · toString()과 같은 메서드가 Annotation interface 내에 정의되어 있어,
모든 애너테이션 객체에 대해 이러한 메서드를 호출하는 것이 가능하다
Class<AnnotationTest> cls = AnnotationTest.class;
Annotation[] annoArr = AnnotationTest.class.getAnnotations();
for(Annotation a : annoArr) {
System.out.println(a.toString());
System.out.println(a.hashCode());
System.out.println(a.equals());
System.out.println(a.annotationType());
}
🔻 AnnotationTest class에 적용된 모든 애너테이션에 대해 위와 같은 메서드들을 호출하였다
✔️ 마커 애너테이션 (Marker Annotation)
🔹 정의된 요소가 하나도 없는 애너테이션
- 값을 지정할 필요가 없는 경우, 애너테이션의 요소를 하나도 정의하지 않는다
( Serializable · Cloneable interface 같은 형태 )
✔️ class 객체
🔹 class 객체 & 관련 메서드
- 모든 class 파일은 Classloader에 의해 메모리에 올라갈 때 생성되는 class에 대한 정보가 담긴 객체
➠ 애너테이션의 정보도 포함 - ' className . class ' 의 형식으로 class 객체를 참조
- getAnnotation()
➠ 해당 class 객체로부터 얻어오고자 하는 애너테이션을 매개변수로 지정하여
해당 애너테이션에 대한 정보를 얻어오는 메서드 - getAnnotations() ➠ 모든 애너테이션을 배열로 받아오는 메서드
- class 정보를 제공하는 메서드들
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Class (Java Platform SE 8 )
Determines if the specified Class object represents a primitive type. There are nine predefined Class objects to represent the eight primitive types and void. These are created by the Java Virtual Machine, and have the same names as the primitive types tha
docs.oracle.com