본문 바로가기
Java Tutorials

#19 Lesson: Generics 4

by xogns93 2024. 8. 1.

Type Erasure

제네릭은 컴파일 타임에 더 엄격한 타입 체크를 제공하고 제네릭 프로그래밍을 지원하기 위해 Java 언어에 도입되었습니다. 제네릭을 구현하기 위해 Java 컴파일러는 타입 소거(Type Erasure)를 다음에 적용합니다:

  • 제네릭 타입에 제한[bounds] 있다면 제한 타입으로, 만약 타입 파라미터가 제한이 없다면 Object로 대체합니다. 따라서 생성된 바이트코드는 일반 클래스, 인터페이스, 메서드만을 포함합니다.
  • 타입 안전을 유지하기 위해 필요한 경우 타입 캐스트를 삽입하십시오.
  • 확장된 제네릭 타입에서 다형성을 유지하기 위해 브리지 메서드를 생성합니다.

타입 소거는 파라미터화된 타입에 대해 새 클래스가 생성되지 않도록 합니다. 결과적으로 제네릭은 런타임 오버헤드를 발생시키지 않습니다.

파라미터화된 타입: 제네릭 타입을 특정 타입으로 지정한 것 (예: Box<String>).
또한 "파라미터화된 타입"이란 제네릭 타입에 구체적인 타입 아규먼트를 제공하여 특정한 타입을 지정한 것을 의미.

 

Erasure of Generic Types

타입 소거 과정에서, 자바 컴파일러는 모든 타입 파라미터를 지우고, 각 타입 파라미터를 제한[bounds]이 있는 경우 첫 번째 제한 타입으로, 제한이 없는 경우 Object로 대체합니다.

 

단일 연결 리스트의 노드를 나타내는 다음 일반 클래스를 고려하십시오:

public class Node<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; }
    // ...
}

 

타입 파라미터 T는 제한(bounds)이 없기 때문에, 아래 코드처럼 Java 컴파일러는 이를 Object로 바꿉니다.

public class Node {

    private Object data;
    private Node next;

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

    public Object getData() { return data; }
    // ...
}

 

다음 예제에서 일반 Node 클래스는 제한된 타입 파라미터를 사용합니다.

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; }
    // ...
}

 

Java 컴파일러는 경계 타입 파라미터 T를 첫 번째 제 클래스인 Comparable로 바꿉니다.

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; }
    // ...
}

 

& 키워드를 사용하여 여러 제한을 선언한 예제 코드

public class MultiBoundExample<T extends Number & Comparable<T>> {
    private T value;

    public MultiBoundExample(T value) {
        this.value = value;
    }

    public void show() {
        System.out.println("Value: " + value);
    }

    public T getValue() {
        return value;
    }
}

 

타입 소거 후의 위 예제는 다음과 같이 될 것입니다:

public class MultiBoundExample {
    private Number value;

    public MultiBoundExample(Number value) {
        this.value = value;
    }

    public void show() {
        System.out.println("Value: " + value);
    }

    public Number getValue() {
        return value;
    }
}

 

Erasure of Generic Methods

Java 컴파일러는 제너릭 메소드 또한 아규먼트에서 타입 파라미터를 지웁니다. 다음과 같은 일반적인 방법을 고려하십시오.

// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

 

타입 파라미터 T는 제한(bounds)이 없기 때문에 Java 컴파일러는 이를 Object로 바꿉니다.

public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

 

다음 클래스가 정의되어 있다고 가정합니다.

class Shape { /* ... */ }
class Circle extends Shape { /* ... */ }
class Rectangle extends Shape { /* ... */ }

 

다른 모양을 그리는 제너릭 메서드를 작성할 수 있습니다.

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

 

자바 컴파일러는 T를 Shape으로 대체합니다:

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

 

Effects of Type Erasure and Bridge Methods

때때로 타입 소거로 인해 예상하지 못한 상황이 발생할 수 있습니다. 다음 예에서는 이것이 어떻게 발생하는지 보여줍니다. 다음 예제는 컴파일러가 때때로 타입 소거 프로세스의 일부로 브리지 메서드라고 하는 합성 메서드를 생성하는 방법을 보여줍니다.

다음 두 클래스가 주어집니다.

public class Node<T> {

    public T data;

    public Node(T data) { this.data = data; }

