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

KOO HEESEUNG·2021년 10월 8일
0

Java의 정석

목록 보기
6/8
post-thumbnail

1. 지네릭스(Generics)

1-1. 지네릭스

컴파일할 때 타입 체크를 해주는 기능. 클래스명 옆에 <> 를 붙여서 특정한 타입의 객체만 담을 수 있도록 지정해주는 것.

타입 안전성을 높이고, 형변환을 생략할 수 있어 코드가 간결해진다는 장점을 갖는다.

1-2. 용어 정리

class Example<T> {}

지네릭스가 적용되어 있는 클래스를 지네릭 클래스라 하며, Example을 원시타입이라 한다.

<> 안의 T 를 타입 변수라고 하며, 객체를 생성할 때는 타입 변수에 실제 타입을 지정해주어야 한다. 타입 변수 대신 지정된 타입을 '대입된 타입(매개변수화된 타입)' 이라 한다.

1-3. 다형성

  1. 참조변수에서 지정한 지네릭 타입과 생성자에 지정한 지네릭 타입은 항상 일치해야 한다.

    Example<String> e = new Example<String>();
    Example<String> e2 = new Example<>(); // JDK 1.7부터 뒤에 지네릭 타입 생략 가능.
  2. 지네릭 타입이 아닌 클래스의 타입간 다형성은 적용 가능하다. 매개변수의 다형성 또한 성립한다.

    List<Fruit> list = new ArrayList<Fruit>();
    
    list.add(new Apple());
    list.add(new Orange());

1-4. 컬렉션

Iterator<E>

public interface Iterator<E> {
  boolean hasNext();
  E next();
  void remove();
}

Iterator에도 지네릭스가 적용되어 next() 를 사용할 때 형변환을 생략할 수 있다.

HashMap<K, V>

HashMap의 key와 value 의 타입을 지정해줄 수 있다. K에는 key의 타입을, V에는 value의 타입을 지정해준다.

get() , keySet() , values() 등을 사용할 때 형변환을 생략할 수 있다.

1-5. 제한된 지네릭 클래스

지네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있게 제한할 수 있다.

인터페이스를 구현해야 한다는 제약 또한 implements가 아닌 extends 를 사용한다.

class Example<T extends Fruit> {}

특정 타입의 자손이면서, 특정 인터페이스를 구현한 클래스로 제한하는 경우 '&' 를 사용하여 연결한다.

class Example2<T extends Fruit & Eatable> {}

1-6. 제약

  1. 타입 변수에 대입은 인스턴스마다 다르게 할 수 있다.
  2. static 멤버에는 모든 인스턴스에 공통이므로, 타입 변수를 사용할 수 없다.
  3. 지네릭 타입의 배열을 생성할 수 없다. 참조변수 선언만 가능.(new 연산자는 컴파일 시에 타입이 무엇인지 정확히 알아야 하기 때문)

1-7. 와일드 카드

지네릭 타입에 다형성을 적용할 수 있도록 하는 방법. ? 를 사용한다.

: T와 그 자손들만 대입 가능 : T와 그 자손들만 대입 가능 : 제한 없음. 와 동일

1-8. 지네릭 메서드

메서드 선언부에 지네릭 타입이 선언된 메서드.

static <T> void sort(List<T> list, Comparator<? super T> c)

아래의 예시에서와 같이 클래스의 타입 매개변수 <T> 와 메서드 타입 매개변수 <T> 는 타입문자만 같을 뿐, 다르다.

지네릭 메서드에서의 타입 매개변수는 메서드 영역에서만 유효하다. 때문에 원래 static 멤버에서는 타입 매개변수를 사용할 수 없지만, 메서드에 지네릭 타입을 선언하는 것은 가능하다.

class FruitBox<T> {
  	...
  static <T> void sort(List<T> list, Comparator<? super T> c) {
    ...
  }
}

메서드를 호출할 때마다 타입을 대입해야 하지만, 대부분 생략 가능하다.

만약 대입된 타입을 생략하지 않는 경우, 참조변수나 클래스 이름을 생략할 수 없다.

와일드 카드 vs 지네릭 메서드

와일드 카드 : 하나의 참조변수로 서로 다른 타입이 대입된 여러 지네릭 객체를 다루기 위한 것
지네릭 메서드 : 메서드를 호출할 때마다 다른 지네릭 타입을 대입할 수 있게 한 것

1-9. 지네릭 타입 형변환

  1. 지네릭 타입과 원시타입간의 형변환은 가능하지만, 바람직하지 않다.
  2. 서로 다른 타입이 대입된 지네릭 타입간의 형변환은 불가능하다.
  3. 와일드 카드가 사용된 지네릭 타입으로는 형변환이 가능하다.

1-10. 지네릭 타입 제거

컴파일러는 지네릭 타입을 제거하고, 필요한 곳에 형변환을 넣어준다.

왜? 하위 호환성 때문에. (지네릭스가 JDK 1.5부터 도입되었기 때문)

과정

  1. 지네릭 타입의 경계 제거

    지네릭 타입 <T> 가 Object로 치환된다. 아래와 같이 <T extends Fruit> 과 같이 제한을 주면, 해당 타입의 최고 조상인 Fruit으로 치환된다.

    class Box<T extends Fruit> {  void add(T t) {...}}

    ⇩⇩

    class Box {  void add(Fruit t) {...}}
  2. 타입이 일치하지 않으면, 형변환을 추가한다.

    T get(int i) {  return list.get(i);}

    ⇩⇩

    Fruit get(int i) {  return (Fruit) list.get(i);}

    와일드 카드가 포함되어 있으면, 적절한 타입으로 형변환이 추가된다.


