지네릭스, 열거형, 애너테이션
(generics, enumeration, annotation)
1. 지네릭스
컴파일할 때 타입을 체크해주는 기능을 ‘지네릭스’라고 한다.
지네릭스는 객체의 타입을 체크하기 때문에
객체 타입 안정성을 높이고 형변환의 번거로움이 줄여준다

또한, 저장된 객체를 꺼낼 때 형변환이 필요없어 편리하다.
왜냐하면 이미 어떤 타입인지 알고 있기 때문이다.
따라서 지네릭스의 장점은
1. 타입 안정성을 제공한다.
- 의도하지 않은 타입의 객체 저장을 막고, 저장된 객체를 꺼낼 때
원래의 타입과 다른 타입으로 형변환되는 오류를 줄여준다.
2. 타입체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
1) 타입 변수


ArrayList클래스에서 ‘< >’ 안에 있는 E를 타입변수라고 한다.
일반적으로 T를 사용하는데, T가 아닌 것을 사용해도 된다.
ArrayList<E>의 경우, Element(요소)의 첫 글자를 따서 타입 변수의 이름으로 E를 사용한다.
또한, 타입 변수가 Map<K,V> 처럼 여러개라면 콤마를 통해 구분한다.
2) 지네릭스 용어 설명
Box<Tv> : 지네릭 클래스. T의 박스 또는 T BOX라고 한다.
T : 타입 변수 또는 타입 매개변수(T는 타입 문자)
Box : 원시타입

타입문자 T를 타입 변수 또는 타입 매개변수라고 하는데,
메서드의 매개변수와 유사한 면이 있기 때문이다.
따라서 타입 매개변수에 타입을 지정하는 것을 지네릭 타입 호출이라고 하며,
지정된 타입 String을 매개변수화된 타입이라고 한다. 혹은 대입된 타입이라고 한다.
예를 들어 Box<String>과 Box<Integer>는
지네릭 클래스 Box<T>에 서로 다른 타입을 대입하여 호출한 것일 뿐,
이 둘이 별개의 클래스를 의미하는 것은 아니다.
add(3, 5)와 add(2,4)가 서로 다른 메서드를 호출한 게 아닌 것처럼 말이다.
컴파일 후, Box<String>과 Box<Integer>는 이들의 원시타입인 Box로 바뀐다. 즉,
지네릭 타입이 제거된다.
3) 지네릭 타입과 다형성
① 참조변수와 생성자의 대입된 타입이 반드시 일치해야 한다.
ArrayList<Tv> list = new ArrayList<Tv>(); // Ok. 일치
ArrayList<Product> list = new ArrayList<Tv>(); //에러!! 불일치
Class Product {}
Class Tv extends Product { }
이처럼 Product와 Tv가 상속관계더라도,
참조변수와 생성자의 대입된 타입은 반드시 일치해야 한다.
② 지네릭 클래스간의 다형성은 성립한다.
(대입된 타입은 여전히 일치해야 한다.)
List<Tv> list = new ArrayList <Tv>(); //ArrayList가 List 인터페이스를 구현
List<Tv> list= new LinkedList <Tv>() //LinkedList가 List 인터페이스를 구현
③매개변수의 다형성도 성립한다.
ArrayList<Product> list = new ArrayList<Product>();
list.add( new Product());
list.add( new Tv()); //Ok
list.add( new Audio()); Ok
단, 메서드에서 저장된 객체를 꺼낼 때 형변환이 필요하다.
E get( int index){} 에서 대입된 타입이 Product 타입이다.
따라서,
Product p = list.get(0); //형변환 불필요
Tv t = list.get(1); // Product 의 자손 객체들은 형변환 필요

4) Iterator<E>

5) HashMap<K,V>
만약 키(key)의 타입이 String 이고,
value의 타입이 Student인 HashMap을 생성하면 아래와 같다.
HashMap <String, Student> map = new HashMap <String, Student> (); //생성
map.put(“자바왕”, new Student(“자바왕”, 1, 1, 100, 100, 100)); //데이터 저장
구체적으로, 아래와 같은 소스 코드를 확인할 수 있다.
public class HashMap extends AbstractMap {
…
public Student get(Object key) {....}
public Student put(String key, Student value) {...}
public Student remove(Object key) {..}
}
따라서 HashMap에서 값을 꺼내오는 get(Object key)를 사용할 때,
그리고 저장된 키와 값을 꺼내오는 keySet()과 values()를 사용할 때
형변환하지 않아도 된다.
6) 제한된 제네릭 클래스
타입 문자로 사용할 타입을 명시하면 한 종류의 타입만 저장하도록 제한할 수 있다.
그렇지만 여전히 모든 종류의 타입을 지정할 수 있다는 것에는 변함이 없다.
이때, 매개변수 T에 지정할 수 있는 타입의 종류를 어떻게 제한할 수 있을까?
extends로 대입할 수 있는 타입을 제한한다.
class FruitBox <T extends Fruit> { //Fruit과 Fruit의 자손만 대입 가능
ArrayList<T> list = new ArrayList<T>();
…
}
FruitBox <Apple> appleBox = new FruitBox <Apple>(); //Ok
FruitBox <Toy> toyBox = new FruitBox <toyBox>(); //에러. Toy는 Fruit 자손 아님
만약 클래스가 아닌, 인터페이스를 구현해야 한다는 제약이 필요하다면
이때도 ‘extends’를 사용한다. implements를 사용하지 않는 것에 주의하자.
interface Eatable {}
class FruitBox < T extends Eatable> {...}
클래스 Fruit의 자손이면서,
인터페이스 Eatable를 구현한다면 다음처럼 &를 활용한다.
class FruitBox <T extends Fruit & Eatable> {...}


