제너릭은 타입 매개변수를 사용하여 클래스, 인터페이스 및 메서드를 생성할 수 있도록 하여 컴파일 타임에 타입 검사를 수행하고 ClassCastException을 피할 수 있도록 도와줍니다.
주요 특징 및 함정:
new List<String>[10] — 컴파일 오류가 발생합니다.T obj = new T();if(obj instanceof List<String>) — 오류가 발생합니다.? extends T — 공변성(읽기), ? super T — 반공변성(쓰기).예:
// 읽기를 위한 공변적 접근 void printNumbers(List<? extends Number> numbers) { for (Number n : numbers) { System.out.println(n); } } // 쓰기를 위한 반공변적 접근 void addIntegers(List<? super Integer> list) { list.add(10); } }
질문: "List<Object>와 List<?>의 차이점은 무엇입니까? List<?>에 어떤 객체라도 추가할 수 있습니까?"
답변: 아니요, List<?>에는 아무것도 추가할 수 없습니다( null을 제외하고), 컴파일러가 어떤 타입 매개변수가 있는지 모르기 때문입니다. 반면 List<Object>에는 어떤 객체든 추가할 수 있습니다.
예:
List<?> list1 = new ArrayList<String>(); // list1.add("test"); // 컴파일 오류! List<Object> list2 = new ArrayList<>(); list2.add("test"); // OK
이야기
개발 팀은 매개변수화된 타입
T[]을 기반으로 캐시를 구현하려고 했습니다. 타입 소거(type erasure)와 제너릭 타입의 배열을 생성할 수 없는 이유로 예상대로 작동하지 않았습니다:Object[]배열이 생성되어 runtime 캐스팅 시 ClassCastException이 발생했습니다.
이야기
하나의 마이크로서비스에서 개발자는 List<?>를 매개변수로 사용하는 수신기를 구현하려고 했으며 컬렉션을 수정하려고 했습니다. 이는 컴파일 오류를 발생시켰고, PECS를 감안하여 로직을 리팩토링해야 하여 릴리스가 지연되었습니다.
이야기
외부 시스템과의 통합 프로젝트에서 개발자는 처리되지 않은 raw-type으로 List를 통해 한 타입의 컬렉션을 다른 타입으로 덮어쓰는 실수를 저질렀습니다: List list = new ArrayList<String>(); 이로 인해 ClassCastException이 발생하고, 서비스가 다른 타입으로 요소를 캐스팅하려고 할 때 production에서 장애가 발생했습니다.