-
[JAVA] 제네릭스(Generics) / 열거형 (Enum)DEV/JAVA 2024. 4. 3. 18:36
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() 모든 열거 객체들을 배열로 리턴 'DEV > JAVA' 카테고리의 다른 글
[JAVA] 쓰레드(Thread)의 동기화 / synchronized / Lock과 Condition / fork & join 프레임워크 (0) 2024.04.23 [JAVA] 쓰레드 (Thread)의 구현 / 싱글쓰레드, 멀티쓰레드 / 우선순위 / 쓰레드 그룹 / 데몬 쓰레드 (2) 2024.04.05 [JAVA] 스트림(Stream) 생성 / 중간연산 map,flatMap / 최종연산 reduce, collect / Optional (2) (0) 2024.03.26 [JAVA] 스트림(Stream) 기본 / 특징 / 연산 (1) (0) 2024.03.20 [JAVA] 람다식 (Lambda expression) (2) 2024.03.17