솜이의 데브로그

Chapter 12 ) 지네릭스, 열거형, 애너테이션 본문

책을 읽자/Java의 정석

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

somsoming 2021. 9. 17. 19:15

출처 : Java의 정석

 

1. 지네릭스 (Generics)

지네릭스는 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시의 타입체크를 해주는 기능이다.

객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.

 

지네릭스의 장점

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

 

지네릭 클래스의 선언

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

ex)

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

 

위의 예시에서 T를 '타입변수'라고 하며, Type의 첫 글자에서 따온 것이다.

상황에 따라 의미있는 문자로 선택해 사용가능하며, 이 기호들은 모두 '임의의 참조형 타입'을 의미한다.

위의 Box 클래스의 객체를 생성할 때는 참조변수와 생성자에 타입 T대신 사용될 실제 타입을 지정해야한다.

ex)

Box<String> b = new Box<String>();
b.setItem(new Object());  //Error. String 외의 타입은 지정 불가
b.setItem("ABC");
String item = b.getItem();

 

 

지네릭스의 용어

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

 

지네릭스의 제한

  • 지네릭 클래스의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다.
  • 그러나 static 멤버에 타입 변수 T를 사용할 수는 없다.
  • 지네릭 타입의 배열을 생성하는 것도 허용되지 않는다. (지네릭 배열 타입의 참조변수 선언은 가능)

 

 

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

객체를 생성할 때는 참조변수와 생성자에 대입된 타입이 일치해야한다.

ex)

Box<Apple> appleBox = new Box<Apple>();  //OK
Box<Apple> appleBox = new Box<Grape>();  //Error

 

두 타입이 상속관계에 있어도 마찬가지로 에러이다.

단, 두 지네릭 클래스의 타입이 상속관계에 있고, 대입된 타입이 같은 것은 괜찮다.

ex) FruitBox가 Box의 자손이라고 할 때

Box<Apple> appleBox = new FruitBox<Apple>();  //OK. 다형성

 

 

제한된 지네릭 클래스

class FruitBox<T extends Fruit>{  //Fruit의 자손만 타입으로 지정 가능
	ArrayList<T> list = new ArrayList<T>();
    ...
}

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

인터페이스를 구현해야하는 경우에도 extends를 사용한다. (implements 사용 안함!)

둘 다 구현해야하는 경우 & 로 연결.

 

 

 

와일드 카드

지네릭 타입을 다르게한다고해서 오버로딩이 되는것이 아니라, 컴파일 에러가 발생하여 '메서드 중복 정의'가 된다.

이럴 때 와일드 카드를 사용하는데, 기호 '?'로 표현하며 와일드 카드는 어떠한 타입도 될 수 있다.

 

  • <? extens T> : 와일드 카드의 상한 제한. T와 그 자손들만 가능
  • <? super T> : 와일드 카드의 하한 제한. T와 그 조상들만 가능
  • <?> : 제한 없음. 모든 타입이 가능. <? extends Object>와 동일

Comparator에 <? super T> 가 항상 습관적으로 따라붙는다.

 

 

 

지네릭 메서드

메서드의 선언부에 지네릭 타입이 선언된 메서드를 지네릭 메서드라 한다.Collections.sort() 가 지네릭 메서드이며, 지네릭 타입의 선언 위치는 반환 타입 바로 앞이다.

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

 

위의 코드에서 지네릭 클래스 FruitBox에 선언된 타입 매개변수 T와 지네릭 메서드 sort()에 선언된 타입 매개변수 T는 타입 문자만 같을 뿐 서로 다른 것이다.static 멤버에는 타입 매개변수를 사용할 수 없지만, 위의 예시처럼 메서드에 지네릭 타입을 선언하고 사용하는 것은 가능하다.지네릭 메서드는 매개변수의 타입이 복잡할 때도 유용하다.

 

 

 

지네릭 타입의 형변환

지네릭 타입과 non-generic 타입간의 형변환은 항상 가능하다.그러나 대입된 타입이 다른 지네릭 타입간에는 형변환이 불가능하다.

Box<Object> objBox = null;
Box<String> strBox = null;

objBox = (Box<Object>)strBox;  // 에러
strBox = (Box<String>)objBox;  //에러

 

매개변수에 다형성을 적용하는 것도 가능하다.ex)

Box<? extends Object> wBox = new Box<String>();

 

 

지네릭 타입의 제거

컴파일러는 지네릭 타입을 이용해 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준다. 그리고 지네릭 타입을 제거한다.

  1. 지네릭 타입의 경계(bound)를 제거한다.
  2. 지네릭 타입을 제거한 후에 타입이 일치하지 않으면 형 변환을 추가한다.

 

 

2. 열거형(enums)

열거형이란?

열거형은 서로 관련된 상수를 편리하게 선언하기 위한 것으로 여러 상수를 정의할 때 사용하면 유용하다.

class Card{
	enum Kind { CLOVA, HEART, DIAMOND, SPADE }
    enum Value { TWO, THREE, FOUR }
    
    final Kind kind;
    final Value value;
}

 

열거형 상수를 사용하면 기존의 소스를 다시 컴파일하지 않아도 된다.

 

 

열거형의 정의와 사용

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

위와 같이 괄호{} 안에 상수의 이름을 나열한다.

