본문 바로가기

JAVA

제네릭(Generic) 이란?

제네릭은 자바의 형변환 에서 발생할 수 있는 문제점을 '사전'에 없애기 위해서 만들어졌다.

 

'사전'에 없앤다는 것은 실행시 예외가 발생하는 것을 처리하는 것이 아니라 컴파일할 때 점검할 수 있도록 하는것을 말한다.

 

1. 제네릭의 사용법


다음의 코드를 살펴보자

public class CastingDTO implements {
    private Object object;
    public void setObject(Object obj){
        this.object= obj;
    }
    public Object getObject(){
        return this.object;
    }
}

 

CastingDTO 클래스는 내부에는 타입이 Object인 객체를 get/set 할 수 있으므로 어떤 타입이든 사용할 수 있다.

이렇게 선언된 클래스의 get method를 사용할 때 return 값이 Object 형 이므로 우리가 사용할 타입으로 형변환을 해줘야 한다.

public class Main(){
	public static void main(String[] args){
    	CastingDTO dto = new CastingDTO();
        String temp = (String)dto.getObject();
        System.out.println(temp);
    }
}

 

이 클래스를 제네릭으로 선언하면 다음과 같다.

public class CastingDTO<T> {
    private T object;
    public void setObject(T obj){
        this.object= obj;
    }
    public T getObject(){
        return this.object;
    }
}

 

크게 차이점이 없어보이지만 선언문에 <>가 생긴것과 그안에 알파벳 T가 적힌것을 볼 수 있다. 클래스의 선언을 제네릭을 사용하면 형변환에서 발생할수 있는 문제점을 '사전'에 예방할 수 있다.

public class Main(){
	public static void main(String[] args){
    	CastingDTO<String> dto = new CastingDTO<String>();
        String temp = dto.getObject();
        System.out.println(temp);
    }
}

 

2. 제너릭 타입의 이름 정하기


제네릭의 선언을 무조건 적으로 T 로 하는것이 아닌 사용자가 임의로 지정해서 사용할 수 있다.

하지만 자바에서 정의한 기본 규칙은 존재한다.

E 요소(Element, 자바 컬렉션에서 주로 사용)  
K
N 숫자
T 타입
V
S,U,V 두번째, 세번째, 네번째에 선언된 타입

 

3. 와일드카드(wildcard)


이번에는 메소드의 매개변수로 넘어가는 제네릭에 대해서 살펴보자

public class Wildcard<W>{
    W wildcard;
    public void setWildcard(W wildcard){
        this.wildcard= wildcard;
    }
    
    public W getObject(){
        return this.wildcard;
    }
}

 

이 클래스를 사용하는 Main 클래스를 만들어 보자

public class Main(){
	public static void main(String[] args){
    	Main m = new Main();
    	WildCard<String> wildCard = new WildCard<String>();
        wildCard.setWildcard("A")
        
        m.wildcardMethod();
    }
    
    public void wildcardMethod(WildCard<?> w){
    	Object object = w.getWildCard();
        System.out.println(object);
    }
}

 

wildcardMethod 를 살펴보면 매개변수로 WildCard<?> w 로 받아온 것을 볼 수 있다.

이는 main 메소드 내부에서 wildCard 를 String형, Integer형 등등 다른 타입으로 매개변수를 넘겨주기 위해서 사용한 것이다 이때 사용한 ? 를 우리는 와일드카드(wildcard) 라고 한다.

 

이때 받아온 WildCard 객체는 Object로 처리해야 한다.

 

또한, 와일드카드(wildcard)는 메소드의 매개변수로만 사용하는 것이 좋다 만약 인스턴스 생성시 와일드카드를 사용하게 된다면 에러 메시지를 뿌리며 컴파일이 되지 않을 것이다.

 

객체간 상속관계가 존재할 경우 Bounded Wildcard 즉 <? extends MotherClass> 라는 식으로 활용이 가능하다.

 

4. 메소드를 제네릭하게 선언하기


앞에서 와일드카드(wildcard)로 매개변수로 받아와 처리하는 방법을 살펴보았다. 그런데, 이방법에는 큰 단점이 있다. 

매개변수로 사용된 객체에 값을 추가할 수 없다는 점이다.

 

위에서 만든 Main 클래스를 수정해 보자

public class Main(){
	public static void main(String[] args){
    	Main m = new Main();
    	WildCard<String> wildCard = new WildCard<String>();
        
        m.wildcardMethod(wildCard,"A");
    }
    
    public <T> void wildcardMethod(WildCard<T> w, T addValue){
    	w.setWildcard(addValue);
        T value = w.getWildcard();
        System.out.println(Value);
    }
}

? 를 사용하는 와일드카드 처럼 타입을 정하지 않는것 보다는 이처럼 명시적으로 메소드 선언시 타입을 지정해 주면 보다 더 견고한 코드를 작성할 수 있다.

 

그렇다면 Bounded Wildcards처럼 사용하지 못하는가? 물론 아니다.

public <T extend MotherClass> void genericMethod(Wildcard<T> w, T addValue)

 

또한 자신이 만들 메소드가 제네릭 타입이 2개일 경우 콤마로 구분하여 나열해 주면 된다.

public <S, T extends MotherClass> void genericMethod(Wildcard<T> w ,T addValue1, S addValue2)