TIL_230305_자바의 정석 복습_제네릭스, 열거형, 애너테이션

창고·2023년 3월 5일
0

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

1. 제네릭스(generics)

(1) 제네릭스, 제네릭 클래스의 선언

  • 제네릭스의 도입
    • JDK1.5에서 처음 도입되었으며 JDK1.8에 도입된 람다식만큼 큰 변화
  • 제네릭스 : 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입 체크를 해주는 기능
  • 제네릭스의 장점
    • 타입 안정성을 제공
    • 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해짐

(2) 제네릭 클래스의 객체 생성과 활용

  • 제네릭 클래스의 선언
    • T : 타입 변수, 임의의 참조형 타입이며 꼭 T가 아니어도 되며 각 타입 변수의 첫글자를 사용해도 된다
      (Key, Value일 경우 K, V, Element일 경우 E 등)
// 제네릭을 활용하지 않은 경우
class Box {
	Object item;
    
    void setItem(Object item) {
    	this.item = item;
    }
    Object getItem() {
    	return item;
    }
}

// 제네릭을 활용한 경우
class Box<T> // 제네릭 타입 T 선언
	T item;
    
    void setItem(T item) {
    	this.item = item;
    }
    Object getItem() {
    	return item;
    }
  • 제네릭스의 용어
class Box<T> {}

// Box<T> : 제네릭 클래스. T의 Box 또는 T Box로 읽음
// T : 타입 변수 또는 타입 매개 변수
// Box : 원시 타입 (raw type)
  • 제네릭스의 제한
  • 모든 객체에 대해 동일하게 동작해야 하는 static 멤버에 대해서는 타입 변수 T를 사용할 수 없음. T는 인스턴스 변수로 간주되기 때문
class Box<T> {
	static T item; // 에러
    static int compare(T t1, T t2...) {} // 에러
}
  • 제네릭 클래스의 객체 생성과 사용
    • 제네릭 클래스의 객체를 생성할 때엔 참조변수와 생성자에 대입된 타입이 일치해야 함
    • 상속 관계에 있다고 해도 에러가 발생함
    • 다만 두 제네릭 클래스의 타입이 상속 관계에 있고 대입된 타입이 같은 것은 괜찮음
Box<Apple> appleBox = new Box<Apple>(); // 통과
Box<Fruit> appleBox = new Box<Apple>(); // 에러. 상속 관계라도 타입이 일치하지 않음
Box<Apple> appleBox = new FruitBox<Apple>(); // FruitBox가 Box의 자손이라면 통과
  • 추정 가능한 경우 타입을 생략할 수 있음 (JDK1.7~)
Box<Apple> appleBox = new Box<Apple>();
Box<Apple> appleBox = new Box<>(); // JDK1.7부터 생략이 가능

(3) 제한된 제네릭 클래스

  • 제네릭 타입에 extends를 사용할 경우 특정 타입의 자손들만 대입할 수 있음
class FruitBox<T extends Fruit> { // Fruit의 자손만 타입으로 지정 가능

}

(4) 와일드 카드

  • 와일드 카드의 필요성
    • 아래와 같은 이슈가 있어 와일드 카드가 필요하게 됨
class Juicer
	static Juice makeJuice(FruitBox<Fruit> box) { // Fruit를 담은 객체를 매개변수로 사용
    	.....
    }
    
    static Juice makeJuce(FruitBox<Apple> box) {}
    
// Juicer 클래스는 제네릭 클래스가 아님
// 제네릭 클래스라고 하더라도 static 메서드에는 타입 매개변수인 T를 사용할 수 없음
// 이 경우 제네릭 타입을 제거하고 특정 타입을 지정해줘야 함
// 그러나 컴파일 과정에서 에러가 발생함. 제네릭 타입이 다르더라도 오버로딩이 성립하지 않음
// -> 제네릭 타입은 컴파일러가 컴파일 시에만 사용하고 제거하므로 결국 동일 메서드 중복 정의로 판단함
  • 와일드 카드
    • <? extends T> : 와일드 카드의 상한 제한. T와 그 자손들만 사용 가능
    • <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 사용 가능
    • <?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일
class Juicer
	static Juice makeJuice(FruitBox<? extends Fruit> box) { // Fruit와 그 자손들을 사용할 수 있음
    	.....
    }

(5) 제네릭 메서드

  • 제네릭 메서드 : 메서드의 선언부에 제네릭 타입이 선언된 메서드
    • static 멤버에 타입 변수를 사용할 수는 없으나 메서드에 제네릭 타입을 선언하고 사용하는 것은 가능
    • 메서드에 선언된 제네릭 타입은 지역 변수를 선언한 것과 같다고 생각하면 됨
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 앞에서의 예제를 제네릭 메서드로 바꿀 경우
class Juicer
	static <T extends Fruit> Juice makeJuice(FruitBox<T> box) { // Fruit와 그 자손들을 사용할 수 있음
    	.....
    }

(6) 제네릭 타입의 형변환, 제거

  • 제네릭 타입의 형변환
    • 제네릭 타입과 원시 타입 간의 형 변환은 항상 가능. 다만 경고만 발생할 뿐
    • 단, 대입된 타입이 다른 제네릭 타입 간에는 형변환 불가
