자바의 정석 ch12. 지네릭스, 열거형, 애너테이션

1. 지네릭스(Generics)

1.1 지네릭스란?

  • 지네릭스: 다양한 타입의 객체를 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능
  • 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어듦

지네릭스 장점

  1. 타입 안정성 제공
  2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해짐

1.2 지네릭 클래스의 선언

class Box {
	object item;
    
    void setItem(Object item) {this.item = item;}
    Object getItem() {return Item;}
}

//지네릭 클래스로 변경→클래스 옆에 '<T>'를 붙이면 됨+Object를 T로 바꾸기
class Box<T> {
	T item;
    
    void setItem(T item) {this.item = item;}
    T getItem() {return item;}
}
  • T, E, K, V...: 타입변수(임의의 참조형 타입)
Box<String> b = new Box<String>(); //타입 T대신 실제 타입(String) 지정
b.setItem(new Object()); //에러. String이외의 타입 지정불가
b.setItem("ABC"); //String타입이므로 가능
String item = b.getItem();

지네릭스의 제한

  • 지네릭 클래스 Box의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다. 지네릭스는 이처럼 인스턴스 별로 다르게 동작하도록 하려고 만든 기능임.
Box<Apple> appleBox = new Box<Apple>(); //Apple객체만 저장가능
Box<Grape> grapeBox = new Box<grape>(); //Grape객체만 저장가능
  • 그러나 모든 객체에 대해 동일하게 동작해야하는 static멤버에 타입 변수 T를 사용할 수는 없음. T는 인스턴스변수로 간주되기 때문.
  • 지네릭 타입의 배열을 생성하는 것도 허용되지 않음. 배열 타입의 참조변수를 선언하는 것(ex. T[] itemArr)은 가능 but 'new T[10]'과 같이 배열을 생성하는 것은 안됨.

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

대입된 타입이 다르면 에러. 상속관계도 해당. 단, 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮음.
그리고 추정이 가능한 경우에는 타입을 생략할 수 있게됨.

Box<Apple> appleBox = new Box(); //가능!

1.4 제한된 지네릭 클래스

  • 타입 문자로 사용할 문자를 명시하면 한 종류의 타입만 저장할 수 있도록 제한할 수 있지만, 그래도 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없다.
  • 그렇다면 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한할 수 있는 방법은 없을까?
    A. 지네릭 타입에 extends를 사용하면 특정 타입의 자손들만 대입할 수 있다.
class FruitBox<T extends Fruit> { //fruit자손만 타입으로 지정가능
	ArrayList<T> list = new ArrayList<T>();

1.5 와일드 카드

  • 지네릭 타입이 다른 것만으로는 오버로딩이 성립되지 않음. 메서드 중복 정의로 적용됨.
  • 이를 해결하기 위해 고안된 것이 '와일드 카드'
<? extends T> //와일드 카드의 상한 제한, T와 그 자손들만 가능
<? super T> //와일드 카드의 하한 제한, T와 그 조상들만 가능
<?> //제한 없음, 모든 타입이 가능. <? extends Object>와 동일

1.6 지네릭 메서드

  • 메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 함.
static <T> void sort(List<T> list, Comparator<? super T> c)
  • 지네릭 클래스에 정의된 타입 매개변수
class FruitBox<T>

와 지네릭 메서드에 정의된 타입 매개변수

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

는 완전 별개의 것. 같은 타입의 문자 T를 사용한다 해도 같은 것이 아님.

  • 지네릭 메서드를 호출할 때, 대입된 타입을 생략할 수 없는 경우에는 참조변수나 클래스 이름을 생략할 수 없다.

1.7 지네릭 타입의 형변환

  • 지네릭 타입과 넌지네릭 타입간의 형변환은 항상 가능. 다만 경고 발생.
  • 대입된 타입이 다른 지네릭 타입 간에는 형변환이 불가능함.

1.8 지네릭 타입의 제거

  • 컴파일러는 지네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다. 즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없는 것이다. 이렇게 하는 주된 이유는 지네릭이 도입되기 이전의 소스 코드와의 호환성을 유지하기 위해서이다.
  • 기본적인 제거과정
  1. 지네릭 타입의 경계를 제거한다.
  2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환을 추가한다.

2. 열거형(enums)

2.1 열거형이란?

  • 자바의 '타입에 안전한 열거형'에서는 실제 값이 같아도 타입이 다르면 조건식의 결과가 false가 된다. 이처럼 값뿐만 아니라 타입까지 체크하기 때문에 타입에 안전하다.

2.2 열거형의 정의와 사용

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

//ex. 동서남북 4방향을 상수로 정의하는 열거형 Direction
enum Direction { EAST, SOUTH, WEST, NORTH }

2.3 열거형에 멤버 추가하기

  • 열거형 상수의 값이 불규칙적인 경우에는 다음과 같이 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적어주면 된다.
enum Direction {EAST(1), SOUTH(5), WEST(-1), NORTH(10);}
  • 열거형에 추상 메서드를 선언하면 각 열거형 상수가 이 추상 메서드를 반드시 구현해야 함.

2.4 열거형의 이해

enum Direction { EAST, SOUTH, WEST, NORTH }
  • 열거형 상수 하나하나가 Direction객체이다.

3. 애너테이션(annotation)

3.1 애너테이션이란?

