Generics_2

KKH_94·2023년 6월 19일
0

JAVA

목록 보기
32/36

Type Inference


타입 유추는 메서드 호출에 적용할 수 있는 타입 아규먼트(또는 아규먼트)를 결정하기 위해 각 메소드 호출 및 해당 메서드 선언을 살펴보는 Java 컴파일러의 기능입니다.

추론 알고리즘은 아규먼트의 타입과 (가능한 경우)반환되는 타입을 결정합니다. 마지막으로 추론 알고리즘은 모든 아규먼트와 함께 작동하는 특정 타입을 찾으려고 시도합니다.

이 마지막 요점을 설명하기 위해 다음 예제에서 타입 추론은 pick 메서드에 전달되는 두 번째 아규먼트가 Serializable 유형임을 확인합니다.

static <T> T pick(T a1, T a2) { return a2; } // T = single type parameter
Serializable s = pick("d", new ArrayList<String>());

위 코드를 컴파일러가 (T a1, T a2) 두개를 다 보고 type inference 함. 컴파일러는 T를 seriallizable로 결정.

Serializable s = <String, ArrayList<String>>pick("d", new ArrayList<String>());


Type Inference(타입 추론) and Generic Methods


제네릭 메서드는 꺾쇠 괄호 사이에 타입을 지정하지 않고 일반 메서드처럼 일반 메서드를 호출할 수 있는 타입 유추를 도입했습니다.

Box 클래스가 필요한 다음 예제 BoxDemo를 고려하십시오.

public class BoxDemo {

  public static <U> void addBox(U u, 
      java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
  } // U = Integer.valueOf(10)[integer type].

  public static <U> void outputBoxes(java.util.List<Box<U>> boxes) {
    int counter = 0;
    for (Box<U> box: boxes) {
      U boxContents = box.get();
      System.out.println("Box #" + counter + " contains [" +
             boxContents.toString() + "]");
      counter++;
    }
  } // U = integer

  public static void main(String[] args) {
    java.util.ArrayList<Box<Integer>> listOfIntegerBoxes =
      new java.util.ArrayList<>();
    BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
    //addBox generic method.
    BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);
    BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes);
    BoxDemo.outputBoxes(listOfIntegerBoxes);
  }
}// listOfIntegerBoxes의 type은 Integer.

The following is the output from this example.

Box #0 contains [10]
Box #1 contains [20]
Box #2 contains [30]

제네릭 메소드 addBox는 U라는 하나의 타입 파라미터를 정의합니다.

일반적으로 Java 컴파일러는 제네릭 메소드 호출의 타입 파라미터를 유추할 수 있습니다. 따라서 대부분의 경우 이를 지정할 필요가 없습니다.

예를 들어 제네릭 메서드 addBox를 호출하려면 다음과 같이 타입 감시를 사용하여 타입 파라미터를 지정할 수 있습니다.

BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);

또는 타입 감시를 생략하면 Java 컴파일러가 타입 파라미터가 Integer라고 자동으로 추론합니다(메소드의 아규먼트에서).

BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);


Type Inference and Instantiation of Generic Classes


컴파일러가 컨텍스트에서 타입 아규먼트를 유추할 수 있는 한 제네릭 클래스의 생성자를 호출하는 데 필요한 타입 아규먼트를 빈 타입 파라미터 세트 (<>)로 바꿀 수 있습니다. 이 꺾쇠 괄호 쌍은 비공식적으로 다이아몬드라고 합니다.

예를 들어 다음 변수 선언을 고려하십시오.

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

생성자의 파라미터화된 타입을 빈 타입 파라미터 세트(<>)로 대체할 수 있습니다.

Map<String, List<String>> myMap = new HashMap<>();

제네릭 클래스 인스턴스화 중에 타입 유추를 활용하려면 다이아몬드를 사용해야 합니다. 다음 예제에서 컴파일러는 HashMap() 생성자가 Map<String, List<String>> 타입이 아닌 HashMap 원시(Raw) 타입을 참조하기 때문에 확인되지 않은 변환 경고를 생성합니다.

Map<String, List<String>> myMap = new HashMap(); 
// unchecked conversion warning 
// "<>"를 안쓰면 raw type.


Type Inference and Generic Constructors of Generic and Non-Generic Classes


생성자는 제네릭 클래스와 제네릭이 아닌 클래스 모두에서 제네릭(즉, 자신의 formal 타입 파라미터를 선언)할 수 있습니다.

다음 예를 고려하십시오.

class MyClass<X> {
  <T> MyClass(T t) {
    // ...
  }
} // <X> 는 클래스 내부, <T> constructor 내부.
//<T> type parameter, (T t) fomal parameter.

