[Java] Ch.12 제네릭스, 열거형, 애너테이션

yoons(이윤서)·2024년 7월 14일

[Java] 자바의 정석

목록 보기
12/14

👉🏻 이 글은 자바의 정석(3판) Chapter.12을 공부하고 적은 내용입니다.

📌 1. 제네릭스(Generics)

👉🏻 제네릭스(Generics)란?
다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능이다. 컴파일 시에 타입을 체크하기 때문에
1. 객체의 타입 안정성을 높이고
2. 형변환의 번거로움이 줄어든다. (코드가 간결해 진다.)

👉🏻 다룰 객체의 타입을 미리 멸시해 줌으로써 번거로운 형변환을 줄여준다.

제네릭 타입은 클래스와 메서드에 선언할 수 있다.

class Box<T> {	// 제네릭 타입 T 선언
	T item;
    void setItem(T item) { this.item = item; }
    T getItem() { return item; }
}

Box<String> b = new Box<String>();	// 제네릭 타입 호출
b.setItem("ABC");

위에서 T타입 변수 (타입변수는 T가 아닌 다른 것도 사용 가능)
Ex. ArrayList<E>, Map<K, V>

이들은 기호만 다를 뿐 '임의의 참조형 타입'을 의미한다는 것은 모두 같다.

  • Box<T> : 제네릭 클래스
  • T : 타입 변수, 타입 매개변수 (T는 타입 문자)
  • Box : 원시 타입 (raw type)
  • String : 대입된 타입 (매개변수화된 타입)

⚠️ 제네릭스의 제한

  1. static멤버에 타입변수 T를 사용할 수 없다.
    : static 멤버는 대입된 타입의 종류에 관계없이 동일한 것이어야 하기 때문.
	static T item;	// Error!
    static int compare(T t1, T t2) { ... }	// Error!
  1. 제네릭 타입의 배열 생성 불가능
    : new연산자 때문. 컴파일 시점에 T가 어떤 타입이 될지 전혀 알 수 없기 때문이다. instanceof도 마찬가지이다.
	T[] itemArr;	// OK. T타입의 배열을 위한 참조변수
    T[] toArray(){
    	T[] tmpArr = new T[itemArr.length];	// Error! 생성불가
        ...
        return tmpArr;
    } 

--> newInstance()와 같이 동적 객체 생성 메서드 이용, Object배열을 생성해서 복사한 다음 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(); }
  1. 참조변수와 생성자에 대입된 타입이 일치해야 한다. (상속관계도 X)
  2. 단, 제네릭 클래스의 타입이 상속관계에 있는 것은 괜찮다.
  3. JDK1.7부터는 추정이 가능한 경우 생성자에 타입 생략 가능.
  4. 대입된 타입과 다른 타입의 객체는 추가 불가능 (자손들은 가능)

- 제한된 제네릭스

👉🏻 자손타입으로 제한 가능

class Fruit<T extends Fruit> {
	ArrayList<T> list = new ArrayList<T>;
	...
}

⚠️ 인터페이스를 구현할 때 또한 implements가 아닌 extends를 사용한다.
여러 개를 구현할 때는 &기호로 연결.


- 와일드 카드

static Jucie makeJucie(FruitBox<Fruit> box) {
	...
}
static Jucie makeJucie(FruitBox<Apple> box) {
	...
}

⚠️ 위와 같이 오버로딩 하면 컴파일 에러가 발생한다. 제네릭 타입이 다른 것만으로는 오버로딩(new)이 성립하지 않는다. 위는 '메서드 중복 정의'이다.

----> "와일드 카드(?)" 고안

<? extends T> : 와일드카드 상한 제한 (T와 그 자손들)
<? super T> : 와일드카드 하한 제한 (T와 그 조상들)
<?> : 제한 없음. 모든 타입 가능 ( = <? extends Object> )

- 제네릭 메서드

메서드의 선언부에 제네릭 타입이 선언된 메서드를 제네릭 메서드라 한다.

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

