제네릭이란?

이 글을 보러 오신분들은 대부분 자바를 공부하면서 제네릭이 뭔지 궁금해서 들어온 분들이실겁니다. 

제네릭(generic)이란 데이터의 타입을 일반화한다(generalize)는 것을 의미합니다.

이렇게 적으면 아마 이해가 어려우실겁니다.

 

 

사실 우리가 흔히 사용하는 List, Map, Set 등의 컬렉션에서 자주 볼 수 있습니다.

 

public static void main(String[] args) {

        List<String> stringList = new ArrayList<>();
        List<Integer> integerList = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        Set<Double> set = new HashSet<>();
        
}

 

객체<타입> 객체명 = new 객체<타입>();

 

이 형식에서 < > 이것이 바로 제네릭 입니다.

 

즉 제네릭은 클래스 외부에서 사용자에 의해 타입을 지정되는 것 ( 타입을 일반화한다 ) 을 의미합니다.

 

 

제네릭의 특징

  1. 클래스나 메소드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있다.
  2. 반환값에 대한 타입 변환 및 타입 검사에 들어가는 노력을 줄일 수 있다.
  3. 잘못된 타입이 입력되는 것을 컴파일 단계에서 방지할 수 있다.
  4. 재사용성이 높다.

 

제네릭 사용방법

타입 설명
<T> Type
<E> Element
<K> Key
<V> Value

이 표현식은 관례적으로 사용되고 있습니다. 반드시 일치해야할 필요도 없고 한 글자일 필요도 없습니다.

하지만 개발은 혼자하는것이 아니기에...

 

 

public interface Generic <T> { ... }
public class Generic <T> { ... }
public class Generic <T, K> { ... }

클래스와 인터페이스는 이렇게 선언하면 됩니다.

 

이번에는 List와 같은 클래스를 하나 만들어 보겠습니다.

 

제네릭 클래스

public class Generic <T> {

    private final List<T> list = new ArrayList<>();


    public void add(T value) {
        list.add(value);
    }

    public void remove(T value) {
        list.remove(value);
    }

    public T get(int index) {
        return list.get(index);
    }

}

public static void main(String[] args) {

    Generic<String> generic = new Generic<>();

    String test = "제네릭";
    generic.add(test);
    generic.remove(test);

    int size = generic.size();
    System.out.println(size); // 0
}

 

만약 String 이 아닌 타입이 입력된다면?

public static void main(String[] args) {

    Generic<String> generic = new Generic<>();

    int test = 1;
    generic.add(test); // 컴파일 에러!
    generic.remove(test); // 컴파일 에러!

    int size = generic.size();
    System.out.println(size);
}

컴파일 과정에서 오류를 방지할 수 있습니다.

 

제네릭 메서드

// [접근제어자] <제네릭타입> [반환타입] [변수명] ( [제네릭타입] 매개변수명 )
public <T> T getMethod(T value) { ... }

 

 

 

 

 

와일드 카드 < ? >

위에서의 제네릭에서는 타입을 제한하지 않았습니다. 와일드카드는 타입의 제한을 둘 수 있습니다.

<K extends T> { ... } // T, T의 자식 타입만 가능
<K super T> { ... } // T, T의 부모 타입만 가능

<? extends T> { ... } // T, T의 자식 타입만 가능
<? super T> { ... } // T, T의 부모 타입만 가능

 

여기서 K extends T와 ? extends T 는 차이가 있습니다.

K는 타입이 지정이 되지만 ?는 타입이 지정되지 않습니다.

 

여기서 주의점은 와일드카드는 클래스에 사용할 수 없다는 것입니다. 클래스에서는 타입을  <K extends T>와 같이 지정해주어야합니다.

 

public class Parent{
    
    public void call() {
        System.out.println("Parent Call");
    }
}

public class Children extends Parent{

    @Override
    public void call() {
        System.out.println("Children Call");
    }
}

public class Main {

    public static void main(String[] args) {
        List<Parent> list = new ArrayList<>();

        list.add(new Parent());
        list.add(new Children());

        listCall(list);
    }

    private static void listCall(List<? extends Parent> list) {
        for (Parent parent : list) {
            parent.call();
        }
        // Parent Call 출력
        // Children Call 출력
    }
}

 

+ Recent posts