JAVA 제네릭이란(Generic)?

업데이트:
3 분 소요

JAVA 5부터 제네릭타입이 추가되었습니다. 제네릭타입을 사용하면 컴파일 과정에서 잘못된 타입사용을 미리 방지할 수 있다는 장점이 있습니다. 대부분의 API 도큐먼트를 보면 제네릭표현이 많으므로 API에 대한 깊은 이해를 위해서는 제네릭에 대한 이해는 필수입니다.

Step 1 : 제네릭 타입

제네릭 타입은 <>을 가지는 클래스와 인터페이스를 말합니다. <>사이에는 <T>처럼 사용 시에 정해지는 즉 사용 시에 받아올 객체에 대한 파라메터를 대표합니다. <T><G> 등 원하는 문자를 넣어주면 됩니다. 그렇다면 이전 코드와 제네릭을 사용하는 코드를 비교해보겠습니다. 아래의 클래스는 원하는 객체를 넣어주던 코드입니다.

public class BeforeTest {
    private Object object;

    public void setObj( Object obj ) {
        this.object  = obj;
    }
    
    public Object getObj() {
        return object;
    }
}
public static void main(String args[]){
    BeforeTest beforeTest = new BeforeTest();
    Integer tempInt = 100;
    beforeTest.setObj(tempInt);
    int sum = (Integer)beforeTest.getObj() + 1000;
    System.out.println("합은 : " + sum);
}
1
2
// 결과 
합은 : 1100

위의 코드는 아쉬운 점은 모든 객체를 받아서 Setting 할 수 있지만 사용 시에 사용 객체로 형 변환 해야 합니다. 또한 저장 시에도 보이진 않지만 Integer 객체가 Object 객체로 형 변환됩니다. 이런 문제를 해결한 제네릭을 이용한 방법입니다. 클래스명 뒤에 <T>을 붙이고 클래스 내부 T에서 T를 사용할 수 있습니다. 클라이언트 코드에서 T에 대한 타입을 정해줍니다.

public class BeforeTest <T>{
    private T object;

    public void setObj( T obj ) {
        this.object  = obj;
    }
    
    public T getObj() {
        return object;
    }
}
public static void main(String args[]){
    BeforeTest<Integer> beforeTest = new BeforeTest<Integer>();
    Integer tempInt = 100;
    beforeTest.setObj(tempInt);
    int sum = beforeTest.getObj() + 1000;
    System.out.println("합은 : " + sum);
}

Step 2 : 멀티 타입 파라미터

제네릭 타입은 두 개 이상의 파라메터를 클라이언트 측에서 받아오고 싶을 때 < A , B , …> 처럼 사용할 수 있습니다.

public class BeforeTest < A, B, C>{
    private A first;
    private B second;
    private C third;

    public void setFirst( A first ) {
        this.first  = first;
    }
    
    public void setSecond( B second ) {
        this.second= second;
    }
    
    public void setThird( C third ) {
        this.third = third;
    }
    
    public void show() {
        System.out.println("A의 타입은 : " + first.getClass().getTypeName());
        System.out.println("B의 타입은 : " + second.getClass().getTypeName());
        System.out.println("C의 타입은 : " + third.getClass().getTypeName());
    }
}
public static void main(String args[]){
    BeforeTest<String, Integer, Double> beforeTest = new BeforeTest<>();
    beforeTest.setFirst("안녕하세요");
    beforeTest.setSecond(10);
    beforeTest.setThird(100.0);
    beforeTest.show();
}
1
2
3
4
// 결과 
A 타입은 : java.lang.String
B 타입은 : java.lang.Integer
C 타입은 : java.lang.Double

JAVA 7 이전에는 BeforeTest<String, Integer, Double> beforeTest = new BeforeTest<String, Integer, Double>() 처럼 new 뒤에도 <>안 내용을 명시해야 했지만 java 7 이후에는 생략하여 BeforeTest<String, Integer, Double> beforeTest = new BeforeTest<>() 가 가능해졌습니다. 이를 다이아몬드 연산자라고 부릅니다.

Step 3 : 제네릭 메소드

제네릭 메소드는 매개타입 또는 리턴타입으로 타입파라메터를 갖는 메소드를 말합니다. 선언하는 방법은 리턴타입 앞에 <>를 추가하고 적어줍니다. 즉 메소드에서 사용할 부분을 리턴타입 내부 <>에 적어주고 뒤에서 사용하면 됩니다. 사용 방법은 아래와 같습니다.

public class BeforeTest  {
    public <T> void show( T inputObj ) {
        if( inputObj.getClass().getTypeName().equals(String.class.getTypeName()) ) {
            System.out.println("문자입니다.");
            System.out.println(inputObj);
        } else if( inputObj.getClass().getTypeName().equals(Integer.class.getTypeName()) ) {
            System.out.println("숫자입니다.");
            System.out.println(inputObj);
        }
    }
}
public class Main {
    public static void main(String args[]){
    BeforeTest beforeTest = new BeforeTest();
    beforeTest.show("문자열 주입");
    beforeTest.show(10);
}
1
2
3
4
5
//출력
문자입니다.
문자열 주입
숫자입니다.
10

2개 이상 예제, Map에 해당 키가 담겨있나 확인하는 예제입니다.

public class BeforeTest  {
    public < T , D > void show( Map< T, D > map, T key ) {
        System.out.println(map.containsKey(key));
    }
}
public static void main(String args[]){
    BeforeTest beforeTest = new BeforeTest();
    Map<Integer, String> map = new HashMap<>();
    map.put(100, "100존재");
    map.put(10, "10존재");
    beforeTest.show(map, 100);
    beforeTest.show(map, 10);
    beforeTest.show(map, 1);
}
1
2
3
4
// 출력
true
true
false

지금까지의 코드는 구체적인 타입을 java가 알아서 추정하도록 코드를 짰습니다. 만약 구체적으로 타입을 명시하고 싶다면 위 클라이언트 코드에서 beforeTest.< Integer, String >show(map, 100); 처럼 명시할 수 있습니다.

Step 4 : 제한된 타입 파라미터

제네릭의 상위 타입을 구체적으로 제한하고 싶을 경우가 있습니다. 메서드, 인터페이스, 클래스에서 동일하게 사용 가능하며 < T extends 상위타입>으로 제한할 수 있습니다. 예를 들어 내부에서 사용할 T 객체가 꼭 Number 클래스의 하위 타입이어야 할 때, 혹은 원하는 상위 인터페이스의 구현체이어야 할 때 사용 가능합니다. 그래야 메소드 안에서 필요한 인터페스의 메소드 혹은 클래스의 메소드를 사용할 수 있습니다.

public class BeforeTest  {
    public < T extends Number, D extends Map > void show( D map, T key ) {
        System.out.println(map.containsKey(key));
    }
}

Step 5 : 와일드카드 타입.

코드에서 ?를 일반적으로 와일드카드라고 부릅니다. 사용하는 경우는 아래와 같습니다.

java 제네릭 하이라키

<?>

모든 클래스나 인터페이스가 올 수 있습니다. 즉 제한없음. A ~ E 모두 올 수 있다.

<? extends 상위타입>

상위타입 이하로만 올 수 있습니다. <? extends D> => D, E 가능

<? supper 하위타입>

하위타입 이상으로만 올 수 있습니다. <? supper D> => D, A 가능

참고자료

– 이것이 자바다 (한빛 미디어)

카테고리:

업데이트:

댓글남기기