[Java] Generic Type (일반 타입)

2024. 2. 25. 09:35
반응형

 

Generic Type

 

제네릭(Generic) 타입은 자바에서 클래스 또는 메서드를 정의할 때, 타입을 파라미터화하는 기능이다.

사용 목적은 다음과 같다.

 

 

1. 어떤 값이 하나의 참조 자료형이 아니라 여러개의 참조 자료형을 사용할 수 있도록 프로그래밍 할 수 있다.
2. 참조 자료형이 변환될 때 검증을 컴파일러가 하기 때문에 안정적이다.

 

 

 

 

 

우리가 많이 사용하는 컬렉션 프레임워크도 제네릭 타입을 사용하는 대표적인 예시이다.

List<Integer> list = new ArrayList<>();

 

컬렉션은 제네릭 타입을 사용하여 List가 여러가지 타입의 Element를 받을 수 있도록 설정할 수 있어 클래스 사용의 유동성이 매우 높다.

또한 제네릭을 사용하여 List를 선언함으로써 해당 List가 어떤 타입의 요소를 다루는지 명확하게 지정할 수 있다.

 


메서드에서의 Generic 타입 사용

 

public static <T, U> void printString(T ele1, U ele2){
    System.out.println(ele1 + " " + ele2);
}
public static void main(String[] args) {
    printString("Hello", 123);
}

 

제네릭 타입을 사용하는 기본적인 형태이다. 위의 코드는 ele1과 ele2가 어떤 자료형이든 상관 없이 사용할 수 있도록 만든 메서드이다.

<T>는 제네릭 타입 매개변수이다. T는 어떤 타입도 될 수 있다는 것을 메서드를 선언할 때 명시하는 것이다.

보통 제네릭 타입 매개변수는 T나, U등 대문자로 표현한다.

 

 

public static <T> T getElement(List<T> list, int index) {
    if (list == null || index < 0 || index >= list.size()) {
        return null;
    }
    return list.get(index);
}

 

메서드의 Argument 뿐만 아니라 Return type 또한 Generic Type을 사용할 수 있다.

위의 코드는 어떤 리스트의 특정 인덱스의 Element를 반환하는 코드를 작성한 것이다.

 

 

 

Generic의 부분적 제한

public static <T extends Number> T convertNumber(double value, Class<T> targetType) {
    if (targetType == Integer.class) {
        return targetType.cast((int) value); // int 타입으로 변환하여 반환
    } else if (targetType == Double.class) {
        return targetType.cast(value); // double 타입 그대로 반환
    } else {
        throw new IllegalArgumentException("Unsupported target type");
    }
}

 

위 코드는 Value와, Integer.class와 같은 변환할 타입 클래스를 매개변수로 받아 변환해주는 코드이다.

다음 코드 제네릭 타입 매개변수로<T extends Number>를 사용하였다. 이는 Generic Type을 Number 클래스의 하위 클래스만을 대입할 수 있도록 제한한 것이다.

 

 

 

 

와일드카드 사용

public static <T> int getSize(List<T> list) {
    return list.size();
}

public static int getSize2(List<?> list) {
    return list.size();
}

 

모든 자료형을 커버하기 위해서 List<?>와 같은 비한정 와일드카드를 사용할 수 있다.

메서드나 클래스 내부에서 특정한 타입을 사용하지 않고, 모든 타입을 다룰 때 위와 같은 비한정 와일드카드를 사용하며, 모든 타입의 요소를 다룰 수 있는 유연한 코드를 작성할 수 있다.

 

public static <T> int getSize3(List<? extends T> list) {
    T element = list.get(0);
    return list.size();
}

 

메서드 내부에서 특정한 타입을 사용해야 하는 경우에는 와일드카드를 List<? extends T>와 같은 형태로 비한정 와일드카드를 사용하여 제네릭 타입이나 다른 와일드카드의 상위 타입으로 선언하여 사용할 수 있다.

 

 

 


클래스와 인터페이스에서의 Generic 타입 사용

 

 

class GenericClass <T> {
    T element;
    void setElement(T value){
        this.element = value;
    }

    T getElement(){
        return element;
    }
}

public static void main(String[] args) {
    GenericClass<String> g1 = new GenericClass();
    g1.setElement("Hello");
    g1.getElement();

    GenericClass<Integer> g2 = new GenericClass();
    g2.setElement(1234);
    g2.getElement();
}

 

 

제네릭 클래스인 GenericClass<T>를 정의하였다. 제네릭 타입 매개변수 T를 가지며, 이를 사용하여 어떤 타입의 요소도 다룰 수 있도록 하였다.

 

제네릭 클래스의 주요 사용 목적은 각 인스턴스마다 자신만의 타입을 가질 수 있도록 하는 것이다. 위의 코드에서 GenericClass<String>은 문자열 타입의 요소를 다루는 인스턴스이고, GenericClass<Integer>는 정수 타입의 요소를 다루는 인스턴스이다. 

 

 

 

 

 

Collection에서의 Generic

 

 

Collection 또한 Generic Type을 사용하는 대표적인 예시라고 하였다.

실제로 Java Collection 내부를 들여다 보면 다음과 같이 Generic Type을 사용하는 클래스임을 알 수 있다.

 

아래의 코드는 실제 java.util에서 제공하는 클래스와 인터페이스이다.

public interface List<E> extends Collection<E> {

    int size();
    boolean add(E e);
    boolean remove(Object o);
    //...
    
}
public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;

    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }


    public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }

    //...
}

 

ArrayList<E>에서 <E>는 제네릭 타입 매개변수이다. 이것은 요소의 타입을 파라미터화하여 특정한 타입의 요소만을 저장하도록 하는데 사용되었다.

List<E> 인터페이스 또한 제네릭을 사용하여 구현체가 어떤 타입의 요소를 저장할 수 있는지를 명시적으로 지정할 수 있도록 하였다.

 
 
 
 
 

아래의 코드는 Generic을 사용하여 ArrayList의 기능의 일부를 직접 작성하여 흉내낸 것이다.

import java.util.Arrays;

public class MyArrayList<T> {
    private static final int INITIAL_CAPACITY = 10;
    private Object[] array;
    private int size;

    public MyArrayList() {
        array = new Object[INITIAL_CAPACITY];
        size = 0;
    }

    public int size() {
        return size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public void add(T element) {
        ensureCapacity();
        array[size++] = element;
    }

    public T get(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + size);
        }
        return (T) array[index];
    }

    private void ensureCapacity() {
        if (size == array.length) {
            int newCapacity = array.length * 2;
            array = Arrays.copyOf(array, newCapacity);
        }
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (int i = 0; i < size; i++) {
            sb.append(array[i]);
            if (i < size - 1) {
                sb.append(", ");
            }
        }
        sb.append("]");
        return sb.toString();
    }

    public static void main(String[] args) {
        MyArrayList<Integer> list = new MyArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);

        System.out.println("Size of list: " + list.size());
        System.out.println("Elements of list: " + list);

    }
}

 

 

 

 

 

반응형

BELATED ARTICLES

more