    public void setData(T data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node<Integer> {
    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

 

다음 코드를 고려하십시오.

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = mn.data;

 

위 코드를 컴파일러가 타입 소거하면 다음과 같은 코드를 예상해 볼 수 있습니다.

MyNode mn = new MyNode(5);
Node n = mn;            // A raw type - compiler throws an unchecked warning
                        // Note: This statement could instead be the following:
                        //     Node n = (Node)mn;
                        // However, the compiler doesn't generate a cast because
                        // it isn't required.
n.setData("Hello");     // Causes a ClassCastException to be thrown.
Integer x = (Integer)mn.data;

 

다음 섹션에서는 n.setData("Hello");에서 ClassCastException이 발생하는 이유를 설명합니다. 

 

Bridge Methods

파라미터화된 클래스[public class MyNode extends Node<Integer>]를 확장하거나 파라미터된 인터페이스를 구현하는 클래스 또는 인터페이스를 컴파일할 때 컴파일러는 타입 소거 프로세스의 일부로 브리지 메서드라고 하는 합성 메서드를 생성합니다. 일반적으로 브리지 메서드에 대해 걱정할 필요는 없지만 스택 추적에 브리지 메서드가 나타나면 당황할 수 있습니다.

 

타입 소거후에, Node와 MyNode 클래스는 다음과 같이 될 것으로 생각됩니다.

public class Node {

    public Object data;

    public Node(Object data) { this.data = data; }

    public void setData(Object data) {
        System.out.println("Node.setData");
        this.data = data;
    }
}

public class MyNode extends Node {

    public MyNode(Integer data) { super(data); }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }
}

 

타입 소거 후 메서드 시그니처가 일치하지 않습니다. Node.setData(T) 메서드는 Node.setData(Object)가 됩니다. 결과적으로 MyNode.setData(Integer) 메서드는 Node.setData(Object) 메서드를 재정의하지 않습니다.

 

이 문제를 해결하고 타입 소거 후 제너릭 타입의 polymorphism을 유지하기 위해 Java 컴파일러는 하위 타입 지정이 예상대로 작동하는지 확인하는 브리지 메서드를 생성합니다.

MyNode 클래스의 경우 컴파일러는 setData에 대해 다음 브리지 메서드를 생성합니다.

class MyNode extends Node {

    // Bridge method generated by the compiler
    //
    public void setData(Object data) {
        setData((Integer) data);
    }

    public void setData(Integer data) {
        System.out.println("MyNode.setData");
        super.setData(data);
    }

    // ...
}

 

디버깅시, 컴파일러에 의해 생성된 setData(Object) 브리지 메서드를 확인할 수 있습니다.

 

브리지 메서드 MyNode.setData(object)는 원래의 MyNode.setData(Integer) 메서드에 위임합니다. 결과적으로 n.setData("Hello"); 명령문은 MyNode.setData(Object) 메서드를 호출하고 "Hello"를 Integer로 캐스팅할 수 없기 때문에 ClassCastException이 발생합니다.

 

 

Non-Reifiable Types[비검증(비구체화) 타입]

타입 소거(Type Erasure)라는 섹션은 컴파일러가 타입 파라미터와 타입 아규먼트에 관련된 정보를 제거하는 과정을 설명합니다. 타입 소거는 non-reifiable 타입을 갖는 varargs 메서드의 가변 인자(formal parameter)에 영향을 미칩니다. varargs 메서드에 대한 자세한 내용은 메서드 또는 생성자에 정보를 전달할 때 아규먼트의 임의 개수 섹션을 참조하세요.

 

이 페이지에서는 다음 주제를 다룹니다:

  • Non-Reifiable Types
  • Heap Pollution
  • Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters
  • Preventing Warnings from Varargs Methods with Non-Reifiable Formal Parameters

 

Non-Reifiable Types

 

 Reifiable 타입은 런타임에 타임 정보를 완전히 사용할 수 있는 타입입니다. 

  • Primitive 타입
  • Non-Generics 타입
  • Raw 타입
  • unbounded wildcard<?> 호출

 

 Non-Reifiable 타입은 타입 소거(제한되지 않은-unbounded- 와일드카드로 정의되지 않은 일반 타입의 호출)에 의해 컴파일 타임에 정보가 제거된 유형입니다.

 

Non-Reifiable 타입은 런타임에 모든 정보를 사용할 수 없습니다. Non-Reifiable 타입의 예로는 List<String> 및 List<Number>가 있습니다. JVM은 런타임 시 이러한 타입 간의 차이를 구분할 수 없습니다. 제네릭에 대한 제한에서 볼 수 있듯이 Non-Reifiable 타입을 사용할 수 없는 특정 상황이 있습니다. 예를 들어, instanceof 표현식이나 배열의 엘리먼로 사용할 수 없습니다.

 

Non-reifiable 타입이 instanceof 연산자와 배열의 엘리먼트로 사용할 수 없음을 보여주는 예시 코드:

1. instanceof 연산자의 피연산자로 Non-Reifiable 타입 사용 불가

public class Example {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();

        boolean isList = stringList instanceof List<String>;  // 컴파일 에러!
        // instanceof 연산자의 피연산자로 Non-reifiable 타입 사용 불가
    }
}

 

위의 예시에서 stringList는 List<String> 타입의 객체입니다. 그러나 stringList instanceof List<String> 표현식은 컴파일 에러가 발생합니다. Non-Reifiable 타입인 List<String>은 instanceof 연산자의 피연산자로 사용할 수 없습니다.

2. 배열의 엘리먼트로 Non-reifiable 타입 사용 불가

public class Example {
    public static void main(String[] args) {
        List<String>[] stringLists = new List<String>[5];  // 컴파일 에러!
        // 배열의 요소로 비검증 가능한 타입 사용 불가
    }
}

위의 예시에서 stringLists는 List<String> 타입의 배열입니다. 그러나 new List<String>[5] 표현식은 컴파일 에러가 발생합니다. Non-Reifiable 타입인 List<String>은 배열의 요소로 사용할 수 없습니다.

위의 두 예시에서 Non-Reifiable 타입인 List<String>을 instanceof 연산자의 피연산자로 사용하거나, 배열의 엘리먼트로 사용하려고 하면 컴파일 에러가 발생합니다. 이는 Non-Reifiable 타입이 일부 제약 사항을 가지고 있어서입니다.

import java.util.ArrayList;
import java.util.List;

public class NonReifiableExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<Number> numberList = new ArrayList<>();

