Generic (Feat. Object, Enum)

Generic이란?

JDK 1.5 부터 지원된 기능으로 클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시 미리 지정 할 수 있는 방법이다.
예를 들어 ArrayList<E> 는 ArrayList의 요소 타입을 제네릭으로 처리하기 때문에 요소에 String, Integer 또는 커스텀 객체 등 데이터 타입을 다양하게 담을 수 있다.


제네릭 타입 변수가 들어갈 자리에는 기본 타입은 사용이 불가능하다.
래퍼 클래스(Wrapper Class)를 이용해야한다.

ArrayList<int> list = new ArrayList<int>(); // Type argument cannot be of primitive type 빌드 오류 !!
ArrayList<Integer> list2 = new ArrayList<>();


오라클에서 얘기하는 왜 제네릭을 사용해야하는가 : https://docs.oracle.com/javase/tutorial/java/generics/why.html


Generic 사용법

파라미터 사용법

클래스에 <> 안에 받고자 하는 타입 파라미터의 개수를 지정한다.(파라미터 명명규칙 참고)

class Product<K, V> {  // 꺽쇠 안에 받은 타입 파라미터가 아래 타입 변수로 선언된다
    private K key;
    private V value;

    public Product(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey(){  return key;  }

    public V getValue(){  return value;  }
}

...

Product<Integer, String> product = new Product<>(1, "sogogi");
Product<String, String> strProduct = new Product<>("d-01", "darkgogi");

메소드 사용법

메소드에서 제네릭을 사용할땐 아래와 같이 사용할 수 있다.

class Response<T> {
    T list;

    public void setList(T list){  // 제네릭을 파라미터로 받아온다
        this.list = list;
    }
    public T getList(){  // 제네릭을 리턴한다.
        return list;
    }
}


※ static 은 제네릭(T-타입변수)에 사용할 수 없다.

Generic 파라미터 명명규칙

인자 설명
T Type
E Element
K Key
V Value
N Number
R Result

Generic 공변성 반공변성

간혹 아래와 같이 <? super ..> 또는 <? extends ...> 가 보이는데 무슨 의미일까?

-- Function interface 내부 메서드  발췌

default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
    Objects.requireNonNull(before);
    return (V v) -> apply(before.apply(v));
}


java 공변성과 반공변성에 대해 우선 알아야 한다.
직접 다루지 않음


결론 : <? super T>T 에 넣는 타입 이상의 타입 자료로만 제한, <? extends T>T 에 넣는 타입 이하의 타입 자료로만 제한한다는 의미


<?> : 타입 변수에 모든 타입을 사용 가능
<? extends T> : T 타입과 T 타입을 상속받는 자손 클래스 타입만을 사용 가능
<? super T> : T 타입과 T 타입이 상속받은 조상 클래스 타입만을 사용 가능



Generic VS Object

❓이전에도 Object가 최상위 객체 타입이기 때문에 Object를 이용하면 모든 객체 타입을 담을 수 있었는데?


사용은 가능하다만 아래와 같은 차이점이 존재한다.

Object를 이용할 경우 어떤 데이터 타입이든 다 담을 수 있다.
그러나 반대로 담겨있는 데이터의 타입이 어떤 건지 알기 어렵다.

ArrayList<Object> objList = new ArrayList<>();
objList.add("String Data"); // String
objList.add(1);             // Integer
objList.add('Y');           // Char

int element = (int) objList.get(0); // 런타임 오류!! String 을 int로 캐스팅 시도 했으니 오류


Generic을 이용하면 지정한 데이터 타입만 담을 수 있게 된다.

즉 꺼내오는 데이터 타입도 컴파일 단계에서 지정되므로 잘못된 데이터 타입을 담으려 하면 컴파일 오류가 발생한다.

ArrayList<String> genericList = new ArrayList<>();
genericList.add("String Data");
genericList.add(1);     // Required type: String 컴파일 오류!!

int element = genericList.get(0);   // Required type: int, Provided: String 컴파일 오류!! genericList에 담긴건 String이니 int element에 담을 수 없다는 뜻


실무에서 개발을 진행하다 보면 모호한 정의가 얼마나 위험한지 모두 알 것이다.

두 번째는 Object를 이용 할 경우 최상위 타입이므로 사용하고자 하는 타입으로의 형변환이 필요하다. (Boxing, Unboxing)

boxing : 기본타입 -> 래퍼 클래스
unBoxiong : 래퍼클래스 -> 기본타입


List 요소를 Object로 처리 한 list는 String 타입을 담은 뒤 꺼내올 때 UnBoxing 없이 꺼내올 수 없다.
이러한 언박싱 과정에서 소비되는 리소스는 데이터가 많을수록 무시할 수 없는 수준이 될 수 있다.

ArrayList<Object> objList = new ArrayList<>();
objList.add("String Data"); // String

String data = objList.get(0); // cast expression to 'String'  컴파일 오류!!
String data = (String) objList.get(0); // 담긴 데이터의 자료형이 String 이 맞다면 성공

Enum

열거형 상수로 class 위치에 대신 enum 을 작성하여 생성할 수 있다.

enum Status {
	WAIT, ING, SUCC, FAIL, ERROR
}

enum을 사용하여 정의 할 경우 자동으로 싱글톤 패턴으로 구현된다.

사용법

텍스트 그대로 추출하기

enum Status {
	WAIT, ING, SUCC, FAIL, ERROR
}

System.out.println("Status = " + Status.ERROR);
// Status = ERROR

각 상수별 추출할 텍스트 지정하기

enum Status {
    WAIT(1),
    ING(2),
    SUCC(3),
    FAIL(4),
    ERROR(5);

    private final int value;

    Status(int value) {
        this.value = value;
    }

    public int value(){
        return value;
    }

}

System.out.println("Status = " + Status.ERROR);
System.out.println("Status = " + Status.ERROR.value());
// Status = ERROR
// Status = 5