DEV/JAVA

[JAVA] 제네릭스(Generics) / 열거형 (Enum)

Imvory 2024. 4. 3. 18:36

1) 제네릭스란?

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

→ 의도하지 않은 타입의 객체가 저장되는것을 막아주고, 저장된 객체를 꺼낼때 원래 타입과 다른 타입으로 잘못 형변환 되어 발생할 수 있는 오류를 줄여준다.

 

장점

  1. 타입 안정성 제공
  2. 타입체크와 형변환을 생략하여 코드가 간결해짐

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() 모든 열거 객체들을 배열로 리턴