티스토리 뷰

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>(); // 위 문장과 동일한 문장, 형변환이 있지만 생략한 것

제네릭 타입의 제거

  • 컴파일러는 제네릭 타입을 제거하고, 필요한 곳에 형변환을 넣음
    • 하위 호환성의 이유(안정성)
  1. 제네릭 타입의 경계(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);
}

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

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

<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 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<? 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에서는 제네릭스, 열거형, 애노테이션에 대한 정의 및 특성과 종류에 대해 배웠습니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함