[JAVA] 제네릭스(Generics) / 열거형 (Enum)
1) 제네릭스란?
: 다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시 타입 체크를 해주는 기능
→ 의도하지 않은 타입의 객체가 저장되는것을 막아주고, 저장된 객체를 꺼낼때 원래 타입과 다른 타입으로 잘못 형변환 되어 발생할 수 있는 오류를 줄여준다.
장점
- 타입 안정성 제공
- 타입체크와 형변환을 생략하여 코드가 간결해짐
2) 사용 방법
: 클래스와 메서드에 선언 가능
- 지네릭 클래스 예시
//일반 클래스
class Box {
Object item;
void setItem(Object itme) { this.item = item; }
Object getItem() { return item; }
}
//지네릭 클래스로 변경
class Box<T> { //지네릭 타입 T 선언
T item;
void setItem(T itme) { this.item = item; }
T getItem() { return item; }
}
클래스 옆에 ‘T’를 붙이고 ‘Object’를 전부 ‘T’로 바꾼다.
여기서 ‘T’는 타입변수이다. 타입변수(type variable) 의 첫 글자를 따온 것이고,
이 타입변수는 T가 아닌 다른 것을 사용해도 된다.
무조건 ‘T’를 사용하기보다 상황에 맞게 의미있는 문자를 선택해서 사용하자.
ex ) ArrayList<E> : Element, Map<K,V> : Key, Value
→ 즉, 기호의 종류만 다를 뿐 '임의의 참조형 타입'을 의미한다
ex) 지네릭클래스의 객체를 생성하는 방법
Box<String> b = new Box<String>(); //지네릭 클래스 사용시 타입 지정
b.setItem(new Object()); //Error, String 타입이 아님
b.setItem("ABC"); //OK
String item = (String)b.getItem(); //형변환 필요 X
- 참조 변수와 생성자의 대입된 타입은 일치해야한다.
- 지네릭 클래스간의 다형성은 성립한다. ( 대입된 타입은 일치해야함 )
- ex ) List<Product> list = new ArrayList<Product>();
- 매개변수의 다형성도 성립한다.
ex)
//Product가 조상 클래스이고, Tv, Audio가 자손 클래스 일때
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());
Product p = list.get(0); //형변환 필요 X
Tv t = (Tv) list.get(1); //타입이 다르므로 형변환 필요
📌 지네릭 클래스의 제한
extends로 대입할 수 있는 타입을 제한함
class FruitBox<T extends Fruit> { //Fruit의 자손만 타입으로 지정 가능
..
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); // Apple클래스가 Fruit클래스의 자손
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // Error
인터페이스의 경우에도 implements가 아닌 extends를 사용
interface Eatable {}
class FruitBox<T extends Eatable> { .. }
상속과 구현을 같이 할 경우 ‘&’ 사용
class Fruit implements Eatable {
public String toString() { return "Fruit"; }
}
class Apple extends Fruit { .. }
class Toy { .. }
interface Eatable {}
class Box<T> {
..
}
class FruitBox<T extends Fruit & Eatable> extends Box<T> {}
// 이 예제에서는 Fruit이 Eatable을 이미 구현하고 있기 때문에 다음과 같이 작성해도 상관X
class FruitBox<T extends Fruit> extends Box<T> {}
📌 지네릭 클래스의 제약
1.static 멤버에 타입변수 사용 불가 → static 멤버변수는 모든 인스턴스에 공통적으로 동작
class Box<T> {
static T item; //x
static int compare(T t1, T t2) { ... } //x
}
2.배열 생성 시 타입변수 사용 불가. 단, 타입 변수로 배열 선언은 가능
class Box<T> {
T[] itemArr; //가능
T[] toArray() {
T[] tepArr = new T[itemArr.length]; //Error
..
}
}
→ new연산자 때문. 해당 연산자는 컴파일 시점에 타입 T가 뭔지 정확히 알아야함
📌 와일드 카드
: 하나의 참조 변수로 대입된 타입이 다른 객체를 참조 가능
ArrayList<? extends Product> list = new ArrayList<Tv>();
ArrayList<? extends Product> list = new ArrayList<Audio>();
- <? extends T> : 와일드 카드의 상한 제한, T와 그 자손들만 가능
- <? super T> : 와일드 카드의 하한 제한, T와 그 조상들만 가능
- <?> : 제한 없음. 모든 타입 가능. <? extends Object>와 동일한 의미
메서드의 매개변수에 와일드 카드 사용 가능
class Juicer {
static Juice makeJuice ( FruitBox<? extends Fruit> box ) {
...
}
}
System.out.println(Juicer.makeJuice(new FruitBox<Fruit>());
System.out.println(Juicer.makeJuice(new FruitBox<Apple>());
3) 지네릭 메서드란?
: 메서드의 선언부에 지네릭 타입이 선언된 메서드. 선언 위치는 반환 타입 바로 앞
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) { ... }
}
메서드를 호출할 때마다 타입을 대입해 사용 ( 대부분 생략 가능 )
class Juicer {
static <T extends Fruit> Juice makeJuice(FruitBox<T> box) {
String tmp = "";
for(Fruit f : box.getList())
tmp += f + " ";
return new Juice(tmp);
}
}
System.out.println(Juicer.<Fruit>makeJuice(fruitBox)); //ok
System.out.println(Juicer.makeJuice(fruitBox)); //ok
System.out.println(this.<Fruit>makeJuice(fruitBox)); //ok
System.out.println(<Fruit>makeJuice(fruitBox)); // 클래스 이름 생략 불가
4) 지네릭 타입의 형변환
1.
Box box = null;
Box<Object> objBox = null;
box = (Box) objBox; // 지네릭 -> 원시
objBox = (Box<Object>) box; // 원시 -> 지네릭
→ 지네릭타입과 넌지네릭 타입간의 형변환은 경고가 발생하지만 가능함
2.
Box<Object> objBox = null;
Box<String> strBox = null;
objBox = (Box<Object>) strBox; // Error. Box<String> -> Box<Object>
strBox = (Box<String>) objBox; // Error. Box<Object> -> Box<String>
→ 대입된 타입이 다른 지네릭 타입간의 형변환은 불가능
3.
Box<? extends Object> box = new Box<String>(); //Box<String> -> Box<? extends Object>
→ 매개변수의 다형성 적용으로 가능
ex )
class Fruit { public String toString() { return "Fruit"; } }
class Apple extends Fruit { public String toString() { return "Apple"; } }
class Grape extends Fruit { public String toString() { return "Grape"; } }
static Juice makeJuice(FruitBox<? extends Fruit> box { ... }
FruitBox<? extends Fruit> box1 = new FruitBox<Fruit>();
FruitBox<? extends Fruit> box2 = new FruitBox<Apple>();
FruitBox<? extends Fruit> box3 = new FruitBox<Grape>();
🌟 반대로의 형변환도 성립하지만 확인되지 않은 형변환이라는 경고가 발생.
FruitBox<? extends Fruit> box = null;
FruitBox<Apple> appleBox = (FruitBox<Apple>) box;
4.
FruitBox<? extends Object> objBox = null;
FruitBox<? extends String> strBox = null;
strBox = (FruitBox<? extends String>) objBox;
objBox = (FruitBox<? extends Object>) strBox;
→ 와일드 카드가 사용된 지네릭타입끼리도 형변환 가능하지만 미확정 타입 형변환 경고 발생.
5) 열거형이란?
: 서로 관련된 상수를 편리하게 선언하기 위한 것.
C언어에서는 타입이 달라도 값이 같으면 조건식 결과가 참이었으나, Java의 열거형은 타입에 안전한 열거형이라서 실제 값이 같아도 타입이 다르면 컴파일 에러가 발생함.
ex)
class EnumExample {
private final static int MONDAY = 1;
private final static int TUESDAY = 2;
private final static int WEDNESDAY = 3;
private final static int THURSDAY = 4;
private final static int FRIDAY = 5;
private final static int SATURDAY = 6;
private final static int SUNDAY = 7;
public static void main(String[] args) {
int day = EnumExample.MONDAY;
switch (day) {
case EnumExample.MONDAY:
System.out.println("월요일 입니다.");
break;
case EnumExample.TUESDAY:
System.out.println("화요일 입니다.");
break;
case EnumExample.WEDNESDAY:
System.out.println("수요일 입니다.");
break;
}
}
}
Enum으로 다음과 같이 정의 가능
enum Week { MONDAY,TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY }
class EnumExample {
public static void main(String[] args) {
int day = Week.MONDAY;
switch (day) {
case MONDAY: //Week.Monday 로 쓰지않음.
System.out.println("월요일 입니다.");
break;
case TUESDAY:
System.out.println("화요일 입니다.");
break;
case WEDNESDAY:
System.out.println("수요일 입니다.");
break;
default:
System.out.println("Invalid value.");
}
}
}
- enum명은 클래스와 같이 첫 문자를 대문자로 나머지는 소문자로 정의
- 관례적으로 열거 상수는 모두 대문자로 작성
- 열거 상수가 여러 단어로 구성될 경우, 단어 사이를 밑줄 ‘_’ 로 연결
- 호출 시 ‘열거형이름.상수명’으로 사용
- 비교문 ‘==’ 사용은 가능하나 비교연산자 ‘<’, ‘>’ 등 사용 불가
- compareTo() 사용 가능
- 비교 대상이 같으면 0
- 왼쪽이 크면 양수
- 오른쪽이 크면 음수 반환
EnumClass 메서드
메서드 | 설명 |
Class<E> getDeclaringClass() | 열거형의 Class객체 반환 |
String name() | 열거형 상수의 이름을 문자열로 반환 |
int ordinal() | 열거형 상수가 정의된 순서를 반환 (0부터 시작) |
T valueOf(Class<T> enumType, String name) | 지정된 열거형에서 name과 일치하는 열거형 상수 반환 |
enum compareTo() | 문자열을 입력받아서 일치하는 열거 객체를 리턴 |
enum[] values() | 모든 열거 객체들을 배열로 리턴 |