1. Generics

Generics 는 타입을 파라미터화 하는 기능이다. Generics 를 이해하기 위해서는 컴파일러의 입장을 이해해야 한다.

컴파일러는 런타임 이전에 에러를 캐치해서 컴파일 시점으로 앞당기고 싶다. 이는 실행 이후에 애플리케이션이 비정상으로 종료되는 것을 방지하고, IDE 내부에서 문제를 해결할 수 있도록 돕는다.

1. 정의

// Not Generics
List list = new ArrayList();
list.add("문자열");
list.add(123);  // 어떤 타입이든 가능
String str = list.get(0);  // 컴파일 에러 Required type:String, Provided:Object
String str = (String) list.get(1);  // ClassCastException 런타임 에러

// Generics
List<String> list = new ArrayList<>();
list.add("문자열");
list.add(123);  // 컴파일 에러 Required type: String, Provided: int
String str = list.get(0);  // 형변환이 필요하지 않음

Generics 은 <> 안에 참조 타입 변수를 할당함으로써 정의할 수 있다.

위의 예시를 보면 ArrayList 는 Object 타입의 배열이므로 String도 Integer 도 올 수 있지만, String 타입만 저장 가능한 ArrayList 를 만들 수는 없다. Generics 는 이를 String 타입으로 제한할 수 있다.

Generics 의 핵심은 컴파일러가 타입 파라미터의 역할을 완전히 이해하고 검증한다는 점이다. 위의 예시를 보면 같은 코드에서 Generics 로 List 의 타입을 String 으로 제한했을 때, list에 Integer 타입의 123 을 넣으려고 하면 컴파일 에러를 발생시킨다.

또한, 4번째 줄 코드를 보면 list 에서 문자열을 꺼내, String 타입의 변수에 할당 하려고 하면 불필요한 타입 캐스팅이 필요하다. 이는 성능에도 악영향을 준다. Generics 로 타입을 제한한 List 는 원소가 String 임을 보증하므로, 타입 캐스팅을 하지 않더라도 컴파일러는 String 으로 인지한다.

정리하자면, Generics 는 컴파일 시점의 타입 체크와 불필요한 타입캐스팅을 해결할 수 있다.

// 타입별로 클래스를 만들어야 함
public class StringBox { private String content; }
public class IntegerBox { private Integer content; }
public class PersonBox { private Person content; }

// 하나의 Generics 클래스로 해결
public class Box<T> { private T content; }

추가적으로 Generics 는 하나의 타입 매개변수로 여러 타입을 지원함으로써 가독성을 향상시키고 코드의 재사용성을 높일 수 있다.

2. 공변 / 반공변

배열에는 ‘공변’ 이라는 특성이 있다. 이는 상속 관계가 그대로 유지되는 것을 의미한다. String 은 Object 의 하위 타입이므로 String[] 도 Object[]의 하위 타입이 되는 것 처럼.