[JAVA] Generics

doxxx·2022년 2월 23일
0

자바 고급 스터디

목록 보기
2/3
post-thumbnail

자바 스터디를 하며 제네릭에 대한 공부를 정리한 내용입니다.

제네릭

제네릭은 클래스나 메서드에서 사용할 내부 데이터 타입을 외부에서 지정하는 기법이다.

제네릭을 사용하는 이유

제네릭의 이점

  • 런타임 오류를 컴파일타임에 수정할 수 있다.
  • 형변환을 제거할 수 있다.
  • 일반 알고리즘을 구현하여 코드 재사용성을 높일 수 있다.

제네릭 타입

제네릭 클래스 또는 인터페이스를 의미한다.

제네릭 클래스

제네릭 클래스란 선언에 타입 매개변수가 쓰인 클래스를 의미하고 다음과 같이 선언한다.

public class Box<T> {
    // T stands for "Type"
    private T t;

    public void set(T t) { this.t = t; }
    public T get() { return t; }
}
  • Box<T>: 제네릭 클래스. T의 박스
  • T: 타입 변수 or 타입 매개변수
  • Box: 원시(raw) 타입

타입 매개변수 명명 규칙

타입 매개변수는 단일 영어 대문자로 표현한다.
E - Element, T - Type 등

제네릭 타입 호출

변수를 전달하는 일반적인 메서드 호출과 유사하지만, 제네릭 타입은 클래스 자체에 타입 변수를 전달한다.

    Box<Integer> integerBox = new Box<>();  
// 1.7 제네릭 클래스의 생성자에서 타입 변수를 생략하여 표현 가능하다. 

지정된 타입 Integer는 매개변수화된 타입이라고 하고 간략하게 대입된 타입 이라고도 한다.

로 타입(Raw Type)

로 타입은 타입 변수가 없는 제네릭 타입을 의미한다.

public class Box<T> {
    public void set(T t) { /* ... */ }
    // ...
}

제네릭 도입 전의 코드와의 호환을 위해 남아있을 뿐이다.
이후의 Type Erasure에서 다루도록 한다.

제네릭 메서드

제네릭 메서드는 지네릭 타입 변수를 전달하는 메서드이다.
제네릭 타입의 선언과 유사하지만, 타입 변수의 범위는 선언된 메서드에 한하게 된다.

// Util 클래스의 compare 제네릭 메서드
public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

이를 호출하는 방법은

    Pair<Integer, String> p1 = new Pair<>(1, "apple");
    Pair<Integer, String> p2 = new Pair<>(2, "pear");
//  boolean same = Util.<Integer, String>compare(p1, p2);   // 타입을 생략할 수 있다.
    boolean same = Util.compare(p1, p2);

제한된 타입 매개변수(Bounded Type Parameter)

타입 매개변수에 지정할 수 있는 타입의 종류를 제한할 때 사용한다.

public class Box<T> {

    private T t;          

    public void set(T t) {
        this.t = t;
    }

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<>();
        integerBox.set(10);
        integerBox.inspect("some text"); // error: this is still String!
    }
}

위는 메서드를 Number와 그 하위 클래스들로 제한한 예시이다.

    <T extends B1 & B2 & B3>

위와 같이 다중으로도 사용이 가능하다.

제네릭 메서드와 제한된 타입 매개변수

제한된 타입 매개 변수의 실용적인 예시를 다룬다.
다음은 배열에서 'elem'보다 큰 요소의 수를 계산 하는 메서드이다.

public static <T> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e > elem)  // compiler error
            ++count;
    return count;
}

위 코드는 컴파일 되지 않는다. >와 같은 연산자는 기본형 타입이 아닌 객체들에는 사용이 불가능 하기 때문이다.
이를 해결하기 위해 Comparable<T> 인터페이스로 제한된 타입 매개변수를 이용한다.

public interface Comparable<T> {
    public int compareTo(T o);
}

다음과 같이 코드를 수정하게 되면 결과를 얻을 수 있다.

public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

제네릭, 상속 및 서브타입

제네릭 타입 또한 상속과 포함관계를 갖는다.

  • Box<Interger>Box<Number>의 서브타입이 아니다. Integer와 Number의 관계는 상관이 없다.
  • Box<Interger>Box<Number>의 조상은 Object이다.

제네릭 클래스와 서브타이핑

그렇다면

