✅ Type Inference
타입 추론(Type Inference)는 메서드 호출에 적용할 수 있는 타입 아규먼트(또는 아규먼트)를 결정하기 위해 각 메소드 호출 및 해당 메서드 선언을 살펴보는 Java 컴파일러의 기능입니다. 추론 알고리즘은 아규먼트의 타입과 (가능한 경우)반환되는 타입을 보고 결정합니다. 마지막으로 추론 알고리즘은 모든 아규먼트와 함께 작동하는 특정 타입을 찾으려고 시도합니다.
다음 예제에서 타입 추론은 pick 메서드에 전달되는 두 번째 아규먼트가 Serializable 유형(ArrayList가 구현한 Serializable)임을 확인함을 볼 수 있습니다.
static <T> T pick(T a1, T a2) { return a2; }
//생략되지 않은 제너릭 메소드
Serializable s = <String, ArrayList<String>>pick("d", new ArrayList<String>());
//생략된 제너릭 메소드
Serializable s = pick("d", new ArrayList<String>());
👉 String, ArrayList<String>, Serializable, 3개의 파라미터 타입 중에 컴파일러는 타입 추론을 통해 Serializable을 선택했습니다. 이는 모든 아규먼트와 함께 작동하는 타입을 찾으려고 하기 때문입니다. String과 ArrayList가 Serializable 인터페이스를 구현했기 때문에 가능한 것입니다.
🔹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); } 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++; } } public static void main(String[] args) { //Enpty Type arguments, <>안의 Box<Integer>이 생략되었다. java.util.ArrayList<Box<Integer>> listOfIntegerBoxes = new java.util.ArrayList<>(); //method의 타입 파라미터를 명시적으로 선언함. BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes); //method의 타입 아큐먼트를 생략 -> Type Inference BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes); //Type Inference : 타입 파라미터는 Integer이 된다. BoxDemo.outputBoxes(listOfIntegerBoxes); //BoxDemo.outputBoxes(ArrayList<Box<Integer>>) //메소드 선언부의 타입 파라미터 U는 타입 아규먼츠로 전달된 Integer이 된다. } }
제네릭 메소드
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<>();
이처럼 제네릭 클래스 인스턴스화 중에 다이아몬드<>를 사용하면 타입 추론을 사용할 수 있습니다.
- 타입 추론이 불가능한 경우
Map<String, List<String>> myMap = new HashMap(); // unchecked conversion warning
위 예제에서 컴파일러는 HashMap() 생성자가 Map<String, List<String>> 타입이 아닌 HashMap 원시(Raw) 타입을 참조하기 때문에 확인되지 않은 변환 경고를 생성합니다. 이는 컴파일러가 인스턴스화되는 객체의 타입에 대한 제네릭 정보가 없기 때문에 타입 정보를 추론할 수 없기 때문입니다.
- 타입 추론이 불가능한 경우
🔹Type Inference and Generic Constructors of Generic and Non-Generic Classes
- 타입 추론과 제네릭 클래스와 일반 클래스의 생성자
생성자는 제네릭 클래스와 제네릭이 아닌 클래스 모두에서 제네릭(즉, 자신의 formal 타입 파라미터를 선언)할 수 있습니다.
➡️ 다음 예를 살펴보겠습니다.
//Generic Class
class MyClass<X> {
<T> MyClass(T t) {
// T는 생성자 내에서만 사용되는 타입 파라미터이다.
}
}
//Non-Generic Class
class NonMyClass {
<T> NonMyClass(T t) {
// T는 생성자 내에서만 사용되는 타입 파라미터이다.
}
}
new MyClass<Integer>("")
위 코드는 파라미터화된 타입 MyClass<Integer>의 인스턴스를 작성합니다. 이 코드는 제네릭 클래스 MyClass<X>의 formal 타입 파라미터 X에 대해 Integer 타입을 명시적으로 지정합니다. 이 제네릭 클래스의 생성자에는 formal 타입 파라미터 T가 포함되어 있습니다. 컴파일러는 이 제네릭 클래스 생성자의 formal 타입 파라미터 T에 대해 Arguments로 받은 문자열(””)에 따라 String 타입을 유추합니다. 생성자의 실제 타입 파라미터는 String으로 String 타입의 객체가 됩니다.
MyClass<Integer> myObject = new MyClass<>("");
👉 MyClass<Integer> myObject = new MyClass<>("");
코드에서 타입 파라미터로 Integer
를 주고 있지만, 생성자의 인자로 빈 문자열(""
)을 전달하고 있습니다. 이 경우, 생성자의 타입 파라미터 T는 빈 문자열(""
)의 타입인 String
으로 유추됩니다. 따라서 객체는 MyClass<String>
타입으로 생성됩니다.
타입 파라미터 T는 생성자 내에서만 사용되며, 외부에서 명시적으로 타입을 지정하지 않은 경우 컴파일러는 유추를 시도합니다. 유추 규칙에 따라 생성자의 인자인 빈 문자열(""
)의 타입인 String
을 기반으로 T를 String
으로 유추하게 됩니다. 따라서 위의 코드에서 myObject
는 MyClass<String>
타입의 객체가 됩니다. 즉, myObject
는 MyClass
클래스의 타입 파라미터로 String
을 가지는 객체를 참조하게 됩니다.
MyClass<Integer> myObject = new MyClass<>("");
코드에서 객체는 실제로 String
타입으로 생성되지만, 해당 객체에서 사용하는 타입 파라미터 X로 선언된 메소드는 Integer
타입을 사용할 수 있습니다. MyClass
클래스의 타입 파라미터 X는 클래스 수준에서 정의되었으며, 메소드 내에서 사용 가능한 범위에 있습니다. 따라서 메소드에서 X를 사용하는 경우에는 클래스의 타입 파라미터 X에 해당하는 타입을 사용할 수 있습니다.
🔹Target Types
- 목표 타입
Java 컴파일러는 제너릭 메소드 호출의 타입 파라미터를 유추하기 위해 타겟 타입(Target Type) 지정을 이용합니다. 표현식의 타겟 타입은 표현식이 나타나는 위치에 따라 Java 컴파일러가 예상하는 데이터 타입입니다.
다음과 같이 선언된 Collections.emptyList 메소드를 살펴보겠습니다.
static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList();
위 코드는 List<String>의 인스턴스를 기대하고 있습니다(
List<String>
). 이 데이터 타입은 타겟 타입입니다. emptyList 메서드는 List<T> 타입의 값을 반환하기 때문에 컴파일러는 타입 아규먼트 T가 String 값이어야 한다고 추론합니다. 이는 Java SE 7 및 8 모두에서 작동합니다. 또는 타입 감시를 사용하고 다음과 같이 T 값을 지정할 수 있습니다.List<String> listOne = Collections.<String>emptyList();
그러나 이 컨텍스트에서는 타입 감시(<String>)를 생략할 수 있습니다.
- Can’t use Target Type of Method Type Parameter in Java SE 7
void processStringList(List<String> stringList) { // process stringList }
emptyList 메서드 호출로 processStringList 메서드에게 아규먼트를 전달하는 메서드 호출한다고 가정합니다. Java SE 7에서 다음 명령문은 컴파일되지 않습니다.
processStringList(Collections.emptyList());
위와 같이 호출하면 다음과 같은 오류 메세지를 볼 수 있습니다.
List<Object> cannot be converted to List<String>
컴파일러는 타입 파라미터 T에 대한 값이 필요하므로 Object 값으로 시작합니다. 따라서 Collections.emptyList를 호출하면 processStringList 메서드와 호환되지 않는 List<Object> 타입의 값이 반환됩니다. 따라서 Java SE 7에서는 다음과 같이 타입 아규먼트의 값을 지정해야 합니다.
processStringList(Collections.<String>emptyList());
- After Java SE 8
❗Java SE 8에서 더 이상 타입 감시가 필요하지 않습니다. 타겟 타입이 무엇인지에 대한 개념이 확장되어 processStringList 메소드에 대한 아규먼트와 같은 메소드 아규먼트를 포함합니다. 이 경우 processStringList에는 List<String> 타입의 아규먼트가 필요합니다. Collections.emptyList 메서드는 List<T>의 값을 반환하므로 List<String>의 타겟 타입을 사용하여 컴파일러는 타입 아규먼트 T의 값이 String인 것으로 유추합니다. 따라서 Java SE 8에서는 다음 코드가 컴파일됩니다.
processStringList(Collections.emptyList());
🏷️이미지 출처와 참고한 사이트
- Can’t use Target Type of Method Type Parameter in Java SE 7
Uploaded by N2T