        // 컴파일러는 이 코드를 컴파일할 때 제네릭 타입을 검사하지만,
        // 런타임에는 제네릭 타입 정보가 소거됩니다.
        
        System.out.println("stringList.getClass(): " + stringList.getClass());
        System.out.println("numberList.getClass(): " + numberList.getClass());

        // 두 클래스의 런타임 타입을 비교
        boolean sameClass = stringList.getClass() == numberList.getClass();
        System.out.println("Are stringList and numberList of the same class at runtime? " + sameClass);
    }
}

 

Heap Pollution

파라미터된 타입의 변수가 해당 파라미터된 타입이 아닌 객체를 참조할 때 힙 오염이 발생합니다. 이 상황은 프로그램이 컴파일 타임에 확인되지 않은[unchecked] 경고를 발생시키는 일부 작업을 수행한 경우에 발생합니다. unchecked 경고는 컴파일 시간(컴파일 시간 타입 검사 규칙의 제한 내) 또는 런타임에 파라미터된 타입(예: 캐스트 또는 메서드 호출)과 관련된 작업의 정확성을 확인할 수 없는 경우 생성됩니다. 확인. 예를 들어 raw 타입과 파라미터된 타입을 혼합하거나 uncheckde 캐스트를 수행할 때 힙 오염이 발생합니다.

 

일반적인 상황에서 모든 코드가 동시에 컴파일되면 컴파일러는 잠재적인 힙 오염에 주의를 환기시키기 위해 unchecked 경고를 발생시킵니다. 코드 섹션을 별도로 컴파일하면 힙 오염의 잠재적 위험을 감지하기 어렵습니다. 코드가 경고 없이 컴파일되는지 확인하면 힙 오염이 발생하지 않습니다.

 

힙 오염이 발생하는 예시들:

import java.util.ArrayList;
import java.util.List;

public class HeapPollutionExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<Object> objectList = (List<Object>) stringList; // 비검증 가능한 타입의 형변환

        objectList.add(10); // 잘못된 유형의 객체를 추가

        String value = stringList.get(0); // ClassCastException 발생: 잘못된 유형의 객체를 가져옴
    }
}

 