2. 열거형(enum)

2-1. 열거형

관련된 상수를 같이 묶어놓은 것.

열거형을 정의하려면 다음과 같이 작성한다.

enum 열거형이름 {상수명1, 상수명2, ... }

열거형을 이용하면 자동적으로 정수값을 0부터 할당해주어 상수가 많은 경우, 간단하게 상수를 선언할 수 있다.

class Card {//  0       1      2       3  
enum Kind { CLOVER, HEART, DIAMOND, SPADE }  
enum Value { TWO, THREE, FOUR }    final Kind kind;  final Value value;}

열거형 상수간 비교에는 ==compareTo() 를 사용 가능하다. 비교 연산자(> , < ) 는 사용 불가.

Java에서는 열거형 상수의 값과 타입을 모두 체크하기 때문에 Kind.CLOVERValue.TWO 를 비교했을 때 값이 0으로 같아도 컴파일 에러가 발생한다.

2-2. 열거형의 조상 - java.lang.Enum

java.lang.Enum은 모든 열거형의 조상이다.

메서드

메서드설명
Class getDeclaringClass()열거형의 Class 객체 반환
String name()열거형 상수 이름을 문자열로 반환
int ordinal()열거형 상수가 정의된 순서 반환(0부터 시작)
T valueOf(Class enumType, String name)열거형 enumType 에서 name과 일치하는 열거형 상수 반환

컴파일러가 자동으로 추가해주는 메서드

메서드설명
values()열거형의 모든 상수 출력에 사용
valueOf(String name)열거형 상수의 이름으로 문자열 상수 참조 가능

2-3. 열거형에 멤버 추가

불연속적인 열거형 상수의 경우, 원하는 값을 괄호 안에 적는다. 괄호() 를 사용하려면 정수를 저장할 인스턴스 변수와 생성자를 새로 추가해줘야 한다.(열거형의 생성자는 제어자가 private)

enum Direction { EAST(1), SOUTH(5), WEST(-1), NORTH(10) }

3. 애너테이션(Annotation)

3-1. 애너테이션

주석처럼 프로그래밍 언어에 영향을 미치지 않지만, 유용한 정보를 제공하는 것.

애너테이션설명
@Override오버라이딩한 메서드인지 컴파일러가 체크하게 함
@Deprecated사용을 권장하지 않는 필드나 메서드에 붙임
@SuppressWarnings특정 경고메시지가 나오지 않도록 함
@SafeVarargs지네릭스 타입의 가변인자에 사용
@FunctionalInterface함수형 인터페이스임을 알림
@Nativenative 메서드에 참조되는 상수 앞에 붙임
↓ 아래는 표준 애너테이션애너테이션 만들 때 사용하는 애너테이션
@Target애너테이션이 어디에 적용되는지 지정
@Documented애너테이션 정보를 javadoc으로 작성된 문서에 포함시킴
@Inherited애너테이션을 상속하게 함
@Retention애너테이션 유지 범위 설정
@Repeatable같은 애너테이션을 여러번 사용할 수 있게 함. 이 애너테이션들을 하나로 묶을 컨테이너 애너테이션(value라는 이름의 배열 타입 요소 선언) 추가 정의해야 함.

3-2. 애너테이션 타입 정의

@interface 애너테이션이름 {  타입 요소이름();  ...}

3-3. 애너테이션의 요소

애너테이션 안에 선언된 메서드

애너테이션 요소는 추상 메서드로, 구현할 필요 없이 애너테이션을 적용할 때 지정해준다.

애너테이션 요소는 기본값을 가질 수 있다. 애너테이션 적용시에 값을 지정해주지 않으면 기본값이 사용된다.

ex)

@interface DateTime {
  String yymmdd() default "991231"
  String hhmmss();
}

@DateTime(yymmdd = "211012", hhmmss = "093021")public class Example {}

애너테이션 요소가 단 하나뿐이고, 요소 이름이 value인 경우, 요소를 지정할 때 이름을 생략할 수 있다.

@interface ExampleAnno {  String value();}

@ExampleAnno("hello")class Example {}

요소 타입이 배열일 경우, 괄호{ } 를 사용하여 여러 값을 지정할 수 있다. 값이 하나인 경우는 괄호{ }를 생략 가능하다.

마커 애너테이션

요소가 하나도 정의되지 않은 애너테이션. @Override가 대표적이다.

3-4. 모든 애너테이션의 조상

모든 애너테이션의 조상은 Annotation 이라는 인터페이스이다. 상속은 허용되지 않지만, Annotation 인터페이스에 정의된 메서드들을 호출 가능하다.

package java.lang.annotation;

public interface Annotation {
  boolean equals(Object obj);
  int hashCode();
  String toString();
  Class<? extends Annotation> annotationType();
}

3-5. 애너테이션 요소의 규칙

애너테이션 요소를 선언할 때, 반드시 지켜야 하는 규칙

  1. 요소의 타입은 기본형, String, enum, 애너테이션, Class 만 허용
  2. ( ) 안에 매개변수 선언 불가
  3. 예외 선언 불가
  4. 타입 매개변수로 정의 불가

다음과 같은 경우 에러가 발생한다.

@Interface WrongAnno {
  String test1(int i);
  String test2() throws Exception;
  ArrayList<T> list();
}

0개의 댓글