  • 소스코드에 대한 문서를 따로 만들기보다 소스코드와 문서를 하나의 파일로 관리하는 것이 낫다고 생각. 그래서 소스코드의 주석에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 HTML문서를 생성해내는 프로그램을 만들어 사용했다.
  • 애너테이션: 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨 것.
  • 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다는 장점

ex.

@Test //이 메서드가 테스트 대상임을 테스트 프로그램에게 알린다.
public void method() {
	...
}

3.2 표준 애너테이션

  • 메타 애너테이션: 애너테이션을 정의하는데 사용되는 애너테이션의 애너테이션

@Override

  • 메서드 앞에만 붙일 수 있는 애너테이션, 조상의 메서드를 오버라이딩하는 것이라는 걸 컴파일러에게 알려주는 역할

@Deprecated

  • 더이상 사용되지 않는 필드나 메서드에 사용
  • 이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니 더이상 사용하지 않을 것을 권함

@functionalInterface

  • 함수형 인터페이스를 선언할 때, 이 애너테이션을 붙이면 컴파일러가 올바르게 선언했는지 확인하고 잘못된 경우에는 에러 발생

@SuppressWarnings

  • 컴파일러가 보여주는 경고메시지가 나타나지 않게 억제
  • 주로 사용되는 것은 "deprecation"('@Deprecated'가 붙은 대상을 사용해서 발생하는 경고), "unchecked"(지네릭스 타입을 지정하지 않았을 때 발생하는 경고), "rawtypes"(지네릭스를 사용하지 않아서 발생하는 경고), "varags"(가변인자의 타입이 지네릭 타입일 때 발생하는 경고)를 억제

@SafeVarags

  • 메서드에 선언된 가변인자의 타입이 non-reifiable타입일 경우, 해당 메서들르 선언하는 부분과 호출하는 부분에서 "unchecked"경고가 발생한다. 해당 코드에 문제가 없다면 이 경고를 억제하기 위해 '@SafeVarags'를 사용해야 함.
  • 생성자와 static이나 final이 붙은 메서드에만 붙일 수 있음. 즉, 오버라이드 될 수 있는 메서드에는 사용불가

3.3 메타 애너테이션

  • 애너테이션에 붙이는 애너테이션으로, 애너테이션을 정의할 때 애너테이션의 적용대상이나 유지기간 등을 지정하는데 사용

@Target

  • 애너테이션이 적용가능한 대상을 지정하는데 사용

@Retantion

  • 애너테이션이 유지되는 기간을 지정하는데 사용

@Documented

  • 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 함

@Inherited

  • 애너테이션이 자손 클래스에 상속되도록 함

@Repeatable

  • 여러번 붙일 수 있음

ex. @ToDo라는 애너테이션이 정의되어 있을 때 '@ToDo'를 여러 번 붙이는 것이 가능

@repeatable(ToDos.class) 
@interface ToDo {
	String value();
}

@ToDo("deleted test codes")
@ToDo("override inherited methods")
class MyClass{
	...
}

@Native

  • 네이티브 메서드에 의해 참조되는 상수필드에 붙이는 애너테이션
  • 네이티브 메서드: JVM이 설치된 OS의 메서드
  • 모든 클래스의 조상인 Object클래스의 메서드들은 대부분 네이티브 메서드

3.4 애너테이션 타입 정의하기

  • 직접 애너테이션을 만들어서 사용
interface 애너테이션 이름 {
	타입 요소이름(); //애너테이션의 요소 선언
    ...
}
  • @Override는 애너테이션, Override는 애너테이션의 타입인 것임.
@interface TestInfo {
	int count();
    String testedBy();
    String[] testTools();
    TestType testType();
    DateType testDate();
}

@TestInfo(
	count = 3,
    testedBy = "kim",
    testTools = {"JUnit", "AutoTester"},
    testType = TestType.FIRST,
    testDate = @DateTime(yymmdd = "160101", hhmmss = "235959")
)

public class NewClass {...}
  • 애너테이션의 각 요소는 기본값을 가질 수 있으며, 기본값이 있는 요소는 애너테이션을 적용할 때 값을 지정하지 않으면 기본값이 사용됨

java.lang.annotation.Annotation

  • 모든 애너테이션의 조상은 Annotation
  • 애너테이션은 상속이 허용되지 않으므로 아래와 같이 명시적으로 Annotation을 조상으로 지정할 수 없음
@interface TestInfo extends Annotation { //에러
	int count();
}

마커 애너테이션

  • 값을 지정할 필요가 없는 경우, 애너테이션의 요소를 하나도 정의하지 않을 수 있다. 이처럼 요소가 하나도 정의되지 않은 애너테이션을 마커 애너테이션이라고 함.
@Target (ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override{} //마커 애너테이션. 정의된 요소가 없음

애너테이션 요소의 규칙

  • 요소의 타입은 기본형, String, enum, 애너테이션, class만 허용
  • ()안에 매개변수를 선언할 수 없음
  • 예외를 선언할 수 없음
  • 요소를 타입 매개변수로 정의할 수 없음

좋은 웹페이지 즐겨찾기