위의 코드에서, stringList는 List<String> 타입의 리스트입니다. 그러나 objectList에 타입 캐스팅을 통해 할당할 때 비검증 가능한(non-reifiable) 타입이 됩니다. 이후 objectList에 add 메서드를 사용하여 잘못된 타입의 객체인 Integer 클래스 객체를 추가합니다. 이는 힙 오염을 발생시키는 예시입니다. 마지막으로 stringList에서 첫 번째 엘리먼트를 가져올 때 ClassCastException이 발생하게 됩니다. 이는 힙 오염으로 인해 잘못된 타입의 객체가 가져와졌음을 나타냅니다.

import java.util.ArrayList;
import java.util.List;

public class HeapPollutionExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<?> unboundedList = stringList; // 와일드카드로 비검증 가능한 타입 사용

        unboundedList.add(10); // 힙 오염 발생: 잘못된 유형의 객체를 추가

        String value = stringList.get(0); // 컴파일 에러: 잘못된 유형의 객체를 가져오려고 함
    }
}

 

위의 코드에서, stringList는 List<String> 타입의 리스트입니다. 그러나 이 리스트가 unboundedList에 와일드카드(List<?>)로 할당될 때, 비검증 가능한(non-reifiable) 타입이 됩니다. unboundedList에 add 메서드를 사용하여 잘못된 타입의 객체인 Integer 클래스 객체를 추가하면 힙 오염이 발생합니다. 마지막으로 stringList에서 첫 번째 엘리먼트를 가져오려고 하면 컴파일 에러가 발생합니다. 이는 힙 오염으로 인해 잘못된 타입의 객체를 가져오려고 하기 때문입니다.

이 예시에서도 힙 오염이 발생하는데, unboundedList에 비검증 가능한(non-reifiable) 타입인 와일드카드(List<?>)를 사용했기 때문입니다. 와일드카드 타입은 특정한 타입의 객체를 추가할 수 없으므로, 잘못된 타입의 객체를 추가하는 시도가 힙 오염을 발생시킵니다.

import java.util.ArrayList;
import java.util.List;

public class HeapPollutionExample {
    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        List<List<?>> nestedList = new ArrayList<>();
        nestedList.add(stringList); // 비검증 가능한 타입을 포함하는 리스트 추가

        List<Integer> integerList = new ArrayList<>();
        nestedList.add(integerList); // 비검증 가능한 타입을 포함하는 리스트 추가

        List<?> list1 = nestedList.get(0);
        List<?> list2 = nestedList.get(1);

        list1.add(10); // 힙 오염 발생: 잘못된 타입의 객체를 추가
        list2.add("Hello"); // 힙 오염 발생: 잘못된 타입의 객체를 추가

        String value = stringList.get(0); // 컴파일 에러: 잘못된 타입의 객체를 가져오려고 함
        Integer number = integerList.get(0); // 컴파일 에러: 잘못된 타입의 객체를 가져오려고 함
    }
}

 

위의 코드에서, stringList는 List<String> 타입의 리스트입니다. 이 리스트를 nestedList에 추가하고, 그 후에 integerList를 nestedList에 추가합니다. 이로 인해 nestedList는 비검증 가능한(non-reifiable) 타입인 List<?>를 포함하는 리스트가 됩니다.
그 다음, list1과 list2는 nestedList에서 가져온 리스트입니다. 이들은 와일드카드(List<?>) 타입으로 선언되었습니다. 그러나 list1.add(10)과 list2.add("Hello")에서 잘못된 타입의 객체를 추가하면 힙 오염이 발생합니다. 이는 힙 오염으로 인해 잘못된 타입의 객체가 리스트에 추가되었음을 나타냅니다.
마지막으로 stringList와 integerList에서 엘리먼트를 가져오려고 하면 컴파일 에러가 발생합니다. 이는 힙 오염으로 인해 잘못된 유형의 객체를 가져오려고 했기 때문입니다.

위의 예시에서 힙 오염은 비검증 가능한(non-reifiable) 타입인 List<?>를 다룰 때 발생합니다. 다른 유형의 리스트를 nestedList에 추가하면서 잘못된 유형의 객체가 리스트에 포함되게 되고, 이로 인해 힙 오염이 발생합니다. 힙 오염을 피하기 위해서는 제너릭 타입을 정확하게 다루고, 올바른 유형의 객체만 추가하고 가져오도록 주의해야 합니다.

 

