티스토리 뷰
728x90
서문
자바의 정석 기초편 챕터 12편을 기재합니다.
목적은 공부한 내용을 기록하는 것에 있기 때문에 완전한 문장이 아닐 수도 있습니다.
또한 모든 내용을 적은 것은 아닙니다.
- 참고 자료
자바의 정석 ( ch.12 )
제네릭스(Generics)
- 컴파일시 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
- 매우 중요한 기능(런타임에러보단 컴파일시에 알려주는 에러가 더 나음)
- 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌
1. 제네릭스 사용 예시
ArrayList<Tv> tvList = new ArrayList<Tv>();
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<Integer> intList = new ArrayList<Integer>();
intList.add(10);
intList.add(20);
intList.add("30"); // The method add(Integer) in the type ArrayList<Integer> 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<E> extends AbstractList<E>{
private transient E[] elementData;
public boolean add(E o){}
public E get(int index){}
...
}
------------------------------------------------
2. 타입 변수 예제
class Ex{
public static void main(String[] args){
ArrayList<Tv> lst = new ArrayList<Tv>();
lst.add(new Tv());
lst.add(new Audio()); // 컴파일 에러
}
}
제네릭스 용어
- Box
: 제네릭 클래스, 'T의 Box' 또는 'T Box'라고 읽음 - T : 타입 변수 또는 타입 매개변수(T는 타입 문자(type))
- Box : 원시 타입(raw type)
class Box<T>{}
Box<String> b = new Box<String>(); // 생성할 때마다 다른 타입을 넣어줄 수 있음,
제네릭스 타입과 다형성
- 참조 변수와 생성자의 대입된 타입은 일치해야 함
ArrayList<Tv> lst = new ArrayList<Tv>(); // OK
ArrayList<Product> lst = new ArrayList<Tv>(); // ERROR , 불일치
- 제네릭 클래스간의 다형성은 성립(여전히 대입된 타입은 일치해야 함)
List<Tv> lst = new ArrayList<Tv>(); // OK 다형성, ArrayList가 List를 구현
List<Tv> lst = new LinkedList<Tv>(); // OK 다형성, linkedList가 List를 구현
- 매개변수의 다형성도 성립
class Product{}
class Tv extends Product{}
class Audio extends Product{}
ArrayList<Product> lst = new ArrayList<Product>();
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<Product> productLst = new ArrayList<Product>();
ArrayList<Tv> tvLst = new ArrayList<Tv>();
// ArrayList<Product> tvLst = new ArrayList<Tv>(); // 에러. 제네릭 대입 타입이 일치하지 않음
// List<Tv> tvLst = new ArrayList<Tv>(); // 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<Product> lst){
for (Product p : lst){
System.out.println(p);
}
}
}
> Tv@442d9b6e
> Audio@6996db8
Iterator
- 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
1. Iterator와 Iterator<E> 사용 예시
public interface Iterator{
boolean hasNext();
Object next();
void remove();
}
Iterator it = lst.iterator();
while(it.hasNext()){
Student s = (Student)it.next();
...
}
public interface Iterator<E>{
boolean hasNext();
E next();
void remove();
}
Iterator<Student> it = lst.iterator();
while(it.hasNext()){
Student s = it.next();
}
------------------------------------
2. Iterator<E>의 예제
class Ex{
public static void main(String[] args){
ArrayList<Student> lst = new ArrayList<Student>();
lst.add(new Student("자바",1,1));
lst.add(new Student("자바의 정석 기초편",1,2));
lst.add(new Student("자바의 정석 3편",2,1));
Iterator<Student> 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<K,V>
- 여러 개의 타입 변수가 필요한 경우, 콤마(,)를 구분자로 선언
1. 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){}
}
HashMap<String, Student> map = new HashMap<String, Student>(); // 생성
map.put("자바", new Student("자바", 1,1,100,100,100)); // 저장
------------------------------
2. HashMap<K,V> 예제
class Ex{
public static void main(String[] args){
HashMap<String, Student> m = new HashMap<>(); // JDK1.7부터 new HashMap<String, Student>(); -> 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<T extends Fruit>{
ArrayList<T> list = new ArrayList<T>();
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // OK
FruitBox<Toy> appleBox = new FruitBox<Toy>(); // error, Toy는 Fruit의 자손이 아님
--------------------------
2. 제한된 제네릭 예제
interface Eatable{}
class FruitBox<T extends Eatable> extends Box<T>{}
class Box<T>{
ArrayList<T> lst = new ArrayList<T>(); // 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<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
// FruitBox<Grape> grapeBox = new FruitBox<Apple>(); // error, 타입 불일치
// FruitBox<Toy>> toyBox = new FruitBox<Toy>(); // 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<Apple> appleBox = new Box<Apple>(); // apple 객체만 저장가능
Box<Grape> grapeBox = new Box<Grape>(); // grape 객체만 저장가능
--------------------------------------------
4. static멤버에 타입 변수 사용 불가 // 모든 인스턴스에 공통이기 때문에 3번이랑 충돌
class Box<T>{
static T item // error
static int compare(T t1, T t2){} // 에러
}
--------------------------------------------
5. 배열(또는 객체) 생성할 때, 타입 변수 사용불가. 타입 변수로 배열 선언은 가능
class Box<T>{
T[] itemArr; // OK, T타입의 배열을 위한 참조변수
...
T[] toArray(){
T[] tmpArr = new T [itemArr.length]; // error, 제네릭 배열 생성불가 -> new 다음에 T가 오면 X
}
}
와일드 카드 <?>
- 하나의 참조변수로 대입된 타입이 다른 객체를 참조 가능
ArrayList<? extends Product> lst = new ArrayList<Tv>(); // OK
ArrayList<? extends Product> lst = new ArrayList<Audio>(); // OK
ArrayList<Product> lst = new ArrayList<Tv>(); // 에러, 타입 불일치
<? extends T> : 와일드 카드의 상한 제한, T와 그 자손들만 가능
<? super ?> : 와일드 카드의 하한 제한, T와 그 조상들만 가능
<?> : 제한 없음, 모든 타입이 가능. <? extends Object>와 동일
- 메서드의 매개변수에 와일드 카드 사용
1. 메서드의 매개변수로 와일드 카드 사용 예시
static Juice makeJuice(FruitBox<? extends Fruit> box){
String tmp = "";
for(Fruit f: box.getList()){
tmp += f + " ";
}
return new Juice(tmp);
}
System.out.println(Juice.makeJuice(new FruitBox<Fruit>())); // Fruit 자손들은 다 사용 가능, 원래는 타입을 일치시켜줘야 하지만 와일드카드로 유연한 코드작성이 가능
System.out.println(Juice.makeJuice(new FruitBox<Apple>()));
---------------------------------------------------
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<? extends Fruit> box){
String tmp = "";
for(Fruit f : box.getList()){
tmp += f + " ";
}
return new Juice(tmp);
}
}
class Ex{
public static void main(String[] args){
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
FruitBox<Grape> grapeBox = new FruitBox<Grape>();
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<T extends Fruit> extends Box<T>{}
class Box<T>{
ArrayList<T> lst = new ArrayList<T>();
void add(T item){lst.add(item);}
T get(int i){return lst.get(i);}
ArrayList<T> getList(){return lst;}
int size(){return lst.size();}
public String toString(){return lst.toString();}
}
> Apple Grape Juice
> Apple Apple Juice
제네릭 메서드
- 제네릭타입이 선언된 메서드(타입 변수는 메서드 내에서만 유효)
static <T> void sort(List<T> lst, Comparator<? super T> c)
- 클래스의 타입 매개변수
와 메서드의 타입 매개변수 는 별개
class FruitBox<T>{
...
static <T> void sort(List<T> lst, Comparator<? super T> c)
...
}
- 메서드를 호출할 때마다 타입을 대입해야(대부분 생략가능)
FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
FruitBox<Apple> appleBox = new FruitBox<Apple>();
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // Juicer.<Fruit>
static <T extends Fruit> Juice makeJuice(FruitBox<T> box){
String tmp = "";
for (Fruit f : box.getList()) tmp += f + " ";
return new Juice(tmp);
}
- 메서드를 호출할 때 타입을 생략할 수 없는 경우, 클래스 이름 생략 불가
System.out.println(<Fruit>makeJuice(fruitBox)); // error, 클래스 이름 생략 불가
System.out.println(this.<Fruit>makeJuice(fruitBox)); // OK
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); // OK
제네릭 타입의 형변환
- 제네릭타입과 원시 타입간의 형변환은 바람직하지 않음(경고 발생)
- 권장은 제네릭은 제네릭타입에 맞게, 원시타입은 원시타입에 맞게 사용하자.
Box<Object> objBox = null;
Box box = (Box)objBox; // ok, 제네릭 타입 -> 원시 타입. 경고 발생
objBox = (Box<Object>)box; // ok, 원시 타입 -> 제네릭 타입. 경고발생
Box<String> strBox = null;
objBox = (Box<Object>)strBox; // error, Box<String> -> Box<Object>
strBox = (Box<String>)objBox; // error, Box<Object> -> Box<String>
- 와일드 카드가 사용된 제네릭 타입으로는 형변환 가능(생략가능)
- 와일드카드에서 명확한타입으로 바꿀 땐 형변환 생략불가
Box<Object> objBox = (Box<Object>)new Box<String>(); // error, 형변환 불가
Box<? extends Object> wBox = (Box<? extends Object>) new Box<String>(); // OK
Box<? extends Object> wBox = new Box<String>(); // 위 문장과 동일한 문장, 형변환이 있지만 생략한 것
제네릭 타입의 제거
- 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형변환을 넣음
- 하위 호환성의 이유(안정성)
- 제네릭 타입의 경계(bound)를 제거
class Box<T extends Fruit>{ void add(T item){} }
class Box{
void add(Fruit item){
}
}
<br>
2. 제네릭 타입 제거 후에 타입이 불일치하면 형변환 추가
```java
T get(int i){
return lst.get(i);
}
Fruit get(int i){
return (Fruit)lst.get(i);
}
- 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가
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);
}
<br>
<br>
### 열거형(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 |
열거형의 Class객체를 반환 |
String name() | 열거형 상수의 이름을 문자형으로 반환 |
int ordinal() | 열거형 상수가 정의된 순서를 반환(0부터 시작) |
T valueOf(Class |
지정된 열거형에서 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<? extends Annotation> 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<T> 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<Ex> 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에서는 제네릭스, 열거형, 애노테이션에 대한 정의 및 특성과 종류에 대해 배웠습니다.
댓글