자바 제네릭스와 제네릭

Soobin Kim·2024년 4월 6일

Java

목록 보기
36/47

제네릭스

다양한 타입의 객체들을 다루는 메서드나 컬렉션 클래스에 컴파일 시 타입을 체크해주는 기능

도입 배경

기존의 컬렉션 프레임워크는 Object 타입을 사용하여 모든 종류의 객체를 하나의 자료구조에 저장할 수 있었다. 이로 인해 컬렉션의 요소를 활용하기 위해 다운캐스팅을 수행하는 경우가 많았는데, 이는 런타임 시에 타입 불일치 오류(ClassCastException)를 발생시킬 수 있었다. 이에 제네릭스를 도입함으로써 컴파일러가 타입을 체크하여 잘못된 타입 사용을 사전에 방지할 수 있게 되었고(런타임 오류 사전 방지), 코드의 가독성도 높아졌다.

  • 타입 안정성 확보(type safety, 실행 오류를 컴파일 오류로 끌어옴)
  • 형 변환이 생략되므로 코드가 간결해지고 가독성이 높아짐

제네릭

Java에서 데이터 타입을 일반화하는 방법으로, 클래스나 메소드 사용할 데이터 타입을 특정(Specific)타입으로 미리 정하는 게 아니라, 필요에 의해 지정할 수 있도록 일반(Generic)타입으로 지정하는 것이다.
즉 제네릭은 하나의 코드를 다양한 타입의 객체에 재사용할 수 있는 객체지향기법이다.

데이터타입을 내부에서 지정하는 것이 아닌 외부에서 지정함으로써 사용하는 데이터 타입을 런타임 시에 결정할 수 있다(실제 데이터 타입이 사용하는 시점에 결정).

제네릭 타입

제네릭 변수설명
E컬렉션의 요소(element)를 나타내는 제네릭 변수. 주로 List, Set, Queue 등의 컬렉션에서 사용된다.
K맵(Map)에서 키(key)를 나타내는 제네릭 변수.
V맵(Map)에서 값(value)을 나타내는 제네릭 변수.
T제네릭 프로그래밍에서 사용되는 일반 타입 변수. 주로 클래스, 메서드의 반환 타입 또는 매개변수로 사용된다. 두 개 이상 일반 타입이 필요할 땐 S, U 등을 사용한다.
N제네릭 프로그래밍에서 숫자(number)를 나타내는 제네릭 변수. 주로 숫자형 데이터 타입(int, double, float 등)을 다루는 경우에 사용된다. 일반적으로는 T와 같은 의미로 사용될 수 있다.

제네릭 클래스

하나 이상의 타입 매개변수를 사용하여 클래스를 정의한 클래스.

public class GenericArray<T> {
    private final static int DEFULAT_CAPACITY = 5;
    private T[] elements;
    private int size = 0;

    public GenericArray() {
        this(DEFULAT_CAPACITY);
    }

    public GenericArray(int capacity) {
        this.elements = (T[])(new Object[capacity]);
    } 

    public void add(T element) {
        if (size == elements.length) {
            enhanceCapacity();
        }
        elements[size++] = element;
    }

    public T get(int index) {
        if (index < 0 || index >= elements.length) {
            throw new IndexOutOfBoundsException("범위 초과");
        }
        return elements[index];
    }

    public int getSize() {
        return this.size;
    }

    private void enhanceCapacity() {
        int newCapacity = size + 1;
        elements = Arrays.copyOf(elements, newCapacity);
    }
}

public class Main {
    public static void main(String[] args) {
        GenericArray<String> ga = new GenericArray<>();
        ga.add("Hello");
    }
}

제네릭 클래스의 인스턴스가 생성될 때 generic 타입의 specific 타입을 받아 그 데이터 타입을 클래스의 변수로 사용한다. 이렇게 구현함으로써 클래스의 멤버 변수, 메서드, 생성자 등에서 사용될 타입을 동적으로 지정할 수 있다.

  • 타입(매개)변수에 타입을 지정하는 것을 제네릭 타입 호출이라고 한다.

  • 선언된 제네릭 클래스를 생성할 때 타입 매개변수는 구체적인 타입으로 대체한다.

    • 'String'과 같이 지정된 타입(구체화된 타입)을 '매개변수화된 타입'이라고 한다.
    • 지정할 타입은 참조 타입만 가능하다.
    • 실제 타입을 명시하면 내부적으로 정의된 타입 변수가 명시된 실제 타입으로 변환되어 처리된다.
    • 제네릭 클래스의 생성자 부분에 있는 적용할 타입은 컴파일러가 문맥에서 타입을 추론할 수 있는 경우 생략할 수 있는데, 적용할 타입이 생략된〈〉를 다이아몬드 연산자라고 한다(Java SE 7부터 생략 가능).

제네릭 메소드

하나 이상의 타입 매개변수를 사용하여 메소드를 정의한 메소드.

public class Utils {
    public static <T> T getLastElement(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        return array[array.length - 1];
    }
}

public class Main {
    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        String[] strArray = {"One", "Two", "Three"};

        Integer lastInt = Utils.getLastElement(intArray);
        System.out.println("Last Integer: " + lastInt); // Output: Last Integer: 5

        String lastStr = Utils.getLastElement(strArray);
        System.out.println("Last String: " + lastStr);  // Output: Last String: Three
    }
}

제네릭 메소드 선언부에 지정한 제네릭 타입은 호출 시에 정해지며, 정해진 구체적인 타입으로 리턴타입이나 파라미터의 타입이 정해진다.

  • 제네릭 클래스가 아닌 일반 클래스에서도 제네릭 메소드를 정의할 수 있다.

0개의 댓글