Potential Vulnerabilities of Varargs Methods with Non-Reifiable Formal Parameters

( 비검증 가능한 formal 파라미터를 사용하는 Varargs 메서드의 잠재적인 취약점)

varargs 입력 파라미터를 포함하는 제네릭 메서드는 힙 오염을 일으킬 수 있습니다.

 

다음의 ArrayBuilder 클래스를 고려해 보세요:

public class ArrayBuilder {

  public static <T> void addToList (List<T> listArg, T... elements) {
    for (T x : elements) {
      listArg.add(x);
    }
  }

  public static void faultyMethod(List<String>... l) {
    Object[] objectArray = l;     // Valid
    objectArray[0] = Arrays.asList(42);
    String s = l[0].get(0);       // ClassCastException thrown here
  }

}

 

다음 예제인 HeapPollutionExample에서는 ArrayBuiler 클래스를 사용합니다.

public class HeapPollutionExample {

  public static void main(String[] args) {

    List<String> stringListA = new ArrayList<String>();
    List<String> stringListB = new ArrayList<String>();

    ArrayBuilder.addToList(stringListA, "Seven", "Eight", "Nine");
    ArrayBuilder.addToList(stringListB, "Ten", "Eleven", "Twelve");
    List<List<String>> listOfStringLists =
      new ArrayList<List<String>>();
    ArrayBuilder.addToList(listOfStringLists,
      stringListA, stringListB);

    ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));
  }
}

 

컴파일할 때 ArrayBuilder.addToList 메서드의 정의에 의해 다음 경고가 생성됩니다.

warning: [varargs] Possible heap pollution from parameterized vararg type T

 

컴파일러는 varargs 메서드를 만나면 varargs formal 파라미터를 배열로 변환합니다. 그러나 Java 프로그래밍 언어는 파라미터된 타입의 배열 작성을 허용하지 않습니다. 메서드 ArrayBuilder.addToList에서 컴파일러는 varargs formal 파라미터 T... 엘리먼트를 formal 파라미터 T[] 요소인 배열로 변환합니다. 그러나 타입 소거로 인해 컴파일러는 varargs formal 파라미터를 Object[] 요소로 변환합니다. 결과적으로 더미 오염의 가능성이 있습니다.

 

다음 코드 스테이트먼트는  varargs formal 파라미터 l을 Object 배열 objectArgs에 할당합니다.

Object[] objectArray = l;

 

이 코드 스테이트먼트는 잠재적으로 힙 오염을 일으킬 수 있습니다. varargs formal 파라미터 l의 파라미터화된 타입과 일치하는 값을 변수 objectArray에 할당할 수 있으므로 l 에 할당할 수 있습니다. 그러나 컴파일러는 이 코드 스테이트먼트에서 확인되지 않은 경고를 생성하지 않습니다. 컴파일러는 varargs formal 파라미터 List<String>... l을 formal 파라미터 List[] l로 변환할 때 이미 경고를 생성했습니다. 이 코드 스테이트먼트는 유효합니다. 변수 l은 Object[]의 subtype인 List[] 타입을 가집니다.

 

결과적으로 컴파일러는 다음 코드 스테이트먼트에 표시된 대로 objectArray 배열의 엘리먼트에 모든 타입의 List 객체를 할당하는 경우 경고 또는 오류를 발행하지 않습니다.
objectArray[0] = Arrays.asList(42);

이 코드 스테이트먼트는 Integer 타입의 객체 하나를 포함하는 List 객체와 함께 objectArray 배열의 첫 번째 엘리먼트에 할당합니다.

 

다음 코드 스테이트먼트로 ArrayBuilder.faultyMethod를 호출한다고 가정합니다.

ArrayBuilder.faultyMethod(Arrays.asList("Hello!"), Arrays.asList("World!"));

런타임 시 JVM은 다음 코드 스테이트먼트에서 ClassCastException을 발생시킵니다.

// ClassCastException thrown here
String s = l[0].get(0);

변수 l의 첫 번째 배열 엘리먼트에 저장된 객체의 타입은 List<Integer>이지만 이 코드 스테이트먼트는 List<String> 타입의 객체를 예상합니다.

 

 

Prevent Warnings from Varargs Methods with Non-Reifiable Formal Parameters

(Non-Reifiable formal 파라미터를 사용하여 Varargs 메서드의 경고 방지)