Box box = null;
Box<Object> objBox = null;
Box<String> strBox = null;

box = (Box) objBox; // 제네릭 -> 원시 타입 가능, 경고 발생
objBox = (Box<Object>) box; // 원시 타입 -> 제네릭 가능, 경고 발생
objBOx = (Box<Object>) strBox; // 불가능, 대입된 타입이 다름
  • 제네릭 타입의 제거
    • 컴파일러는 제네릭 타입을 이용해 소스파일을 체크하고 필요한 곳에 형변환을 넣어준 후, 제네릭 타입을 제거
    • 따라서 컴파일 된 파일(.class) 에는 제네릭 타입에 대한 정보가 없음

2. 열거형(enums)

(1) 열거형의 정의와 사용

  • 열거형 : 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용
  • 열거형의 정의
enum 열거형 이름 {상수명1, 상수명2}

public enum PowerRangers {RED, BLUE, GREEN, YELLOW, PINK}
  • 열거형에 멤버, 추상 메서드 추가
public enum PowerRangers {
	RED(1), BLUE(2), GREEN(3), YELLOW(4), PINK(5);
    
    private final int id;
    
    PowerRangers(int id) {
    	this.id = id;
    }
    
    int getId() {
    	return id;
    }
    
    abstract void signal(); // 추상 메서드
    
}

(2) 열거형의 이해

  • 열거형 상수 하나하나가 열거형 객체이며, static이다 (위에서는 PowerRangers 객체)
  • 일반 클래스로 바꾸면 다음과 같다
class PowerRangers {
	static final PowerRangers RED = new PowerRangers("RED");
    ...
    
    private String name;
    private PowerRangers(String name) {
    	this.name = name;
    }

}

3. 애너테이션(annotation)

(1) 애너테이션의 정의

  • 애너테이션의 기원
    • 소스 코드와 문서를 하나의 파일로 관리하는 것이 낫다고 생각해서 만들어진 주석 /** ~ */ -> 해당 주석에 소스코드에 대한 정보를 저장하고 주석으로부터 HTML 문서를 생성해내는 javadoc.exe를 만들어서 사용
    • 해당 기능을 응용해서 프로그램의 소스코드 안에 다른 프로글매을 위한 정보를 미리 약속된 형식으로 포함시킨 것이 바로 애너테이션
  • 애너테이션의 특징
    • 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점이 있음
    • JDK에서 기본적으로 제공하는 애너테이션과 다른 프로그램에서 제공하는 것이 있음
    • JDK에서 제공하는 표준 애너테이션은 주로 컴파일러를 위한 것
    • 추가로 새로운 애너테이션을 정의할 때 사용하는 메타 애너테이션이 존재

(2) 표준 애너테이션

  • @Override : 컴파일러에게 오버라이딩하는 메서드라는 것을 알림
  • @Deprecated : 앞으로 사용하지 않을 것을 권장하는 대상에 붙임
  • @SupperessWarnings : 컴파일러의 특정 경고 메시지가 나타나지 않게 해줌
  • @SafeVarargs : 제네릭스 타입의 가변인자에 사용 (JDK1.7)
  • @FunctionalInterface : 함수형 인터페이스라는 것을 알림 (JDK1.8)
  • @Native : native 메서드에 참조되는 상수 앞에 붙임 (JDK1.8)

(3) 메타 애너테이션

  • @Target : 애너테이션이 적용가능한 대상을 지정하는데 사용
  • @Documented : 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 함
  • @Inherited : 애너테이션이 자손 클래스에 상속되도록 함
  • @Retention : 애너테이션이 유지되는 범위를 지정하는데 사용
    • SOURCE : 소스 파일에만 존재, 클래스 파일에는 존재하지 않음
    • CLASS : 클래스 파일에 존재, 실행 시에 사용 불가, 기본값
    • RUNTIME : 클래스 파일에 존재, 실행시에 사용 가능
  • @Repeatable : 애너테이션을 반복해서 적용할 수 있게 함 (JDK1.8)

(4) 애니테이션 타입 정의하기

  • 애너테이션 정의하기
@interface 애너테이션 이름 {
	타입 요소 이름(); // 애너테이션의 요소를 선언, 추상 메서드의 형태를 가짐, 상속을 통해 구현하지 않아도 됨
}
  • 애너테이션의 요소
    • 애너테이션의 요소는 추상 메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 됨. 다만 애너테이션 적용 시 이 요소들의 값을 빠짐없이 지정해줘야 함. 순서는 상관 없음.
    • 요소는 null을 제외한 기본값을 가질 수 있음
@interface TestInfo {
	int count() default 1; // 기본값을 지정
    String testedBy();
    String[] testTools();
}
  • 마커 애너테이션
    • 값을 지정할 필요가 없는 경우 요소를 하나도 정의하지 않을 수 있음
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {}
  • 애노테이션 요소의 규칙
    • 요소의 타입은 기본형, StRING, enum, 애너테이션, Class만 허용
    • () 안에 매개변수를 선언할 수 없음
    • 예외를 선언할 수 없음
    • 요소를 타입 매개변수로 정의할 수 없음
profile
공부했던 내용들을 모아둔 창고입니다.

0개의 댓글