공부/이펙티브코틀린

아이템 24 - 제네릭 타입과 variance 한정자를 활용하라

띵커베르 2024. 6. 13. 22:17
728x90

기본 - 제네릭 클래스 => 무공변성 -> 즉 공변이 없다

out -> 공변성 => 자기자신과 자식 객체 허용 -> 생산만 -> 꺼낼수만 있다.

in -> 반공변성 => 자기자신과 부모 객체 허용 -> 소비만 -> 넣을수만 있다.

*변성 -> 제네릭 클래스 타입 파라미터에 따라 제네릭 클래스 간의 상속 관계가 어떻게 되는지를 나타내는 용어

 

선언 지점 변성 -> 클래스 자ㅔ를 공변하거나 반공변하게 만드는 방법

사용 지점 변셩 -> 특정 함수 또는 특정 변수에 대해 공변/반공변을 만드는 방법

제네릭 제약: class Cage<T: Animal> 처럼 제네릭 클래스의 타입 파라미터의 제약을 걸 수 있다. 그외로는 where 로 여러 제약을 줄 수 있다.

타입소거: JDK 의 호환성을 위해 런타임 때 제네릭 클래스의 타입 파라미터 정보가 지워지는 것

 

 

 

  • 타입 파라미터 끼리, 상속관계가 존재하더라도 실제 제네릭 클래스의 코드에서는 아무 관계가 아니다
  • 자바의 배열과 리스트를 살펴보자
    • 자바의 배열은 제네릭과 다르다
      A 객체가 B 객체의 하위 타입이면,A 배열이 B 배열의 하위 타입으로 간주한다.
      • Object(상위클래스) <- String(하위클래스) => Object[] <- String[]
      • 이러한 관계를 공변하다고 한다.(상속관계가 유지되므로..)
      • 공변하기 때문에 아래와 같이 이상한 코드가 작성 가능하다.
String[] strs = new String[]{"a", "b", "c"}; // 가능
Object[] objs = strs; // 가능
Object[0] = 1; // 가능은 하지만 문제가되는 부분
		-> objs 사실 String[] 이기 때문에 int 를 넣을 수 없다. 런타임 에러 발생 함.
  • List 는 제네릭을 사용하기 때문에 공변인 Array 와 다르게 무공변 하다
List<String> strs = List.of("a", "b", "c");
List<Object> objs = strs; // Type Mismatch! 발생 즉 컴파일 때 발견

 

  • 위와같은 이유에서 이펙티브 자바에서는 -> Item28. 배열보다는 리스트를 사용하라 라고 적혀있음.

 

  • 제네릭은 자바1.5(자바 5) 부터 개념이 생김
  • 제네릭이 없던 시절의 List 와 제네릭이 생긴 후 List<String> 호환성을 유지해야 한다.
    • 이러한 호환성을 유지하기 위해 Java 5 는 List<String> 도 runtime 떄는 type 정보를 제거하는 방식을 선택함.
    • 이로인해 Java 에서는 지금도 raw type 을 만들 수 있다
      • raw type: 제네릭 타입을 사용하지 않은 타입을 말한다. 즉 타입을 지정하지 않는..
        ex: val list = listOf(1, 2, 3)
    • 코틀린 같은 경우에는 언어 초기부터 제네릭이 고려되었기 때문에 ray type 객체를 만들 수 없다
// raw type 이란 제네릭 타입을 사용하지 않은 타입을 말한다.
// 절대 권장하지 않음
val list = listOf(1, 2, 3)

// 코틀린 같은 경우에는 언어 초기부터 제네릭이 고려되었기 때문에 ray type 객체를 만들 수 없다
// One type argument expected for interface List<out E>
// val list2: List = listOf(1, 2, 3)

 

  • 여튼 코틀린도 JVM 위에서 동작하기 때문에 런타임 때는 타입 정보가 사라진다 이를 타입 소거라 부른다.
// 타입 소거를 확인할 수 있는 코드
fun checkStringList(data: Any) {
    // Cannot check for instance of erased type: List<String>
    // if (data is List<String>) { 에러..
    // }
}

// 런타임 때는 String 정보가 사라지기에 List<string> 인지 알 수 없다.

 

  • star projection 을 활용해 최소한 List 인지는 확인할 수 있다.
if(data is List<*>) {
    println("List<String> 타입이 맞습니다.")
}
// 때때로 제네릭 타입 파라미터까지 추측할 수도 있다.
fun checkMutableList(data: Collection<String>) {
    if (data is MutableList<String>) {
        println("MutableList<String> 타입이 맞습니다.")
    }
}

 

728x90