이 열거형에 정의된 이름을 사용하는 방법은 '열거형이름.상수명' 이다.

  • 열거형 상수간의 비교에는 '=='를 사용할 수 있다.
  • 그러나 >, < 같은 비교연산자는 사용할 수 없고 compareTo() 를 사용한다.

 

java.lang.Enum

  • values() 는 열거형의 모든 상수를 배열에 담아 반환한다.
  • ordinal() 은 모든 열거형의 조상인 java.lang.Enum클래스에 정의된 것으로, 열거형 상수가 정의된 순서를 정수로 반환한다.

 

열거형에 멤버 추가하기

열거형 상수의 값이 불연속적인 경우에는 다음과 같이 열거형 상수의 이름 옆에 원하는 값을 괄호()와 함께 적는다.

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

그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해야한다.

하나의 열거형 상수에 여러 값을 지정할 수도 있다. 이 때는 그에 맞게 인스턴스 변수와 생성자 등을 새로 추가해야한다.

 

 

열거형의 이해

열거형 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;
    }
}

 

Direction클래스의 static 상수 EAST, SOUTH, WEST, NORTH의 값은 객체의 주소이고, 이 값은 바뀌지 않는 값이므로 '=='로 비교가 가능하다.

 

 

3. 애너테이션 (annotation)

애너테이션이란?

소스코드와 문서를 하나의 파일로 관리. 소스코드의 주석 '/** ~ */' 에 소스코드에 대한 정보를 저장하고, 소스코드의 주석으로부터 HTML문서를 생성해내는 프로그램(javadoc.exe)을 만들어서 사용했다.

이 기능을 응용하여 프로그램의 소스코드 안에 다른 프로그램을 위한 정보를 미리 약속된 형식으로 포함시킨것이 애너테이션이다.

애너테이션은 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다.

 

 

표준 애너테이션

  • @Override : 메서드 앞에만 붙일 수 있는 애너테이션으로, 조상의 메서드를 오버라이딩 하는 것이라는 걸 컴파일러에게 알려준다.
  • @Deprecated : 더 이상 사용되지 않는 필드나 메서드에 붙인다. 이 애너테이션이 붙은 대상은 다른것으로 대체되었으니 더 이상 사용하지 않음을 권한다.
  • @FunctionalInterface : 함수형 인터페이스를 선언 시 이 애너테이션이 붙으면 컴파일러가 올바르게 선언했는지 확인하고, 잘못된 경우 에러를 발생시킨다.
  • @SuppressWarnings : 컴파일러가 보여주는 경고메시지가 나타나지 않게 억제해준다. 애너테이션 뒤에 괄호() 문자열로 지정.
  • @SafeVarargs : 메서드에 선언된 가벼인자의 타입이 non-reifiable타입일 경우 메서드를 선언하는 부분과 호출하는 부분에서 경고 발생. 이 경고를 억제하기 위해 사용한다.

 

메타 애너테이션

메타 애너테이션은 '애너테이션을 위한 애너테이션', 즉 애너테이션을 정의할 때 애너테이션의 적용대상이나 유지기간을 지정하는데 사용된다.

  • @Target : 애너테이션이 적용가능한 대상을 지정하는데 사용된다.
    @Target ({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface SuppressWarnings {
    	String[] value();
    }​
  • @Retention : 애너테이션이 유지되는 기간을 지정.
    - SOURCE, CLASS, RUNTIME
  • @Documented : 애너테이션에 대한 정보가 javadoc으로 작성한 문서에 포함되도록 한다.
  • @Inherited : 애너테이션이 자손 클래스에 상속되도록 한다. @Inherited가 붙은 애너테이션을 조상 클래스에 붙이면 자손 클래스도 이 애너테이션이 붙은것과 같이 인식된다.
  • Repeatable : 애너테이션을 여러번 붙일 수 있다.
  • @Native : 네이티브 메서드에 의해 참조되는 상수필드에 붙이는 애너테이션.

 

애너테이션 타입 정의하기

@기호를 제외하면 인터페이스를 정의하는 것과 동일하다.

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

 

애너테이션의 요소

  • 애너테이션 내에 선언된 메서드를 애너테이션의 요소라고 한다.
  • 반환값이 있고 매개변수는 없는 추상메서드의 형태를 가지며, 상속을 통해 구현하지 않아도 된다.
  • 애너테이션을 적용할 때 요소들의 값을 빠짐없이 지정해주어야한다. (순서 상관없음)
  • 기본값을 지정할 수 있으며, 애너테이션 적용 시 값을 지정하지 않으면 기본값 사용

 

java.lang.annotation.Annotation

모든 애너테이션의 조상은 Annotation이다. 그러나 애너테이션은 상속이 허용되지 않으므로 명시적으로 Annotation을 조상으로 지정할 수 없다.

 

마커 애너테이션 Marker Annotation

요소가 하나도 정의되지 않은 애너테이션을 마커 애너테이션이라 한다.

 

 

애너테이션 요소의 규칙

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

 

 

 

 

느낀점

뭔소린지 잘 모르겠다.... 지네릭스부터는 처음 배워보는 개념이라 뭔지 모르겠는데 어렵다ㅠ

뭣도 모르고 그냥 코딩할 때 애너테이션 보고 이건 뭐지 하고 넘겼었는데 그게 이거구나.. 정도ㅎㅎ 하다보면 알겠지..