Java 개념 정리III

6 분 소요

Java 개념 정리

지네릭스, 열거형, 애너테이션

지네릭스

1. 지네릭스(Generics)란?

  • 컴파일 타입을 체크해 주는 기능(compile-time type check) - JDK1.5
  • 객체의 타입 안정성을 높이고 형변환의 번거로움을 줄여줌 (하나의 컬렉션에는 대부분 한 종류의 객체만 저장)
  • 지네릭스의 장점: 타입 안정성을 제공한다, 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.

2. 지네릭 클래스의 선언

  • 클래스를 작성할 때, Object타입 대신 T와 같은 타입 변수를 사용
    class Box<T>{ //지네릭 타입 T를 선언
      T item;
    
      void setItem(T item){ this.item = item; }
      T getItem(){ return item; }
    }
    
  • 참조변수, 생성자에 T대신 실제 타입을 지정하면 형변환 생략가능
    Box<String> b = new Box<String>(); //타입 T 대신, 실제 타입을 지정
    b.setItem(new OBject());  //에러. String이외의 타입은 지정불가
    b.setItem("ABC");  //OK. String타입임로 가능
    String item = (String)b.getItem();  //형변환이 필요없음
    

    3. 지네릭스 용어

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

4. 지네릭스의 제약사항

  • static 멤버에는 타입 변수 T를 사용할 수 없다.
    class Box<T>{
      static T item; //에러
      static int compare(T t1, T t2){...} //에러
    }
    
  • 지네릭 타입의 배열 T[]를 생성하는 것은 허용되지 않는다.
    class Box<T>{
      T[] itemArr;  //OK. T타입의 배열을 위한 참조변수
      T[] toArray(){
        T[] tmpArr = new T[itemArr.length];  //에러. 지네릭 배열 생성불가
        return tmpArr;
      }
    }
    

5. 지네릭 클래스의 객체 생성과 사용

  • 지네릭 클래스 Box< T >의 선언
    class Box<T>{
      ArrayList<T> list = new ArrayList<T>();
      void add(T item){ list.add(item); }
      T get(int i){ return list.get(i); }
      ArrayList<T> getList(){ return list; }
      int size(){ return list.size(); }
      public String toString(){ return list.toString(); }
    }
    
  • Box< T >의 객체 생성. 참조변수와 생성자에 대입된 타입이 일치해야함
    Box<Apple> appleBox = new Box<Apple>();  //OK
    Box<Apple> appleBox = new Box<Graph>();  //에러. 대입된 타입이 다르다.
    Box<Fruit> appleBox = new Box<Apple>();  //에러. 대입된 타입이 다르다.
    
  • 두 지네릭 클래스가 상속관계이고, 대입된 타입이 일치하는 것은 OK.
    Box<Apple> appleBox = new FruitBox<Apple>(); //OK. 다형성
    Box<Apple> appleBox = new Box<>(); //OK. JDK1.7부터 생략가능
    
  • 대입된 타입과 다른 타입의 객체는 추가할 수 없다.
    Box<Apple> appleBox = new Box<Apple>();
    appleBox.add(new Apple()); //OK.
    appleBox.add(new Graph());  //에러. Box<Apple>에는 Apple객체만 추가가능
    

6. 제한된 지네릭 클래스

  • 지네릭 타입에 ‘extends’를 사용하면, 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.
    class FruitBox<T extends Fruit>{ //Fruit의 자손만 타입으로 지정가능
      ArrayList<T> list = new ArrayList<T>();
      void add(T item){ list.add(item); }
    }
    
  • add()의 매개변수의 타입 T도 Fruit와 그 자손 타입이 될 수 있다.
    FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
    fruitBox.add(new Apple()); //OK. Apple이 Fruit의 자손
    fruitBox.add(new Graph()); //OK. Grape가 Fruit의 자손
    
  • 인터페이스의 경우에도 ‘implements’가 아닌, ‘extends’를 사용
    interface Eatable{}
    class FruitBox<T extends Eatable>{...}
    class FruitBox<T extends Fruit & Eatable>{...}
    

7. 와일드 카드 ‘?’

  • 지네릭 타입에 와일드 카드를 쓰면, 여러 타입을 대입가능
  • 단, 와일드 카드에는 <? extends T & E>와 같이 ‘&’를 사용불가
    <? extends T> 와일드카드의 상한 제한. T와 그 자손들만 가능
    <? super T> 와일드카드의 하한 제한. T와 그 조상들만 가능
    <?> 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
    

