TIL - 20.12.26

2 minute read

[Effective Java] 5장 제네릭 Item 32. 제네릭과 가변인수를 함께 쓸 때는 신중해라

  • 제네릭 등 실체화 불가 타입의 배열은 타입 안전성이 깨질 수 있기 때문에 직접 만들 수 없도록 막았다.
    • “타입 안전성이 깨진다.” ⇒ “런타임에 ClassCastExcption이 발생할 수 있다.”
    • 에러가 발생할 수 있으므로 컴파일 시에 경고로 알려준다.
  • 가변인수 메서드를 호출하면 내부적으로 가변인수를 담기 위한 배열이 하나 만들어진다.
    • 실체화 불가 타입을 가변 인수로 받는 경우(String... 이나 T...) 실체화 불가 타입에 대한 배열이 만들어진다는 의미
  • 재네릭 배열을 직접 만드는 것은 막으면서, 가변인수에서 생성하는 것은 경고로만 알리는 이유는?

    • T... 와 같은 가변 인자가 실무에서 유용하기 때문이다.

    • 대표적으로 printf() 등 자바 라이브러리도 이런 메서드를 제공한다.

      public PrintStream printf(String format, Object ... args) {
        return format(format, args);
      }
      
  • 규칙 : 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 메서드를 정의할 때는 @SafeVarargs를 붙여준다.
    • 안전하지 않아도 붙이라는 뜻이 아니라 varargs 메서드를 만들거면 무조건 안전하게 만들라는 뜻
    • “안전한 제너릭 varargs 메서드”란?? 다음 두 조건을 만족하는 것을 의미
      • varargs 매개변수 배열에 아무것도 저장하지 않는다.
      • 그 배열(혹은 복제본)을 외부에 노출하지 않는다. (메서드 내에서만 사용하고 리턴or전달 X )

제너릭 배열 사용시 주의 사항 - 안전하지 않은 제너릭 varargs 메서드

배열과 제네릭의 중요한 차이점은 Type을 확인하는 시점이다.

  • 배열 : 런타임에 type 정보를 저장하고 확인
  • 제네릭 : 컴파일 타임에 유형 오류를 확인하고 런타임에는 유형 정보가 없다. ⇒ Type Erasure

제너릭 배열을 직접 생성하면 컴파일 오류가 발생한다.

T[] elements = new T[size];

why? 타압 안정성이 깨지기 때문에 막음

다음 코드를 살펴보자

public <T> T[] getArray(int size) {
    T[] genericArray = new T[size]; // suppose this is allowed
    return genericArray;
}

바인딩되지 않은 제네릭 유형 TObject로 해석 되므로 런타임시 메서드는 다음과 같다.

(T[]와 같은 제너릭 배열을 구현했다면, T라는 타입은 실행시간에선 정확한 자료형으로 지정이 안되니, Object[]로 변환한다.)

public Object[] getArray(int size) {
    Object[] genericArray = new Object[size];
    return genericArray;
}

그런 다음 메서드를 호출하고 결과를 String 배열 에 저장하면 :

String[] myArray = getArray(5);

코드는 잘 컴파일되지만 런타임에 ClassCastException 과 함께 실패한다.

이는 String [] 참조에 Object [] 를 할당했기 때문이다. 특히 컴파일러에 의한 암시 적 캐스트는 Object [] 를 필수 유형 String [] 로 변환하지 못한다.

이렇듯 제네릭 배열을 직접 초기화 할 수는 없지만, 정확한 유형의 정보가 호출 코드에서 제공되는 경우 동일한 작업을 수행 할 수 있습니다. (⇒ “가변인수 메서드를 호출하면 내부적으로 가변인수를 담기 위한 배열이 하나 만들어져서 사용된다.”) 이 때 발생할 수 있는 ClassCastException을 주의할 것!!

안전하게 작성되지 않은 코드는 컴파일이 아니라 런타임시에 발견될 수 있으므로 이런 위험을 피해야 한다.

REF

Java: 제너릭 배열을 사용하지 말아야 되는 이유?

Java 제너릭 배열 만들기

[Effective Java] 5장 제네릭

Java 가변 매개변수, 가변인자(varargs)


2d Array sort

1번 인덱스로 정렬

Arrays.sort(arr, Comparator.comparingInt(a -> a[1]));

좌표 정렬

(x, y) 좌표가 주어졌을 때, y를 오름차순으로 정렬하면서, y가 같다면 x를 오름차순으로 정렬

자바의 Arrays.sort()가 stable sort1로 정렬해주기 때문에, x를 기준으로 우선 정렬 후, y를 정렬하도록 하면 된다.

Arrays.sort(arr, Comparator.comparingInt(a -> a[0]));
Arrays.sort(arr, Comparator.comparingInt(a -> a[1]));

결과

1 6
3 5
0 6
5 7
3 8
5 9
6 10
8 11
8 13
2 13
12 14
=====> sort
(3, 5)
(0, 6)
(1, 6)
(5, 7)
(3, 8)
(5, 9)
(6, 10)
(8, 11)
(2, 13)
(8, 13)
(12, 14)
  1. 우선 순위가 같은 원소들끼리는 원래의 순서를 따라가도록 하는 정렬