Generics

Moom2n·2024년 2월 28일
0

Java

목록 보기
12/26

Generics 개요

다양한 타입에 적용할 수 있는 객체를 만들수 있도록 지원

- Generics 개요

  • 객체 생성시 부여되는 타입을 이용한 파라미터 다형성(Parametric Polymorphism) 구현
  • 타입(Class, Interface) 또는 메소드에 정의
public class Box<T> {
    T item;

    public Box(T item) {
        this.item = item;
    }

    public T getItem() {
        return this.item;
    }
}

class Test {
    public static void main(String[] args) {
        Box<String> box1 = new Box<String> ("abc");
        Box<Integer> box2 = new Box<> (1); 
    }
}

- 강력한 타입 검사

  • Generics는 타입 안정성(Type Safety)를 지원
    -- 타입 파라미터를 통해 전달된 타입 정보를 이용하여 컴파일 타임에 검사

  • java.lang.Object 타입으로 변환되는 객체 형 변환은 런타임에 검사됨
    -- 컴파일시에 오류를 발견할 수 없음
    -- java.lang.ClassCastException 예외 발생

  • Generics는 타입이 명확히 명시되므로 컴파일 타임에 오류를 검출할 수 있음

- 타입 변환 감소

  • Generic을 적용할 경우 별도의 타입 변환 과정을 필요로 하지 않음
public class TypeSafetyGenericsExample {
    public static class Box<T> {
        T item;}
    public static void main(String [] args) {Integer value = box.get();}
}

- 알고리즘 일반화 구현

  • 메소드에도 Generic 을 적용할 수 있다. 메소드 선언 시 Generic 을 표기해야 한다.

아래는 Generic 을 사용한 버블소트 정렬 메소드 예제.

import java.util.Arrays;

public class Box {
    public static <T extends Comparable<T>> void bubbleSort(T[] items) {
        for(int i=items.length-1; i>0; i--) {
            for(int j=0; j<i; j++) {
                if(items[j].compareTo(items[j+1]) > 0) {
                    T item = items[j];
                    items[j] = items[j+1];
                    items[j+1] = item;
                }
            }
        }
    }

    public static void main(String[] args) {
        Integer[] integerList =  {1, 10, 7, 2, 5, 4, 9, 8, 3, 6};
        String[] stringList =  {"기욱", "채호", "John", "Michael", 
        "William", "Noah", "ASAP", "Jacob"};

        System.out.println("정렬 전 : " + Arrays.toString(integerList));
        bubbleSort(integerList);
        System.out.println("정렬 후 : " + Arrays.toString(integerList));

        System.out.println("정렬 전 : " + Arrays.toString(stringList));
        bubbleSort(stringList);
        System.out.println("정렬 후 : " + Arrays.toString(stringList));
    }
}
  • 실행결과
정렬 전 : [1, 10, 7, 2, 5, 4, 9, 8, 3, 6]
정렬 후 : [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
정렬 전 : [James, Robert, John, Michael, William, Noah, Liam, Jacob]
정렬 후 : [Jacob, James, John, Liam, Michael, Noah, Robert, William]

Generic 타입

- Generic 타입 선언

  • 타입 파라미터는 클래스나 인터페이스의 이름뒤에 선언하며, < > 기호 사이에 열거됨
class Pair<K, V> {
    private K key;
    private V value;

    public K getKey() {
        return this. key;
    }

    public V getValue() {
        return this. value;
    }
}

- Generic 타입 생성자

  • Generic 타입 생성자에서는 타입 파라미터를 사용하지 않음
  • 일반 크래스와 동일한 방법으로 생성자 선언
class Box<T> {
    T box;
    public Box(T box) {
        this.box = box;
    }
}

위와 같이 선언한 클래스는 아래와 같은 방식으로 생성할 수 있습니다.

Box<Integer> box = new Box<Integer>(1);

- Generic 객체 생성

  • Generic 타입을 생성할 때 타입 파라미터가 주어져야 함
  • 참조 변수와 생성자에 주어지는 타입 파라미터는 동일해야 함
Box<Integer> box = new Box<Integer>();
Box<String> box = new Box<String>();
  • 오류
Box<Object> s = new Box<String>();

Generic 메소드

- Generic 메소드 선언

  • 일반 메소드 선언과 동일하며, 일반화 할 타입을 타입 파라미터로 선언
  • 메소드 앞에 <> 기호를 사용하여 타입 파라미터 목록을 선언
public class GenericMethod {
    public static <T> List<T> arrayToList(T[] array) {
        List<T> list = new LinkedList<T>();

        for(T t : array) {
            list.add(t);
        }
        return  list;
    }

    public static void main(String[] args) {
        Integer[] array = {1, 2, 3};

        GenericMethod me = new GenericMethod();
        //메소드 호출 시 메소드 이름앞에 <> 기호와 함께 타입 파라미터를 선언해야 합니다.
        List<Integer> list = me.<Integer>arrayToList(array);
    }
}

T 는 레퍼런스 타입이므로 아래의 코드는 컴파일이 불가능하다.

public <T extends Number> T add(T i, T j) {
	return i+j;		// 에러
}

- 타입 추론

  • 메소드 시그너처의 타입 파라미터는 호출시 생략이 가능함
    -- 컴파일러에서 주어진 파라미터를 통해 적용될 타입의 추론이 가능함
public <T> void fromArrayToCollection (T[] array, Collection<T> collection) {
    for(T t : array) {
        collection.add(t);
    }
}

String[] sa = new String[100];
Collection<String> cs = new ArrayList< >();

fromArrayToCollection(sa, cs);	// T는 String으로 추론됨

String[] sa = new String[100];
Collection<Integer> ci = new ArrayList< >();

fromArrayToCollection(sa, ci);	// ERROR : T는 String과 Integer로 추론됨

타입 파라미터

타입 파라미터에는 기본 데이터 타입을 제외한 모든 타입을 사용할 수 있으며, 정의되는 타입 내에서 변수 선언의 타입으로 사용됩니다.

- 타입 파라미터 명명 규칙

타입 파라미터 명명 권장 사항

  • E : 요소 (Element - Java Collections Framework에서 폭 넓게 사용됨)
  • K - 키 (Key)
  • N - 숫자 (Number)
  • T - 타입 (Type)
  • V - 값 (Value)
  • S, U, V 등 - 2번째, 3번째, 4번째 등

- 다중 타입 파라미터

  • 파라미터에는 하나 이상의 타입을 지정할 수 있음
public interface Pair<K, V> {
    public K getKey();
    public V getValue();
}
  • 다중 파라미터 타입도 타입 인자를 통해 타입 파라미터의 추론이 가능한 경우, 생성자에서 타입 인수의 생략이 가능함
Pair<String, Integer> p1 = new OrderPair("Even", 8);
Pair<String, String> p2 = new OrderPair("Hello", "World");

- 타입 제한

  • 타입 파라미터로 전달 가능한 함수는 참조 타입, 즉 클래스나 인터페이스로 제한됨
  • int, boolean등 기본 데이터 타입을 사용해야 하는 경우 Wrapper 클래스를 사용
Pair<String, int> p1 = new OrderedPair<String, int>("Even", 8); 	//ERROR
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);