클래스의 타입변수와 메서드의 타입변수는 완전히 별개의 것이다.

static 멤버에는 타입 매개변수를 사용할 수 없지만, 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능하다.

  • 복잡하게 선언된 제네릭 메서드
	public static <T extends Comparable<? super T>> void sort (List<T> list)

	// ⬇️ List <T>의 요소가 Comparable인터페이스를 구현한 것이어야 한다.
	public static <T extends Comparable<T>> void sort (List<T> list)
  1. 타입 T를 요소로 하는 List를 매개변수로 허용한다.
  2. 'T'는 Comparable을 구현한 클래스여야 하며
    'T' 또는 그 조상의 타입을 비교하는 Comparable이어야 한다.

📌 2. 열거형(enums)

👉🏻 열거형(enums)이란?
JDK1.5부터 새로 추가되었다.
열거형이 갖는 값뿐만 아니라 타입까지 관리. (타입에 안전한 열거형)
: 실제 값이 같아도 타입이 다르면 조건식의 결과가 false가 된다.

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

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 열거형이름 { 상수명1, 상수명2, ... }
	enum Kind	{ COLVER, HEART, DIAMOND, SPADE }
    enum Value	{ TWO, THREE, FOUR }
    
    final Kind kind;	// type이 int가 아님에 유의
    final Value value;
}

if(Card.Kind.CLOVER == Card.Value.TWO)	// false. 타입이 다르기 때문.
  • == 사용 가능 (static 상수의 값이 객체의 주소이고 이 값은 바뀌지 않기 때문)
  • 비교연산자 사용 불가능 ( -> compareTo() 사용 )
  • switch문의 조건식에도 열거형 사용
  • 열거형 상수 하나하나가 객체이다.

- 열거형에 멤버 추가

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이다.

- 열거형에 추상 메서드 추가하기

  • 열거형에 추상메서드를 선언하면 각 열거형 상수가 이 추상메서드를 반드시 구현해야 한다.

📌 3. 애너테이션(annotation)

👉🏻 애너테이션(annotation)이란?
애너테이션의 뜻은 주석, 주해, 메모이다.
미리 정의된 @태그들을 이용해서 주석 안에 정보를 저장하고, javadoc.exe라는 프로그램이 이를 읽어서 문서를 작성하는데 사용한다.
👉🏻 이를 이용하여 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 애너테이션이다.

JDK에서 제공하는 애너테이션은 java.lang.annotation 패키지에 포함되어 있다.

📖 표준 애너테이션

자바에서 기본적으로 제공하는 애너테이션은 몇 개 없다. 그나마 이들의 일부는 메타 애너테이션으로, 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션이다.

애너테이션설명
@Override컴파일러에게 오버라이딩하는 대상이라는 것을 알린다.
@Deprecated앞으로 사용할 것을 권장하는 대상에 붙인다.
@SuppressWarning컴파일러의 특정 경고가 나타나지 않게 해준다.
@FunctionalInterface함수형 인터페이스라는 것을 알린다.
@SafeVarargs제네릭스 타입의 가변인자에 사용.
@Nativenative메서드에서 참조되는 상수 앞에 붙인다.

@Override

컴파일러가 같은 이름의 메서드가 조상에 있는지 확인하고 없으면 에러 메세지를 출력한다. 이는 알아내기 어려운 실수를 미연에 방지해주므로 반드시 붙이는 것이 좋다.

📖 메타 애너테이션

애너테이션을 위한 애너테이션으로, 애너테이션을 정의할 때 애너테이션 적용대상(target)이나, 유지기간(retention)을 지정하는데 사용된다.

애너테이션설명
@Target애너테이션이 적용가능한 대상을 지정하는데 사용
@Documented애너테이션 정보가 작성된 문서에 포함되게 한다.
@Inherited애너테이션이 자손 클래스에 상속되도록 한다.
@Retention애너테이션이 유지되는 범위를 지정하는데 사용.
@Repeatable애너테이션을 반복해서 적용 가능하게 한다.
profile
개발공부하는 잠만보

0개의 댓글