Java Generic Array (Feat. HashMap)

wannabeking·2023년 4월 21일
1

Java

목록 보기
12/13
post-thumbnail

다음과 같은 Box<T> generic type이 존재할 때,

class Box<T> {

    private T value;
    
    ...

    public T getValue() {
        return value;
    }
}

String을 actual type parameter로 Box<T> 배열을 생성하고자 합니다.

Box<String>[] boxes = new Box<String>[3];

빨간 줄과 함께 IDE는 Generic array creation이라는 컴파일 에러를 알려줍니다.

왜 이러한 에러가 발생하는 것일까요?



Generic array creation 에러 이유

우선, Java에서 제네릭과 배열은 다음과 같은 차이가 존재합니다.

  • 배열은 공변이다.
  • 제네릭은 불공변(무공변)이다.

업캐스팅을 사용한 다음 예시를 보면서, 공변과 불공변의 차이를 알아보겠습니다.

StringObject의 하위 클래스이기 때문에 업캐스팅이 가능합니다.
그런데, String[] 또한 Object[]로 업캐스팅이 가능합니다.

이러한, type의 super, sub 관계를 그대로 따라가는 성질을 공변이라고 합니다.


공변의 경우 업캐스팅하여 사용이 가능하므로, 코드의 유연성의 큰 도움을 줍니다. (파라미터로 Object[] 받기)

하지만, 만약 Obejct[]로 업캐스팅한 String[]arr[0] = 1;이라는 코드를 추가했다면 공변이기 때문에 컴파일 시점에서 에러를 찾지 못하고 런타임 시점에 에러가 발생할 것입니다. (Object[]Integer 원소를 넣는게 가능하므로)


그렇다면 제네릭을 사용하면 어떨까요? - 위 사진과 같이 컴파일 시점에 에러가 발생합니다.

즉, ArrayList<String>ArrayList<Obejct>의 하위 개념이 아닙니다. - 이러한 성질을 불공변이라고 합니다. (제네릭의 등장 이유가 형 안정성이니 당연한 결과)


이제 위와 같은 컴파일 에러가 발생하는 이유는, 공변인 배열과 불공변인 제네릭은 형질 자체가 다르기 때문에 생성 자체를 막으려는 것임을 알 수 있습니다.

만약 제네릭 배열을 사용한다면,

  • List<String>[] listArr = ArrayList<String>[3];로 제네릭 배열을 생성
  • Obejct[] objects = listArr;와 같이 String List를 Object로 업캐스팅
  • objects[0] = List.of(1, 2);로 Integer List를 넣어줌
  • listArr[0].get(0); // class cast exception!

와 같이 형 안정성을 보장받을 수 없게 됩니다. (제네릭의 의의에 위배)


만약 제네릭 사용 시에도 업캐스팅하여 사용하는 유연성을 가지고 싶다면, 와일드 카드 타입을 사용하면 됩니다.

와일드 카드 타입으로 업캐스팅을 허용하는 이유는 read-only이기 때문



HashMap 내부의 제네릭 배열 사용

transient는 직렬화 대상에서 제외하는 키워드

앞서 제네릭 배열 생성을 막는 것을 알아보았는데, HashMap의 내부 코드를 살펴보면 버킷 배열 구현에 사용하고 있다는 것을 확인할 수 있습니다.

어떻게 사용할 수 있는지 궁금해서 찾아보던 중, readObject() 메소드에서 raw type으로 생성하고 강제 형변환 하는 것을 확인했습니다. (컬렉션 raw type은 generic type이 아니므로 배열 생성 가능)


같은 방법으로 위의 Box 예제 코드에 대입해 봤더니,

에러가 발생하지 않고, 제대로 출력되는 것을 확인할 수 있었습니다.


그렇다면, HashMap은 왜 제네릭 배열을 사용하고자 했을까요? - 컬렉션을 사용하는 방법도 있었을 것입니다.

그 이유는, 성능을 위해서 입니다.

HashMap은 성능에 공을 들여 만든 컬렉션으로, (자세한 내용은 Naver D2 참고)
배열을 사용하는 것이 반복문 처리 성능 등 이점이 많기 때문에 사용한 것입니다.

또한, 이러한 제네릭 배열의 사용은 client는 직접 코드를 까보지 않으면 알 수 없습니다. (내부적인 구현이 완벽하면, 런타임 에러가 발생할 수 없는 구조)


지금까지 Generic Array에 대해 알아보았습니다.

제네릭 배열은 잘 활용하면 성능 이점을 가져다 주는 자료구조로 사용할 수 있지만, 형 안정성을 보장하지 못해 런타임 에러가 발생할 수 있으므로 신중하게 사용하는 것이 좋을 것 같습니다! 🤓



profile
내일은 개발왕 😎

0개의 댓글