Language/Java

Java - 제네릭(Generic)은 무엇인가? 에 대한 정리! (1)

JaeHoney 2022. 5. 8. 12:51

제너릭(Generic)

제너릭(Generic)은 JDK ArrayList나 LinkedList, HashMap 등의 자료구조를 사용할 때 주로 사용한다.

ArrayList<Student> students = new ArrayList<Study>();

 

그렇다면 제너릭의 정확한 역할은 무엇인가 ? 우리는 제너릭을 잘 활용하고 있을까?

 

제너릭을 사용하는 이유

제너릭은 JDK 1.5에 처음 도입되었다.

Oracle Javadoc
- Generics add stability to your code by making more of your bugs detectable at compile time.

 

다음은 제너릭의 기능을 정리한 것이다.

  • 잘못된 타입이 들어오는 것을 컴파일 단계에서 방지한다.
    • 자바 컴파일러는 잘못 사용된 타입 때문에 발생하는 에러를 잡기 위해 제너릭을 사용한 타입 체크를 해준다.
  • 클래스 외부에서 타입을 지정하기 때문에 타입을 체크하고 변환할 필요가 없다.

제너릭을 사용하지 않으면 아래와 같은 상황이 발생한다.

-문제 발생-
1) ArrayList에 3을 추가했다.
2) 개발자가 실수로 ArrayList에 student 객체를 추가했다.
3) ArrayList 항목을 전부 더하는 메서드를 실행하다가 런타임 에러가 터진다.

-해결 방안-
이러한 상황을 방지하기 위해서는 ArrayList가 특정 타입의 원소만 가져야 한다.
1. 만약 int타입만 원소로 받는다면 해결할 수 있다.
  -> 다른 타입에 대해 사용할 때 새로운 클래스(IntArrayList, StringArrayList, ...)를 또 만들어야 한다.
2. 생성자로 type을 받아서 뭔가 동작을 할 떄마다 해당 type과 파라미터 타입을 비교한다.
  -> 많은 불필요한 노력과 수고가 필요하다.

이러한 문제점을 자바에서는 제너릭(Generic)을 사용해서 깔끔하게 해결한다.

 

클래스 및 인터페이스

기본적으로 클래스 및 인터페이스에서 제너릭은 아래와 같이 사용한다.

public class MyClass <T> { ... }
public Interface MyInterface <T> { ... }

이 때 T 타입은 { ... } 블록 내부에서만 유효하다.

 

보통 제너릭은 아래 표의 타입을 많이 사용한다.

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

그렇지만 표의 내용은 암묵적인 룰일 뿐이고 이름을 직접 명시해도 전혀 상관없다.

 

제너릭 타입을 두 개로 둘 수도 있는다.

public class MyClass <K, V> { ... }
 
public class Application {

    public static void main(String[] args) {
        MyClass<Integer, String> instance = new MyClass<Integer, String>();
    }
}

이 때 K는 Integer가 되고 V는 String이 되어서 동작한다. 그렇다면 제너릭 타입은 클래스 내부에서 어떻게 동작할까?

 

제너릭 타입은 클래스 내부에서 타입으로써 사용 가능하다.

public class MyClass <E> {
	
    private E element;
    
    void set(E element) {
        this.element = element;
    }
    
    void print() {
        System.out.println(element);
    }
	
}

class Main {

    public static void main(String[] args) {
        MyClass<Integer> instance = new MyClass<Integer>();
        instance.set(5);
        instance(print); // 5 출력
    }
}

set() 메서드를 보면 받은 제너릭을 파라미터 타입으로 명시해서 타입을 제한하기 때문에 편리하고 컴파일 단계에서 에러를 잡을 수 있다.

 

아래는 HashMap의 일부이다.

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    
    static class Node<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        Node<K,V> next;

        Node(int hash, K key, V value, Node<K,V> next) {
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey()        { return key; }
        public final V getValue()      { return value; }
        public final String toString() { return key + "=" + value; }

        // ... 생략
    }
    // ... 생략
}

HashMap에서는 inner static 클래스까지 K와 V 타입을 전달하고 inner static 클래스인 Node 클래스에서 부모 클래스인 HashMap이 가진 제너릭 타입을 받아서 용도에 맞게 사용한다.

Generic Method

제너릭은 클래스 뿐만 아니라 메서드에도 사용할 수 있다. 이를 제너릭 메서드라고 한다.

public <T> T genericMethod(T o) {
    // ... 생략
}

해당 메서드를 호출하기 위해서는 2가지 방법이 있는데, 타입을 지정하는 방법과 생략하는 방법이다. 타입을 생략하면 컴파일러가 매개변수를 보고 타입을 추정한다.

instance.genericMethod(1);
instance.<Integer>genericMethod(1);

이를 사용하면 클래스에서 지정한 제너릭 타입과 별도로 메소드에서 제너릭 유형을 받아서 사용할 수 있다.

 

제너릭 메서드는 정적 메소드에서 타입을 받아서 처리해야 할 때 주로 사용한다. 왜냐하면 static 메소드는 객체가 생성되기 전에 Heap에 올라가게 된다. 즉, 객체가 생성되지 않아 제너릭 타입을 가지지도 않은 상태로 static method가 클래스의 제너릭 타입에 접근하면 컴파일 에러가 난다.

 

그때 메서드가 클래스의 제너릭 타입과 무관하게 Generic을 받아서 Generic Method로 사용한다.

 

<참고 - 제너릭을 사용할 수 없는 상황

  1. new T[10]과 같이 제너릭으로 배열을 생성할 수는 없다.
    • new 연산자는 heap에 충분한 공간이 있는 지 확인하는 과정을 거친다.
    • 컴파일 시점에 T의 size를 알 수 없다.
  2. static 클래스나 static 변수에도 제너릭을 사용할 수 없다.
    • static 키워드를 사용하면 컴파일 시점에 생성되기 때문

단, static 메서드에서는 제너릭을 사용할 수 있다. static method는 컴파일시점에 생성되지만, 클래스의 제너릭 타입과 별개로 메서드가 별개로 제너릭 타입을 받아서 사용하는 것이다. 즉 메서드의 틀이 컴파일 시점에 생성되는 것이다.

 

정리

제너릭을 사용하는 이유는 아래와 같다.

  • 타입체크, 형변환이 필요 없다. 타입 안정성이 보장된다.
  • 코드의 재사용성이 높아진다.

이러한 점은 다양한 Instance를 가질 수 있는 자료구조나 클래스를 만들 때 정말 유용하게 사용할 수 있다.

 

다음 2장에서는 제너릭을 사용해서 타입을 제한하는 방법에 대해서 알아보겠다. (+ 와일드 카드)

- https://jaehoney.tistory.com/187

 

Java - 제네릭(Generic)타입을 제한하는 방법! (2)

제네릭을 사용한 타입 제한 지난 1장(Java - 제네릭은 무엇인가)에 이어서 제너릭을 사용해서 타입을 제한하거나 와일드카드로 사용하는 방법을 알아본다. 지난 장에서 봤듯 제너릭을 사용하면

jaehoney.tistory.com

 


Reference