MyClass 클래스의 다음 인스턴스화를 고려하십시오.

new MyClass<Integer>("") // ("") = constructor. 컨스트럭터가 generic method.

위 코드는 파라미터화된 타입 MyClass<Integer>의 인스턴스를 작성합니다.

이 코드는 제네릭 클래스 MyClass<X>의 formal 타입 파라미터 X에 대해 Integer 타입을 명시적으로 지정합니다.

하지만 이 제네릭 클래스의 생성자에는 formal 타입 파라미터 T가 포함되어 있습니다. 컴파일러는 이 제네릭 클래스 생성자의 formal 타입 파라미터 T에 대해 String 타입을 유추합니다(이 생성자의 실제 타입파라미터는 String 개체이기 때문).

Java SE 7 이전 릴리스의 컴파일러는 제너릭 메소드와 유사하게 제너릭 생성자의 실제 타입 파라미터를 유추할 수 있습니다. 그러나 Java SE 7 이상의 컴파일러는 다이아몬드(<>)를 사용하는 경우 인스턴스화되는 제너릭 클래스의 실제 타입 파라미터를 유추할 수 있습니다.

다음 예를 고려하십시오.

MyClass<Integer> myObject = new MyClass<>("");

이 예제에서 컴파일러는 제네릭 클래스 MyClass<X>의 formal 타입 파라미터 X에 대해 Integer 타입을 유추합니다. 이 제네릭 클래스 생성자의 formal 타입 파라미터 T에 대한 String 타입을 유추합니다.

  • 참고: 유추 알고리즘은 호출 아규먼트, Target type 및 가능하고 명백한 예상 반환 타입만 사용하여 타입을 유추한다는 점에 유의하는 것이 중요합니다. 추론 알고리즘은 프로그램 후반부의 결과를 사용하지 않습니다.


Target Types


Java 컴파일러는 제너릭 메소드 호출의 타입 파라미터를 유추하기 위해 타겟 타입(Target Type) 지정을 이용합니다. 표현식의 타겟 타입은 표현식이 나타나는 위치에 따라 Java 컴파일러가 예상하는 데이터 타입입니다.

다음과 같이 선언된 Collections.emptyList 메소드를 고려하십시오.

static <T> List<T> emptyList();

Consider the following assignment statement.

List<String> listOne = Collections.emptyList();
//'<T>' 타입 파라미터를 string으로 바꿈.

위 코드는 List<String>의 인스턴스를 기대하고 있습니다. 이 데이터 타입은 타겟 타입입니다.

emptyList 메서드는 List<T> 타입의 값을 반환하기 때문에 컴파일러는 타입 아규먼트 T가 String 값이어야 한다고 추론합니다.

이는 Java SE 7 및 8 모두에서 작동합니다. 또는 타입 감시를 사용하고 다음과 같이 T 값을 지정할 수 있습니다.

List<String> listOne = Collections.<String>emptyList();


그러나 이 컨텍스트에서는 타입 감시(<String>)가 필요하지 않습니다.

하지만 다른 상황에서는 필요했습니다.

다음 방법을 고려하십시오.

void processStringList(List<String> stringList) {
    // process stringList
}

emptyList 메서드 호출로 processStringList 메서드에게 아규먼트를 전달하는 메서드 호출한다고 가정합니다. Java SE 7에서 다음 명령문은 컴파일되지 않습니다.

processStringList(Collections.emptyList());

The Java SE 7 compiler generates an error message similar to the following:

List<Object> cannot be converted to List<String>

컴파일러는 타입 파라미터 T에 대한 값이 필요하므로 Object 값으로 시작합니다.

따라서 Collections.emptyList를 호출하면 processStringList 메서드와 호환되지 않는 List<Object> 타입의 값이 반환됩니다.

따라서 Java SE 7에서는 다음과 같이 타입 아규먼트의 값을 지정해야 합니다.

processStringList(Collections.<String>emptyList());

Java SE 8에서 더 이상 타입 감시가 필요하지 않습니다. 타겟 타입이 무엇인지에 대한 개념이 확장되어 processStringList 메소드에 대한 아규먼트와 같은 메소드 아규먼트를 포함합니다.

이 경우 processStringList에는 List<String> 타입의 아규먼트가 필요합니다.

Collections.emptyList 메서드는 List<T>의 값을 반환하므로 List<String>의 타겟 타입을 사용하여 컴파일러는 타입 아규먼트 T의 값이 String인 것으로 유추합니다. 따라서 Java SE 8에서는 다음 코드가 컴파일됩니다.

processStringList(Collections.emptyList());

profile
_serendipity

0개의 댓글