7) 지네릭스의 제약
지네릭 클래스 Box를 생성할 때, 객체별로 다른 타입을 지정하는 것은 가능하다.
Box<Apple> appleBox = new Box<Apple>(); // ok. Apple 객체만 저장 가능
Box<Grape> grapebox= new Box<Grape>(); // ok. Grape 객체만 저장 가능
① 그러나, 모든 객체에 동일하게 동작해야하는 static 멤버에 타입 변수 T를 사용할 수 없다.
T는 인스턴스 변수로 간주되는데, static 멤버는 인스턴스 변수를 참조할 수 없다.
static 멤버는 대입된 타입의 종류와 관계 없이 동일한 것이어야 한다.
② 또한, 지네릭 타입의 배열을 생성할 수 없다.
지네릭 배열을 생성할 수 없는 것은 new 연산자 때문이다.
new 연산자는 컴파일하는 시점에 타입 T가 무엇인지 정확히 알아야 하기 때문이다.
마찬가지로 instanceof 연산자도 T를 피연산자로 사용할 수 없다.
8) 와일드 카드
제네릭 클래스를 생성할 때, 참조변수와 생성자에 지정된 지네릭 타입은 일치해야 한다.
일치하지 않으면 컴파일 에러가 발생한다. 상속 관계가 적용되는 클래스끼리라도 마찬가지이다.
그렇다면, 지네릭 타입에 다형성을 적용할 방법은 없을까?
와일드 카드를 사용하면 된다.
와일드 카드
< ? extends T> 와일드 카드의 상한 제한. T와 그 자손들만 가능
< ? super T> 와일드 카드의 하한 제한. T와 그 조상들만 가능.
< ? > 제한 없이 모든 타입 가능. < ? extends Object>와 동일
와일드 카드 → 하나의 참조변수로 다른 지네릭 타입이 저장된 객체를 다룰 수 있다.
Tv와 Audio가 Product의 자손이라고 가정하자.
ArrayList < ? extends Product> list = new ArrayList<Tv>(); //ok
ArrayList < ? extends Product> list = new ArrayList<Audio>(); //ok
와일드 카드를 통해 메서드의 매개 변수 다형성에도 적용 가능하다.


9) 지네릭 메서드
메서드의 선언부에 지네릭 타입이 선언된 메서드 → 지네릭 메서드
선언 위치는 반환 타입 바로 앞이다.
Class FruitBox <T> {
…
static <T> void sort(List<T>, Comparator < ? super T> c {
…
}
}
지네릭 클래스에 정의된 타입 매개변수가 T인 것과
지네릭 메서드에 정의된 타입 매개변수가 T라도 전혀 별개의 것이다.
타입 문자만 같을 뿐, 서로 다른 것이다.
그리고 sort()가 static 메서드라는 것에 주목하자.
static 메서드에는 타입 매개변수를 사용할 수 없는데,
이처럼 지네릭 타입을 선언하고 사용하는 것은 가능하다.
10) 지네릭 타입의 형변환
원시 타입과 지네릭 타입의 형변환 → 가능.경고 발생
서로다른 지네릭 타입간의 형변환 → 에러.
2. 열거형(enum)
여러 상수를 정의할 때 편리하게 사용하는 것 —> 열거형

이럴 때, 열거형을 이용하면 간단하게 상수를 선언할 수 있다.