파라미터화된 타입의 파라미터가 있는 varargs 메서드를 선언하고 메서드 본문이 varargs formal 파라미터의 부적절한 처리로 인해 ClassCastException 또는 기타 유사한 예외를 throw하지 않는지 확인하면 컴파일러가 다음과 같은 경고를 방지할 수 있습니다. 정적 및 비 생성자 메서드 선언에 다음 어노테이션을 추가하여 이러한 종류의 varargs 메서드를 생성합니다.

@SafeVarargs

 

@SafeVarargs 어노테이션은 메서드 contract의 문서화된 부분입니다. 이 어노네이션은 메소드의 구현이 varargs formal 파라미터를 부적절하게 처리하지 않을 것이라고 주장합니다.

바람직하지는 않지만 메소드 선언에 다음을 추가하여 이러한 경고를 억제하는 것도 가능합니다.

@SuppressWarnings({"unchecked", "varargs"})

 

그러나 이 방법은 메서드 호출 사이트에서 생성된 경고를 억제하지 않습니다. @SuppressWarnings 스테이트먼트에 익숙하지 않은 경우 이 어노테이션을 참조하십시오.

 

 

Restrictions on Generics

To use Java generics effectively, you must consider the following restrictions:

 

Restrictions on Generics (The Java™ Tutorials > Learning the Java Language > Generics (Updated))

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

Restrictions on Generics (The Java™ Tutorials > Learning the Java Language > Generics (Updated))

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

Restrictions on Generics (The Java™ Tutorials > Learning the Java Language > Generics (Updated))

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com

 

Cannot Instantiate Generic Types with Primitive Types

Consider the following parameterized type:

class Pair<K, V> {

    private K key;
    private V value;

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

    // ...
}

 

When creating a Pair object, you cannot substitute a primitive type for the type parameter K or V:

Pair 객체를 생성할 때, 타입 파라미터 K 또는 V에  primitive 타입으로 대체할 수 없습니다:

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

 

You can substitute only non-primitive types for the type parameters K and V:

타입 파라미터 K와 V에는 오직 non-primitive 타입만 대체할 수 있습니다:

Pair<Integer, Character> p = new Pair<>(8, 'a');

Note that the Java compiler autoboxes 8 to Integer.valueOf(8) and 'a' to Character('a'):

자바 컴파일러는 8을 Integer.valueOf(8)로, a를 Character(a)로 오토박싱한다는 것을 주목하세요:

Pair<Integer, Character> p = new Pair<>(Integer.valueOf(8), new Character('a'));

For more information on autoboxing, see Autoboxing and Unboxing in the Numbers and Strings lesson.

 

Cannot Create Instances of Type Parameters

You cannot create an instance of a type parameter. For example, the following code causes a compile-time error:

타입 파라미터의 인스턴스를 생성할 수 없습니다. 예를 들어, 다음 코드는 컴파일 시간 에러를 발생시킵니다:

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

As a workaround, you can create an object of a type parameter through reflection:

해결 방법으로, 리플렉션을 통해 타입 파라미터의 객체를 생성할 수 있습니다:

public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}

You can invoke the append method as follows:

다음과 같이 append 메소드를 호출할 수 있습니다:

List<String> ls = new ArrayList<>();
append(ls, String.class);

 

Cannot Declare Static Fields Whose Types are Type Parameters

A class's static field is a class-level variable shared by all non-static objects of the class. Hence, static fields of type parameters are not allowed. Consider the following class:

클래스의 static 필드는 클래스의 모든 non-static 객체에 의해 공유되는 클래스 레벨의 변수입니다. 따라서, 타입 파라미터의 static 필드는 허용되지 않습니다. 다음 클래스를 고려해보세요:

public class MobileDevice<T> {
    private static T os;

    // ...
}

If static fields of type parameters were allowed, then the following code would be confused:

타입 파라미터의 static 필드가 허용된다면, 다음 코드는 혼란스러워질 것입니다:

MobileDevice<Smartphone> phone = new MobileDevice<>();
MobileDevice<Pager> pager = new MobileDevice<>();
MobileDevice<TabletPC> pc = new MobileDevice<>();

Because the static field os is shared by phone, pager, and pc, what is the actual type of os? It cannot be Smartphone, Pager, and TabletPC at the same time. You cannot, therefore, create static fields of type parameters.