8. 지네릭 메서드

  • 반환타입 앞에 지네릭 타입이 선언된 메서드
    static <T> void sort(List<T> list, Comparator<? super T> c)
    
  • 클래스의 타입 매개변수< T >와 메서드의 타입 매개변수 < T >는 별개
    class FruitBox<T>{
      static <T> void sort(List<T> list, Comparator<? super T> c){
      }
    }
    
  • 지네릭 메서드를 호출할 때, 타입 변수에 타입을 대입해야 한다. (대부분의 경우, 추정이 가능하므로 생략할 수 있음.)
    FruitBox<Fruit> fruitBox = new FruitBox<Fruit>();
    FruitBox<Apple> appleBox = new FruitBox<Apple>();
    
    System.out.println(Juicer.<Fruit>makeJuice(FuitBox));
    System.out.println(Juicer.makeJuice(appleBox));  //대입된 타입 생략가능
    

9. 지네릭 타입의 형변환

  • 지네릭 타입과 원시 타입간의 형변환은 불가능
    Box box = null;
    Box<Object> = null;
    
    box = (Box)objBox;  //ok. 지네릭 타입 -> 원시 타입. 경고 발생
    objBox = (Box<Object>)Book;  //ok. 원시타입 -> 지네릭 타입. 경고 발생
    
  • 와일드 카드가 사용된 지네릭 타입으로는 형변환 가능
    Box<? extends Object> wBox = new Box<String>();
    
    FruitBox<? extends Fruit> box = null;
    FruitBox<Apple> appleBox = (FruitBox<Apple>)box; //ok. 미확인 타입으로 형변환
    
  • < ? extends Object >를 줄여서 < ? >로 쓸 수 있다.
    Optional<?> EMPTY = new Opitonal<?>(); //에러. 미확인 타입의 객체는 생성불가
    Optional<?> EMPTY = new Optional<Object>();  //OK.
    Optional<?> EMPT = new Optional<>();  //OK. 위의 문장과 동일
    

10. 지네릭 타입의 제거

  • 컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣는다.
    1. 지네릭 타입의 경계(bound)를 제거
    2. 지네릭 타입 제거 후에 타입이 불일치하면, 형변환을 추가
    3. 와일드 카드가 포함된 경우, 적절한 타입으로 형변환 추가

열거형(enums)

1. 열거형이란?

  • 관련된 상수들을 같이 묶어 놓은 것. Java는 타입에 안전한 열거형을 제공
    class Card{
      enum Kind{ CLOVER, HEART, DIAMOND, SPADE } //열거형 Kind를 정의
      enum Value{ TWO, THREE, FOUR } //열거형 Value를 정의
    
      final Kind kind; //타입이 int가 아닌 kind임에 유의하자.
      final Value value;
    } 
    

2. 열거형의 정의와 사용

  • 열거형을 정의하는 방법: enum 열거형이름{상수명1, 상수명2, …}
  • 열거형 타입의 변수를 선언하고 사용하는 방법
    enum Direction{EAST, SOUTH, WEST, NORTH}
    class Unit{
      int x,y; //유닛의 위치
      Direction dir; //열거형을 인스턴스 변수로 선언
    
      void init(){
        dir = Direction.EAST; //유닛의 방향을 EAST로 초기화
      }
    }
    
  • 열거형 상수의 비교에 ==와 compareTo() 사용가능
    if (dir == Direction.EAST){
      x++;
    } else if (dir > Direction.WEST){ //에러. 열거형 상수에 비교연산자 사용불가
    } else if (dir.compareTo(Direction.WEST)>0){ compareTo는 가능
    }
    

3. 모든 열거형의 조상 - java.lang.Enum

  • 모든 열겨형은 Enum의 자손이며, 아래의 메서드를 상속받는다.
    Class<E> getDeclaringClass() - 열거형의 Class객체를 반환한다.
    String name() - 열거형 상수의이름을 문자열로 반환한다.
    int ordinal() - 열거형 상소가 정의된 순서를 반환한다.(0부터 시작)
    T valueOf(Class<T> enumType, String name) - 지정된 열거형에서 name과 일치하는 열거형 상수를 반환한다.
    
  • 컴파일러가 자동적으로 추가해 주는 메서드도 있다.
    static E values()
    static E valueOf(String name)
    Direction d = Direction.valueOf("WEST");
    

4. 열거형에 멤버 추가하기

  • 불연속적인 열거형 상수의 경우, 원하는 값을 괄호()안에 적는다.
    enum Direction{ EAST(1), 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); //에러. 열거형의 생성자는 외부에서 호출불가