위와 달리 값을 지정하지 않아도 자동적으로 0으로 시작하는 정수값이 할당된다.
이전 코드에서는
if(Card.CLOVER==Card.TWO) //true
그러나 열거형을 통해 상수를 정의하는 경우
값을 비교하기 전에 ‘타입’을 먼저 비교하게 된다.
이로 인해 값이 같더라도 타입이 다르다면 컴파일 에러가 발생한다.
if(Card.CLOVER==Card.TWO) // 컴파일 에러. 타입이 달라서 비교 불가
1) 열거형의 정의와 사용
① 열거형 정의 : enum 열거형이름 {상수명1, 상수명2, 상수명3, ….}
예를 들어 동서남북 4방향을 상수로 정의하는 열거형 Direction은 다음과 같다.
enum Direction {EAST, SOUTH, WEST, NORTH}
② 열거형 사용 : ‘열거형이름.상수명’
Class Unit {
int x, y; //유닛의 위치
Direction dir; // 열거형 인스턴스 변수를 선언
void init() {
dir = Direction.EAST; // Unit의 방향을 동쪽으로 초기화
}
}
③ 열거형 상수간 비교
equals() 가 아닌 ‘==’으로 비교 가능하다.
‘<’ 혹은 ‘>’와 같은 비교 연산자는 사용할 수 없다.
단, comparTo()는 사용 가능하다.
2) 열거형의 조상 - java.lang.Enum
모든 열거형의 조상은 java.lang.Enum이다.
이 클래스는 다음과 같은 메서드를 제공한다.
메서드 | 설명 |
Class<E> getDeclaringClass() | 열거형의 Class객체를 반환한다 |
String name() | 열거형 상수의 이름을 문자열로 반환 |
int ordinal() | 열거형 상수가 정의된 순서를 반환(0부터 시작) |
T valueOf(Class<T> enumType, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수를 반환 |
컴파일러가 자동으로 추가해주는 메서드가 더 있다.
values()는 열거형 Direction에 정의된 모든 상수를 출력하는 데 사용된다.
->enum 타입의 배열로 리턴한다.

valuesOf()는 열거형 상수의 이름으로 문자열 상수에 대한 참조를 얻을 수 있다.

2-2) 열거형에 멤버 추가하기
Enum 클래스에 정의된 ordinal()이 열거형 상수가 정의된 순서를 반환하지만,
이 값을 열거형 상수의 값으로 사용하지 않는 게 좋다.
이 값은 내부적인 용도로 사용되기 위한 것이기 때문이다.
열거형 상수가 불규칙한 경우 아래와 같이 이름 옆에 원하는 값을 괄호()와 함께 작성하면 된다.
enum Direction {EAST(1), SOUTH(5), WEST(-1), NORTH(10) }
그리고 지정된 값을 저장할 수 있는 인스턴스 변수와 생성자를 새로 추가해야 한다.
이때, 주의할 점은 열거형 상수를 모두 정의한 후, 다른 멤버를 추가해야 한다는 것이다.
또한 열거형 상수의 마지막에 세미클론 ; 도 꼭 작성해야 한다.

열거형 생성자를 추가했지만, 열거형 생성자는 외부에서 호출 불가하다.
열거형 생성자는 제어자가 암묵적으로 private이기 때문이다.


3.애너테이션
애너테이션은 주석(comment)처럼 프로그래밍 언어에 영향을 미치지 않으면서,
다른 프로그램에게 유용한 정보를 제공할 수 있는 것
예를 들어, 자신이 작성한 소스코드 중에서 특정 메서드만 테스트를 하고 싶다.
다음과 같이 @Test라는 애너테이션을 메서드 앞에 붙인다.
@Test는 이 메서드가 테스트해야 한다는 것을 알릴 뿐, 프로그램 자체에 영향을 주지 않는다.
1) 애너테이션 구분
① @Override
메서드 앞에만 붙일 수 있는 애너테이션
조상의 메서드를 오버라이딩하는 것을 컴파일러에게 알려준다.
만약 @Override를 메서드에 붙이면,
컴파일러가 같은 이름의 메서드가 조상에 있는지 확인하고 없으면 에러메시지를 출력한다.
② @Deprecated
더이상 사용되지 않는 필드나 메서드에 @Deprecated 를 붙인다.
이 애너테이션이 붙은 대상은 다른 것으로 대체되었으니
더 이상 사용하지 않을 것을 권장한다는 의미이다.
따라서 @Deprecated 이 붙은 것들 가능한 한 사용하지 않는다.
③ @FuctionalInterface
함수형 인터페이스를 선언할 때, 이 애너테이션을 붙이면
컴파일러가 함수형 인터페이스를 올바르게 선언했는지 확인한다.
잘못된 경우 에러를 발생시킨다.
④ @SuppressWarnigs
컴파일러의 경고 메시지가 나타나지 않도록 억제한다.
2) 메타 애너테이션
메타 애너테이션 —> 애너테이션을 위한 애너테이션
즉, 애너테이션에 붙이는 애너테이션으로써
애너테이션의 적용대상이나 유지 기간등을 지정하는데 사용된다.
애너테이이션 | 설명 |
@Target | 애너테이션이 적용가능한 대상을 지정하는데 사용 |
@Documented | 애너테이션 정보가 javadoc으로 작성된 문서에 포함되게 한다. |
@Inherited | 애너테이션이 자손클래스에게도 상속되도록 한다. |
@Retention | 애너테이션이 유지되는 범위를 지정하는데 사용한다 |
@Repeatable | 애너테이션을 반복해서 적용할 수 있게 한다 |
'Java' 카테고리의 다른 글
Java 람다와 스트림 - (1) (0) | 2022.05.02 |
---|---|
Java 쓰레드 (0) | 2022.04.29 |
Java 컬렉션프레임웍 - (2) (0) | 2022.04.27 |
Java 컬렉션 프레임웍 - (1) (0) | 2022.04.26 |
Java 날짜와 시간 & 형식화 (0) | 2022.04.18 |