Java Generics
Jaesup Kwak
2016.11.26
Generic의 필요성
List strList = new ArrayList(); // String의 리스트를 사용할 의도
strList.add(new Integer(0)); // 의도에 맞지 않지만 컴파일러는 모름
String str = (String) strList.get(0); // 의도에 맞지만 런타임 예외 (ClassCastException)
• 기존 (< Java 1.5) Collection 클래스들 (Wrapper 클래스)의 문제점
• opaque 타입을 활용한 구현이기 때문에
• 의도 - 이름, 주석에 의존
• 넣을 때 - 엉뚱한 타입의 객체를 넣게 될 가능성
• 꺼낼 때 - 매번 직접 올바른 타입 캐스팅 필요
• 매번 조심해야
• 상속을 활용한 매개변수 타입을 갖는 메소드 이용시에도 해당
해결방안
• Collection이나 Wrapper 클래스의 객체를 생성할 때
컴파일러에게 담을 타입을 한번 알림
• 의도 - 코드에 명확히 나타남
• 넣을 때 - 컴파일러가 타입 체크
• 꺼낼 때 - 컴파일러가 타입 캐스팅
• 타입을 나타내는 매개변수(type parameter) 개념의
도입
• 메소드에 대해서도 적용 가능한 개념
사용법-정의하기
• 제네릭 타입 (generic type)
• class X<T> { T 이용 }
• interface X<T> { T 이용 }
• 제네릭 메소드 (generic method)
• <T> Result x(T 이용) { T 이용 } // Result에도 T 이용 가능은 함
• 타입 매개변수 (type parameter)
• simple: <T>, <U,V>
• bounded: <T extends Number>, <T extends Comparable<T>>
사용법-이용하기
• X<String> x = new X<>();
• <String>x(…) 혹은 그냥 x(…) // by 타입 추론
• 타입 인수 (type argument)
• simple: <String>, <String, Integer>
• wildcard:
• unbounded wildcard “?”
• upper bounded wildcard “? extends”
• lower bounded wildcard “? super”
• 형인자 타입 (parameterized type)
• 제네릭 타입의 타입 매개변수에 실제 타입 인수를 지정한 결과 타입
• 제네릭 타입의 호출(invocation of generic type)이라고도 말함
generic type은 불변형
• T2가 T1의 하위 타입인 경우 (T2 is-a T1)
• 타입 매개변수 T를 갖는 타입 F를 F<T>로 표현할 때
• F<T2> is-a F<T1> 관계이면 F<T>는 공변형(covariant)
• F<T1> is-a F<T2> 관계이면 F<T>는 반?변형(contravariant)
• F<T1>과 F<T2>에 is-a 관계가 없으면 F<T>는 불변형(invariant)
• (Ex)
• Integer is-a Number
• List<Number>와 List<Integer> 사이에 is-a 관계가 없다 -> generic type is
invariant
• Integer[] is-a Number[] -> array is covariant
wildcard
• (문제) 제네릭 타입의 불변성은 이를 매개변수로 사용하는 메소드의 재사용성을 크게 제
한
• (해결) 메소드 매개변수를 선언할 때 타입 인수로 “?”를 지정하여 임의의 타입 인수를 나
타낼 수 있음
• (결과) 임의의 타입 인수를 지정한 해당 제네릭 타입의 객체를 인수로 지정하여 메소드 호
출 가능
• 타입 인수로 wildcard 지정 가능한 곳
• 멤버 변수 / 지역 변수 /반환 타입 선언
• 타입 인수로 wildcard 지정 불가능한 곳
• 제네릭 메소드 호출 / 제네릭 타입의 객체 생성 / 상위타입 (class X extends Y<?> {})
• unbounded wildcard “?”: 임의의 타입 인수와 매칭
• upper bounded wildcard “? extends Upper”: Upper 이하의 임의의 타입 인수와 매칭
• lower bounded wildcard “? super Lower”: Lower 이상의 임의의 타입 인수와 매칭
PECS - wildcard 사용 가
이드
• Producer Extends Consumer Super
• 제네릭 메소드의 인자가 메소드 내에서
• 소스/생산자로 사용되는 경우 —> “? extends”를 사용
• 타겟/소비자로 사용되는 경우 —> “? super”를 사용
• 일반적으로 유용하고 견고한 API를 만드는데 도움
• stream 오퍼레이션, 작업 변수 활용, Robustness rule 경험칙
• receive, 읽기: UpperBounded src —> tmp // 관대하게 받아서
• send, 쓰기: tmp —> LowerBounded target // 엄격하게 처리
타입 소거• 타입 소거 (type erasure) - Java에서 generic type을 구현한 방법/기법
• 컴파일러는 타입 매개변수와 타입 인수 정보를 활용하여 타입 체크, 타입 캐
스팅, 브릿지-메소드 생성후 generic 관련 타입 정보를 제거하고 컴파일을
수행한다
• 생성된 바이트코드에는 타입 매개변수나 타입 인수 정보가 포함되지 않는
다
• 브릿지-메소드
• 제네릭 타입의 하위 클래스에서 타입 매개변수를 사용하는 메소드를
override한 경우
• 상위 클래스에 대한 타입 소거 결과 메소드의 signature가 변경되어
override 관계가 없어질 수 있다
• 이를 해결 하기 위해 컴파일러가 필요에 맞게끔 하위 클래스에 생성하는 메
소드
• 일반적으로 의식할 필요는 없으나 stack trace에서 관찰된다. 놀라지 말라
실체화 불가 타입 (Non-
reifiable)
• 실행시점에 타입에 대한 정보가 완전하지 않음, 타입 소거의 결과
• 실체화 가능 타입
• primitive types
• non-generic types
• raw types: List
• invocations of unbound wildcards: List<?>
• 실체화 불가 타입
• <?> 이외의 generic type 호출: List<String>, List<? extends Number>, List<? super
Integer>
• 런타임에 JVM은 실체화 불가 타입들을 서로 구분할 수 없다
• 제네릭의 제약 사항중 많은 부분이 이에 기인함 (다음 페이지)
제약사항
• 기본 타입을 타입 인수로 사용할 수 없다: <int>
• 타입 매개변수로
• 객체 생성할 수 없다: new T
• static 필드를 선언할 수 없다: static T
• 예외를 catch할 수 없다: catch (T e) — 반면, throws 구문에는 사용 가능하다: void x() throws T
• parameterized type으로
• 타입 테스트할 수 없다: instanceof List<Integer>
• 타입 캐스팅할 수 없다: (List<Integer>) x
• 배열 생성할 수 없다: new List<Integer>[]
• 타입 소거후 메소드 signature가 동일하게 되는 오버로딩은 불가능하다
• void x(List<String> strList) {}
• void x(List<Integer> intList) {}
• 제네릭 예외 클래스를 만들 수 없다: class MyException<T> extends Exception {}
참고자료
• Oracle, “The Java(TM) Tutorials”,
https://docs.oracle.com/javase/tutorial/java/generics/
• Wikipedia, “Covariance and contravariance (computer science)”,
https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_scien
ce)
• 조슈아 블로크, 이병준(옮김), “Effective Java 2/E”