- 상속 및 서브타입

  • 타입 파라미터의 상속 관계는 Generic 타입의 상속과 무관함
Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK

Generic 타입 상속

  • Generic 클래스 역시 일반 클래스와 동일하게 확장될 수 있습니다. 서브 타입은 슈퍼 타입 Generic 파라미터 보다 더 많은 Generic 파라미터를 가질 수 있습니다.
public class Product<T, M> {
    private T kind;
    private M model;

    public T getKind() {
        return kind;
    }

    public void setKind(T kind) {
        this.kind = kind;
    }

    public M getModel() {
        return model;
    }

    public void setModel(M model) {
        this.model = model;
    }
}

public class ChildProduct<T, M, C> extends Product<T, M> {
    private C company;

    public C getCompany() {
        return company;
    }

    public void setCompany(C company) {
        this.company = company;
    }
}

- 와일드 카드

  • 파라미터나 return 값이 특정한 타입 파라미터를 정의하지 않고 임의의 타입을 지정

  • 무제한 와일드 카드

void genericMethod(Collection<?> collection) { ... }
  • 슈퍼 타입 제한 와일드 카드
void genericMethod(List<? extends Number>) { ... }
  • 서브 타입 제한 와일드 카드
void genericMethod(List<? super Number>) { ... }

타입 삭제

Generics에서 타입 파라미터는 컴파일 타임에만 사용되고, 컴파일 후에는 삭제 후 Object 타입으로 치환되어 바이트 코드에는 Generic에 관한 정보가 저장되지 않습니다.

- Generic에서의 타입 삭제

Generic 타입 파라미터는 컴파일시에 모두 삭제된다. 타입 파라미터가 제한되지 않는 경우, Object 타입으로 변경된다. 타입 제한이 있는 경우, 해당 타입으로 변경됨

public class Node<T extends Comparable<T>> {
    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() {
        return data;
    }
}

해당 소스가 컴파일 시 아래의 코드로 변경된다.

public class Node {
    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() {
        return data;
    }
}

Comparable의 서브 타입으로 Generic 파라미터를 제한합니다. 이 경우 컴파일러는 타입 파라미터 T를 첫 번째 바인딩된 타입인 Comparable로 대치합니다.

- Generic 메소드에서의 타입 삭제

Generic 타입과 동일하게 타입 파라미터는 삭제되며, 타입 제한이 없는 경우 타입 파라미터는 Object 타입으로 변경된다. 타입 제한이 없는 경우 해당 타입으로 변경됨

public static <T extends Shape> void draw(T shape) { ... }

해당 소스가 컴파일 시 아래의 코드로 변경된다.

public static void draw(Shape shape) { ... }

컴파일러는 draw Generic 메소드의 타입 파라미터 T를 Shape로 대치합니다.


Generics 제약

Generic 타입은 일반 타입과 달리 제약 사항들이 있습니다.

- 타입 파라미터 제약

  • 타입 파라미터 인자로 기본 데이터 타입을 사용할 수 없음

  • 타입 파라미터 변수의 객체를 생성할 수 없음

public static <E> void append(List<E> list, Class<E> cls) {
    E elem = cls.newInstance();
    list.add(elem);
}
  • static 필드로 선언할 수 없음

- Generic 타입 제약

  • Generic 타입은 타입 변환 될 수 없으며, instanceof 연산자를 사용할 수 없음

  • Generic 타입의 배열은 생성할 수 없음

  • Exception 타입의 서브 타입이 될 수 없음

// 직접적으로 Exception 클래스 확장
class MathException<T> extends Exception { /* ... */ } // 컴파일시 오류
// Throwable 직접적으로 확장
class QueueFullException<T> extends Throwable { /* ... */ } // 컴파일시 오류

- 메소드 오버로딩 제약

  • 타입 파라미터만 다른 동일한 시그너처의 메소드는 존재할 수 없음
public class Example {
    public void print(Set<String> set) { }
    public void print(Set<Integer> set) { }
}

타입 파라미터는 모두 컴파일시 Object로 치환되기 때문이다.

0개의 댓글

관련 채용 정보