static 필드 os가 전화기, 호출기, 그리고 개인용 컴퓨터에 의해 공유되기 때문에, os의 실제 타입이 무엇인가요? 이것은 동시에 스마트폰, 호출기, 그리고 태블릿 PC일 수는 없습니다. 따라서, 타입 파라미터의 static 필드를 생성할 수 없습니다.

 

Cannot Use Casts or instanceof with Parameterized Types

Because the Java compiler erases all type parameters in generic code, you cannot verify which parameterized type for a generic type is being used at runtime:

자바 컴파일러가 제네릭 코드에서 모든 타입 파라미터를 지우기 때문에, 런타임에 어떤 파라미터화된 타입이 사용되고 있는지 확인할 수 없습니다:

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

The set of parameterized types passed to the rtti method is:

rtti 메소드에 전달된 파라미터화된 타입들의 세트는:

S = { ArrayList<Integer>, ArrayList<String> LinkedList<Character>, ... }

The runtime does not keep track of type parameters, so it cannot tell the difference between an ArrayList<Integer> and an ArrayList<String>. The most you can do is to use an unbounded wildcard to verify that the list is an ArrayList:

런타임은 타입 파라미터를 추적하지 않으므로, ArrayList<Integer>와 ArrayList<String>의 차이를 알 수 없습니다. 할 수 있는 최대한은 unbounded 와일드카드를 사용하여 리스트가 ArrayList인지 확인하는 것입니다:

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

Typically, you cannot cast to a parameterized type unless it is parameterized by unbounded wildcards. For example:

일반적으로, unbounded 와일드카드로 파라미터화되지 않은 이상, 파라미터화된 타입으로 캐스팅할 수 없습니다. 예를 들어:

List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // compile-time error

However, in some cases the compiler knows that a type parameter is always valid and allows the cast. For example:

그러나, 어떤 경우에는 컴파일러가 타입 파라미터가 항상 유효하다는 것을 알고 캐스트를 허용합니다. 예를 들어:

List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;  // OK

 

Cannot Create Arrays of Parameterized Types

You cannot create arrays of parameterized types. For example, the following code does not compile:

파라미터화된 타입의 배열을 생성할 수 없습니다. 예를 들어, 다음 코드는 컴파일되지 않습니다:

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

The following code illustrates what happens when different types are inserted into an array:

다음 코드는 다른 타입들이 배열에 삽입될 때 발생하는 상황을 보여줍니다:

Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.

If you try the same thing with a generic list, there would be a problem:

같은 것을 제네릭 리스트로 시도한다면 문제가 발생할 것입니다:

Object[] stringLists = new List<String>[2];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown,
                                            // but the runtime can't detect it.

If arrays of parameterized lists were allowed, the previous code would fail to throw the desired ArrayStoreException.

파라미터화된 리스트의 배열이 허용된다면, 이전 코드는 원하는 ArrayStoreException을 발생시키지 못할 것입니다.

Cannot Create, Catch, or Throw Objects of Parameterized Types

A generic class cannot extend the Throwable class directly or indirectly. For example, the following classes will not compile:

제네릭 클래스는 Throwable 클래스를 직접적이거나 간접적으로 확장할 수 없습니다. 예를 들어, 다음 클래스들은 컴파일되지 않을 것입니다:

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

 

A method cannot catch an instance of a type parameter:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}

 

You can, however, use a type parameter in a throws clause:

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}

 

Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type

각 오버로드의 formal parameter 타입이 동일한 raw 타입으로 소거되는 경우, 메소드를 오버로드할 수 없습니다

 

A class cannot have two overloaded methods that will have the same signature after type erasure.

클래스는 타입 소거 후에 동일한 시그니처를 가질 두 개의 오버로드된 메소드를 가질 수 없습니다.

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

The overloads would all share the same classfile representation and will generate a compile-time error.

이 오버로드들은 모두 같은 클래스 파일 표현을 공유하며 컴파일 시간 에러를 발생시킬 것입니다.

'Java Tutorials' 카테고리의 다른 글

#21 Lesson: Annotations  (0) 2024.08.02
#20 Lesson: Packages  (0) 2024.08.02
#18 Lesson: Generics 3  (1) 2024.07.29
#17 Lesson: Generics 2  (1) 2024.07.29
#16 Lesson: Generics 1  (1) 2024.07.28