Java generics

  • 1.
  • 2.
    Generic의 필요성 List strList= new ArrayList(); // String의 리스트를 사용할 의도 strList.add(new Integer(0)); // 의도에 맞지 않지만 컴파일러는 모름 String str = (String) strList.get(0); // 의도에 맞지만 런타임 예외 (ClassCastException) • 기존 (< Java 1.5) Collection 클래스들 (Wrapper 클래스)의 문제점 • opaque 타입을 활용한 구현이기 때문에 • 의도 - 이름, 주석에 의존 • 넣을 때 - 엉뚱한 타입의 객체를 넣게 될 가능성 • 꺼낼 때 - 매번 직접 올바른 타입 캐스팅 필요 • 매번 조심해야 • 상속을 활용한 매개변수 타입을 갖는 메소드 이용시에도 해당
  • 3.
    해결방안 • Collection이나 Wrapper클래스의 객체를 생성할 때 컴파일러에게 담을 타입을 한번 알림 • 의도 - 코드에 명확히 나타남 • 넣을 때 - 컴파일러가 타입 체크 • 꺼낼 때 - 컴파일러가 타입 캐스팅 • 타입을 나타내는 매개변수(type parameter) 개념의 도입 • 메소드에 대해서도 적용 가능한 개념
  • 4.
    사용법-정의하기 • 제네릭 타입(generic type) • class X<T> { T 이용 } • interface X<T> { T 이용 } • 제네릭 메소드 (generic method) • <T> Result x(T 이용) { T 이용 } // Result에도 T 이용 가능은 함 • 타입 매개변수 (type parameter) • simple: <T>, <U,V> • bounded: <T extends Number>, <T extends Comparable<T>>
  • 5.
    사용법-이용하기 • X<String> x= new X<>(); • <String>x(…) 혹은 그냥 x(…) // by 타입 추론 • 타입 인수 (type argument) • simple: <String>, <String, Integer> • wildcard: • unbounded wildcard “?” • upper bounded wildcard “? extends” • lower bounded wildcard “? super” • 형인자 타입 (parameterized type) • 제네릭 타입의 타입 매개변수에 실제 타입 인수를 지정한 결과 타입 • 제네릭 타입의 호출(invocation of generic type)이라고도 말함
  • 6.
    generic type은 불변형 •T2가 T1의 하위 타입인 경우 (T2 is-a T1) • 타입 매개변수 T를 갖는 타입 F를 F<T>로 표현할 때 • F<T2> is-a F<T1> 관계이면 F<T>는 공변형(covariant) • F<T1> is-a F<T2> 관계이면 F<T>는 반?변형(contravariant) • F<T1>과 F<T2>에 is-a 관계가 없으면 F<T>는 불변형(invariant) • (Ex) • Integer is-a Number • List<Number>와 List<Integer> 사이에 is-a 관계가 없다 -> generic type is invariant • Integer[] is-a Number[] -> array is covariant
  • 7.
    wildcard • (문제) 제네릭타입의 불변성은 이를 매개변수로 사용하는 메소드의 재사용성을 크게 제 한 • (해결) 메소드 매개변수를 선언할 때 타입 인수로 “?”를 지정하여 임의의 타입 인수를 나 타낼 수 있음 • (결과) 임의의 타입 인수를 지정한 해당 제네릭 타입의 객체를 인수로 지정하여 메소드 호 출 가능 • 타입 인수로 wildcard 지정 가능한 곳 • 멤버 변수 / 지역 변수 /반환 타입 선언 • 타입 인수로 wildcard 지정 불가능한 곳 • 제네릭 메소드 호출 / 제네릭 타입의 객체 생성 / 상위타입 (class X extends Y<?> {}) • unbounded wildcard “?”: 임의의 타입 인수와 매칭 • upper bounded wildcard “? extends Upper”: Upper 이하의 임의의 타입 인수와 매칭 • lower bounded wildcard “? super Lower”: Lower 이상의 임의의 타입 인수와 매칭
  • 8.
    PECS - wildcard사용 가 이드 • Producer Extends Consumer Super • 제네릭 메소드의 인자가 메소드 내에서 • 소스/생산자로 사용되는 경우 —> “? extends”를 사용 • 타겟/소비자로 사용되는 경우 —> “? super”를 사용 • 일반적으로 유용하고 견고한 API를 만드는데 도움 • stream 오퍼레이션, 작업 변수 활용, Robustness rule 경험칙 • receive, 읽기: UpperBounded src —> tmp // 관대하게 받아서 • send, 쓰기: tmp —> LowerBounded target // 엄격하게 처리
  • 9.
    타입 소거• 타입소거 (type erasure) - Java에서 generic type을 구현한 방법/기법 • 컴파일러는 타입 매개변수와 타입 인수 정보를 활용하여 타입 체크, 타입 캐 스팅, 브릿지-메소드 생성후 generic 관련 타입 정보를 제거하고 컴파일을 수행한다 • 생성된 바이트코드에는 타입 매개변수나 타입 인수 정보가 포함되지 않는 다 • 브릿지-메소드 • 제네릭 타입의 하위 클래스에서 타입 매개변수를 사용하는 메소드를 override한 경우 • 상위 클래스에 대한 타입 소거 결과 메소드의 signature가 변경되어 override 관계가 없어질 수 있다 • 이를 해결 하기 위해 컴파일러가 필요에 맞게끔 하위 클래스에 생성하는 메 소드 • 일반적으로 의식할 필요는 없으나 stack trace에서 관찰된다. 놀라지 말라
  • 10.
    실체화 불가 타입(Non- reifiable) • 실행시점에 타입에 대한 정보가 완전하지 않음, 타입 소거의 결과 • 실체화 가능 타입 • primitive types • non-generic types • raw types: List • invocations of unbound wildcards: List<?> • 실체화 불가 타입 • <?> 이외의 generic type 호출: List<String>, List<? extends Number>, List<? super Integer> • 런타임에 JVM은 실체화 불가 타입들을 서로 구분할 수 없다 • 제네릭의 제약 사항중 많은 부분이 이에 기인함 (다음 페이지)
  • 11.
    제약사항 • 기본 타입을타입 인수로 사용할 수 없다: <int> • 타입 매개변수로 • 객체 생성할 수 없다: new T • static 필드를 선언할 수 없다: static T • 예외를 catch할 수 없다: catch (T e) — 반면, throws 구문에는 사용 가능하다: void x() throws T • parameterized type으로 • 타입 테스트할 수 없다: instanceof List<Integer> • 타입 캐스팅할 수 없다: (List<Integer>) x • 배열 생성할 수 없다: new List<Integer>[] • 타입 소거후 메소드 signature가 동일하게 되는 오버로딩은 불가능하다 • void x(List<String> strList) {} • void x(List<Integer> intList) {} • 제네릭 예외 클래스를 만들 수 없다: class MyException<T> extends Exception {}
  • 12.
    참고자료 • Oracle, “TheJava(TM) Tutorials”, https://docs.oracle.com/javase/tutorial/java/generics/ • Wikipedia, “Covariance and contravariance (computer science)”, https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_scien ce) • 조슈아 블로크, 이병준(옮김), “Effective Java 2/E”