interface PayloadList<E,P> extends List<E> {
  void setPayload(int index, P val);
 /* ... */
}

// is a 관계.. 공부 더 필요! Help

https://docs.oracle.com/javase/tutorial/java/generics/inheritance.html

타입 추론

자바 컴파일러가 메서드 선언부와 정의를 확인하여 타입을 추론하는 것이다.
추론 알고리즘은 인자의 타입을 결정하고, 가능한 경우에 결과가 할당되는 타입 또는 리턴되는 타입까지 결정한다.
추론 알고리즘은 모든 인자와 어울리는 선(공통 부모)에서 가장 구체적인 타입을 찾는다.

타입 추론과 제네릭 메서드

타입 추론 덕분에 제네릭 메서드를 사용할 때 보통의 메서드와 같이 특정 타입을 명시하지 않은채 호출할 수 있다.

// 내용추가 예정
https://docs.oracle.com/javase/tutorial/java/generics/genTypeInference.html

와일드카드

제네릭 코드에서 ? 는 알수 없는 타입을 의미한다.
매개변수의 타입, 필드, 지역변수, 리턴 타입 등으로 쓰인다.

상한 와일드카드

상한 와일드카드는 변수에 대한 제한을 완화할 수 있다.
상한 와일드카드는 알 수 없는 타입을 특정 타입과 그 자손들로 제한한다.

public static void process(List<? extends Foo> list) { /* ... */ }

위의 process 메서드는 Foo와 Foo의 서브타입에 접근 가능하다.

예시로 다음 sumOfList는 배열에 있는 수의 합을 반환한다.

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

위 코드를 Integer값과 Double값에 대해 메서드를 사용해보자

List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));
// sum = 6.0
List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));
// sum = 7.0

비한정적 와일드카드(Unbounded Wildcard)

언바운드 와일드카드 타입은 ?를 사용하여 나타낸다. 예를 들어 List<?>는 Unknown 타입 리스트 라고 부른다.
언바운드 와일드카드를 사용하는 경우는 다음과 같다.

  • Object클래스가 제공하는 기능을 사용하여 구현할 수 있는 메서드를 작성할 때
  • 타입 매개변수에 의존하지 안흔 제네릭 클래스의 메서드를 사용할 때
    • 예시: List.size , List.clear, Class<?>
      간단하게 제네릭 타입을 쓰고 싶지만 타입 매개변수가 무엇인지 신경쓰고 싶지않을 때 쓴다.

예시

다음 printList메서드를 예를 들어보자.
리스트의 임의의 값을 출력하기 위한 printList메서드를 작성하려고 한다.

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

위의 경우엔 Object인수만을 출력한다.
따라서 List<Integer>, List<String>, List<Double>List<Object>의 서브타입이 아니므로 출력이 불가능 하다.

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

List<?> 제네릭 메서드를 사용하여 이를 해결할 수 있다.

하한 와일드카드

상한 와일드카드와 유사하다.
하한 와일드카드는 알 수 없는 타입을 특정 타입과 그 조상들로 제한한다.

만약, Integer객체들을 리스트에 추가하는 메서드를 만들고 싶다면,
Integer값을 포함할 수 있는 List<Integer>, List<Number>List<Object>에서도 동작해야 한다.

따라서List<? super Integer>를 사용한다.

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

와일드카드와 서브타이핑

제네릭, 상속 및 서브타입과 연결되는 내용이다.
와일드카드를 사용하여 제네릭 클래스나 인터페이스간의 관계를 만들 수 있다.


와일드카드 가이드라인

와일드카드의 상한과 하한을 언제 사용할지 결정하기 위한 지침이다.
copy(src, dest)메서드를 예를 들어보자

  • 입력 매개변수
    • src는 복사할 데이터를 제공한다.
  • 출력 매개변수
    • dest는 다른 곳에서 사용할 데이터를 보관한다.

와일드카드 사용 여부와 종류는 다음 지침을 따라 사용한다.

  • 입력 매개변수는 extends를 이용한 상한 와일드카드로 정의한다.
  • 출력 매개변수는 super를 이용한 하한 와일드카드로 정의한다.
  • 입력 매개변수가 Object클래스에 정의된 메서드로 접근 가능하면 언바운드 와일드카드로 정의한다.
  • 매개변수가 입력과 출력으로 모두 접근한다면 와일드 카드를 사용하지 않는다.

0개의 댓글