5. 열거형의 이해

  • 열거형 Direction이 아래와 같이 선언되어 있을 때,
    enum Direction { EAST, SOUTH, WEST, NORTH }
    
  • 열거형 Direction은 아래와 같은 클래스로 선언된 것과 유사하다.
    class Direction{
      static final Direction EAST = new Direction("EAST");
      Static final Direction SOUTH = new Direction("SOUTH");
      static final Direction WEST = new Direction("WEST");
      static final Direction NORTH = new Direction("NORTH");
    
      private String name;
    
      private Direction(String name){
        this.name = name;
      }
    }
    

애너태이션(Annotation)

1. 애너테이션이란?

  • 주석처럼 프로그래밍 언어에 영향을 미치지 않으며, 유용한 정보를 제공
  • 에너테이션 사용 예
    @Test //이 메서드가 테스트 대상임을 프로그램에게 알린다.
    public void method(){
    }
    

2. 표준 애너테이션

  • Java에서 제공하는 애너테이션
    • @Override - 컴파일러에게 오버라이딩하는 메서드라는 것을 알린다.
    • @Deprecated - 앞으로 사용하지 않을 것을 권장하는 대상에 붙인다.
    • @SuppressWarnings - 컴파일러의 특정 경고메시지가 나타나지 않게 해준다.
    • @SafeVarargs - 제니릭스 타입의 가변인자에 사용한다 (JDK 1.7)
    • @FuctionalInterface - 함수형 인터페이스라는 것을 알린다 (JDK 1.8)
    • @Native - native메서드에서 참조되는 상수 앞에 붙인다 (JDK 1.6)

3. 메타 애너테이션

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

4. 애너테이션 타입 정의하기

  • 애너테이션을 직접 만들어 슬 수 있다.
    @interface 애너테이션 이름{
      타입 요소이름(); //애너테이션의 요소를 선언한다.
    }
    
  • 애너테이션의 메서드는 추상메서드이며, 애너테이션을 적용할 때 모두 지정해야 한다. (순서 상관없음)
    @interface TestInfo{
      int count();
      String testdBy();
      String[] testTools();
      TestType testType(); //enum TestType{FIRST, FINAL}
      DateTime testDate();  //자신이 아닌 다른 앤테이션(@DateTime)를 포함할 수 있다.
    }
    @interface Datetime{
      String yymmdd();
      String hhmmss();
    }
    

5. 애너테이션 요소의 기본값

  • 적용시 값을 지정하지 않으면, 사용될 수 있는 기본값 지정 가능(null제외)
    @interface Testinfo{
      int count() default 1; //기본값을 1로 지정
    }
    @TestInfo //@TestInfo(count=1)과 동일
    public class NewClass{...}
    
  • 요소의 이름이 value인 경우 생략할 수 있다.
    @TestInfo(5) //@TestInfo(value=5)와 통일
    public class NewClass{...}
    
  • 요소의 타입이 배열인 경우, 괄호{}를 사용해야 한다.
    @interface Testinfo{
      String[] info() default{"aaa","bbb"}; //기본값이 여러개인 경우. 괄호{} 사용
      String[] info2() default "ccc"; //기본값이 하나인 경우. 괄호 생략 가능
    }
    @TestInfo //@TestInfo(info={"aaa","bbb}, info2="ccc")와 동일
    @TestInfo(info2={}) //@TestInfo(info={"aaa","bbb"}, info2={})와 동일
    class NewClass{...}
    

6. 모든 애너테이션의 조상 - java.lang.annotation.Annotation

  • Annotation은 모든 애너테이션의 조상이지만 상속은 불가
    @interface TestInfo extends Annotation{ //에러. 허용되지 않는 표현
       int count();
       String testedBy();
    }
    
  • 사실 Annotation은 인터페이스로 정의되어 있다.
    package java.lang.annotation;
    public interface Annotation{ //Annotation자신은 인터페이스이다.
      bolean equals(Object obj);
      int hashCode();
      String toString();
        
      Class<? extends Annotation> annotationType(); //애너테이션이 타입을 반환
    }
    

7. 마커 애너테이션 - Marker Annotataion

  • 요소가 하나도 정의되지 않은 애너테이션
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.SOURCE)
    public @interface Override{} //마커 애너테이션. 정의된 요소가 하나도 없다.
    

8. 애너테이션 요소의 규칙

  • 애너테이션의 요소를 선언할 때 아래의 규칙을 반드시 지켜야 한다.
    • 요소의 타입은 기본형, String, enum, 애너테이션, Class만 허용됨
    • 괄호()안에 매개변수를 선언할 수 없다.
    • 예외를 선언할 수 없다.
    • 요소를 타입 매개변수로 정의할 수 없다.

태그:

카테고리:

업데이트: