[JAVA] Generic

buckshot·2024년 6월 3일

JAVA

목록 보기
6/18
post-thumbnail

지금까지 Java를 공부하면서 간단한 코두룰 작성하는데 제네릭에 대하여 자세히 이해를 하고 사용하지는 않았다. 그래서 이번 기회에 제네릭에 대하여 자세히 알아 보겠다.

Generic

제네릭은 Java 5에서 처음 도입되었습니다. 이는 컬렉션 프레임워크의 개선과 동시에 이루어졌습니다. Java 5 이전에는 컬렉션 클래스들이 Object 타입을 사용했기 때문에, 컬렉션에 저장된 객체를 사용할 때마다 명시적인 캐스팅이 필요했습니다. 이로 인해 런타임에 ClassCastException이 자주 발생하곤 했습니다. 제네릭은 이러한 문제를 해결하고자 도입되었습니다.

제네릭의 구현 방식은 타입 소거(Type Erasure)라는 기법을 사용합니다. 이는 제네릭 타입 정보를 컴파일 타임에만 사용하고, 런타임에는 모든 제네릭 타입이 Object로 변환되는 방식입니다. 이를 통해 기존 자바와의 호환성을 유지하면서 제네릭 기능을 제공할 수 있게 되었습니다. 예를 들어, List는 컴파일 타임에만 String 타입을 체크하고, 런타임에는 List로 취급됩니다.

타입 소거는 또한 제네릭 메소드와 클래스가 다양한 타입을 지원할 수 있게 합니다. 예를 들어, List와 같은 제네릭 타입을 사용하면 List, List 등 여러 타입을 지원할 수 있습니다. 제네릭 타입의 바운드를 설정할 수도 있습니다. 예를 들어, T extends Number는 T가 Number 클래스나 그 하위 클래스임을 보장합니다.

제네릭이 도입되기 전에는 자바에서 컬렉션과 같은 구조에 Object 타입을 사용했습니다. 이로 인해 컬렉션에서 객체를 꺼내 사용할 때마다 명시적인 캐스팅이 필요했습니다. 이러한 점은 런타임에서 타입 오류를 야기할 수 있었습니다. 예를 들어, 리스트에 다양한 타입의 객체를 저장하고 이를 다시 꺼내 사용할 때마다 올바르게 캐스팅해야 했습니다. 만약 잘못된 캐스팅을 하면 ClassCastException이 발생했습니다.

또한, 제네릭이 없을 때는 같은 기능을 수행하는 여러 버전의 메소드나 클래스를 각기 다른 타입에 대해 작성해야 했습니다. 이는 코드의 중복을 초래하거나 유지 및 보수를 어렵게 만들었습니다. 이러한 이유 때문에 제네릭의 도입이 필요해졌습니다.

제네릭을 통해 컴파일 타임에 타입을 확인할 수 있게 되며, 타입 불일치로 인해 발생하는 오류를 줄일 수 있습니다. 간단하게 말하자면, 리스트 선언부에 을 사용하면 컴파일 타임에 문자열만 저장할 수 있도록 강제되어 다른 타입의 객체가 저장되는 것을 방지합니다. 이를 통해 타입의 안정성을 강화할 수 있습니다. 추가적으로 컬렉션에서 객체를 꺼낼 때 명시적인 캐스팅이 필요 없고, 코드의 재사용성을 높여줍니다.

예를 들어, String 타입과 Integer 타입을 모두 지원하는 자료구조를 만들고 싶다고 가정해봅시다. 그렇지 않으면 각각의 클래스에 대해 별도의 타입을 만들어야 하는데, 이는 비효율적입니다. 따라서 특정 타입을 미리 지정하지 않고, 필요에 따라 타입을 지정할 수 있도록 하는 것이 제네릭의 개념입니다. 제네릭을 사용하면 하나의 클래스나 메소드를 다양한 타입에 대해 재사용할 수 있으며, 타입의 안정성을 높이고 명시적인 캐스팅을 줄여 코드의 가독성과 유지보수성을 더욱 향상시킬 수 있습니다.

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

보통은 위 표처럼 타입들을 많이 사용된다.

사용 방법

* 클래스 및 인터페이스 선언

public class GenericClass1 <T> {}
public interface GenericInterface1 <T> {}

public class GenericClass2 <T, K> {}
public interface GenericInterface2 <T, K> {}

제네릭 타입의 클래스와 인터페이스는 이와같이 선언을 한다. 추가적으로 타입을 두개로 설정할 수 있다.

선언된 클래스를 사용할 때 구체적인 타입을 명시를 해야한다.

public class Main {
  public static void main(Srting[] args) {
    GenericClass2<String, Integer> generic = new GenericClass2<String, Integer>();
  }
}

이 때 주의해야 할 부분은 타입 파라미터로 명시할 수 있는 것은 참조 타입 뿐이다.

* 제네릭 클래스

제네릭 클래스는 클래스 정의에 의하여 타입 파라미터를 사용하여 다양한 타입을 처리할 수 있다.

// 제네릭 클래스 정의
class Box<T> {
    private T item;

    public void setItem(T item) {
        this.item = item;
    }

    public T getItem() {
        return item;
    }
}

public class Main {
    public static void main(String[] args) {
        // String 타입을 사용하는 제네릭 클래스
        Box<String> stringBox = new Box<>();
        stringBox.setItem("Hello");
        System.out.println("String Box contains: " + stringBox.getItem());

        // Integer 타입을 사용하는 제네릭 클래스
        Box<Integer> intBox = new Box<>();
        intBox.setItem(123);
        System.out.println("Integer Box contains: " + intBox.getItem());
    }
}

* 제네릭 인터페이스

제네릭 인터페이스는 인터페이스 정의에서 타입 파라미터를 사용하여 다양한 타입을 처리할 수 있다. 이는 클래스와 유사하게 동작을 한다.

// 제네릭 인터페이스 정의
interface Pair<K, V> {
    K getKey();
    V getValue();
}

// 제네릭 인터페이스 구현 클래스
class OrderedPair<K, V> implements Pair<K, V> {
    private K key;
    private V value;

    public OrderedPair(K key, V value) {
        this.key = key;
        this.value = value;
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }
}

public class Main {
    public static void main(String[] args) {
        // String, Integer 타입을 사용하는 제네릭 인터페이스 구현체
        Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
        System.out.println("Key: " + p1.getKey() + ", Value: " + p1.getValue());

        // Another example with different types
        Pair<String, String> p2 = new OrderedPair<>("Hello", "World");
        System.out.println("Key: " + p2.getKey() + ", Value: " + p2.getValue());
    }
}

* 제네릭 메소드

제네릭 메소드는 메소드 정의에서 타입 파라미터를 사용하여 다양한 타입을 처리할 수 있다.
제네릭 메소드는 클래스가 제네릭이지 않아도 된다.

public class Main {
    // 제네릭 메소드 정의
    public static <T> void printArray(T[] array) {
        for (T element : array) {
            System.out.print(element + " ");
        }
        System.out.println();
    }

    public static void main(String[] args) {
        // Integer 배열을 사용한 제네릭 메소드 호출
        Integer[] intArray = {1, 2, 3, 4, 5};
        printArray(intArray);

        // String 배열을 사용한 제네릭 메소드 호출
        String[] stringArray = {"A", "B", "C", "D"};
        printArray(stringArray);
    }
}
profile
let's go insane

0개의 댓글