[java] 왜 배열이 공변이지만 제네릭은 변하지 않습니까?

Joshua Bloch의 효과적인 Java에서

  1. 배열은 두 가지 중요한 점에서 일반 유형과 다릅니다. 첫 번째 배열은 공변량입니다. 제네릭은 변하지 않습니다.
  2. 공변량은 단순히 X가 Y의 하위 유형 인 경우 X []도 Y []의 하위 유형이됨을 의미합니다. 배열이 공변량 임 string이 Object의 하위 유형이므로

    String[] is subtype of Object[]

    불변은 단순히 X가 Y의 하위 유형인지 여부에 관계없이 단순히

     List<X> will not be subType of List<Y>.

내 질문은 왜 배열을 Java에서 공변량으로 만드는 결정입니까? 왜 배열이 변하지 않습니까? 목록 공변량입니까? 와 같은 다른 SO 게시물이 있습니다 . 그러나 그들은 Scala에 초점을 맞춘 것으로 보이며 따라갈 수 없습니다.



답변

Wikipedia를 통해 :

Java 및 C #의 초기 버전에는 제네릭 (일명 파라 메트릭 다형성)이 포함되지 않았습니다.

이러한 설정에서, 배열을 변하지 않게 만드는 것은 유용한 다형성 프로그램을 배제합니다. 예를 들어, 배열을 섞기위한 함수 또는 Object.equals요소 의 메소드를 사용하여 두 배열의 동등성을 테스트하는 함수를 작성해보십시오 . 구현은 배열에 저장된 정확한 유형의 요소에 의존하지 않으므로 모든 유형의 배열에서 작동하는 단일 함수를 작성할 수 있어야합니다. 유형의 기능을 쉽게 구현

boolean equalArrays (Object[] a1, Object[] a2);
void shuffleArray(Object[] a);

그러나 배열 유형이 변하지 않는 것으로 취급되면 정확히 유형의 배열에서만 이러한 함수를 호출 할 수 있습니다 Object[]. 예를 들어 문자열 배열을 섞을 수 없었습니다.

따라서 Java와 C # 모두 배열 유형을 공변량으로 취급합니다. 예를 들어 C # string[]에서 하위 유형은 object[]이고 Java String[]에서 하위 유형은 Object[]입니다.

이것은 “왜, 더 정확하게”? 배열이 공변 왜 “라는 질문에 응답, 또는 했다 공변을 만든 배열 시간에 ?”

제네릭이 소개되었을 때 Jon Skeet 의이 답변 에서 지적한 이유로 의도적으로 공변량이되지 않았습니다 .

아니요, a List<Dog>List<Animal>입니다. 당신이 할 수있는 일을 고려하십시오 List<Animal>-고양이를 포함하여 동물을 추가 할 수 있습니다. 자, 강아지의 쓰레기에 논리적으로 고양이를 추가 할 수 있습니까? 절대적으로하지.

// Illegal code - because otherwise life would be Bad
List<Dog> dogs = new List<Dog>();
List<Animal> animals = dogs; // Awooga awooga
animals.add(new Cat());
Dog dog = dogs.get(0); // This should be safe, right?

갑자기 당신은 매우 혼란스러운 고양이 를 가지고 있습니다 .

와일드 카드 는 공분산 (및 공분산) 표현을 가능하게 했기 때문에 위키 백과 기사에 설명 된 배열을 공변량으로 만드는 원래 동기는 제네릭에 적용되지 않았습니다 .

boolean equalLists(List<?> l1, List<?> l2);
void shuffleList(List<?> l);


답변

그 이유는 모든 배열이 런타임 동안 요소 유형을 알고 있지만 일반 컬렉션은 유형 삭제 때문에 발생하지 않기 때문입니다.

예를 들면 다음과 같습니다.

String[] strings = new String[2];
Object[] objects = strings;  // valid, String[] is Object[]
objects[0] = 12; // error, would cause java.lang.ArrayStoreException: java.lang.Integer during runtime

이것이 일반 컬렉션에서 허용 된 경우 :

List<String> strings = new ArrayList<String>();
List<Object> objects = strings;  // let's say it is valid
objects.add(12);  // invalid, Integer should not be put into List<String> but there is no information during runtime to catch this

그러나 나중에 누군가가 목록에 액세스하려고 할 때 문제가 발생할 수 있습니다.

String first = strings.get(0); // would cause ClassCastException, trying to assign 12 to String


답변

수 있음 도움이 : –

제네릭은 공변량이 아닙니다

Java 언어의 배열은 공변량입니다. 즉, Integer가 Number를 확장하면 (정확한) Integer도 Number 일뿐만 아니라 Integer []도 a Number[]이므로 전달하거나 할당 할 수 있습니다. Integer[]a Number[]가 필요한 곳. (공식적으로, Number가 Integer Number[]의 수퍼 타입 ​​인 경우 수퍼 타입은의 수퍼 타입 ​​인 경우도 Integer[]있습니다.) 제네릭 형식의 경우에도 마찬가지입니다. 즉 List<Number>,의 수퍼 타입 이며 예상 List<Integer>되는 List<Integer>위치를 전달할 수 있습니다 List<Number>. 불행히도, 그런 식으로 작동하지 않습니다.

그것이 그렇게 작동하지 않는 좋은 이유가 있음이 밝혀졌습니다. 에 a List<Integer>을 할당 할 수 있다고 상상해보십시오 List<Number>. 그런 다음 다음 코드를 사용하면 정수가 아닌 것을 다음에 넣을 수 있습니다 List<Integer>.

List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));

ln은이므로 List<Number>Float를 추가하는 것은 완벽하게 합법적입니다. 그러나 ln이와 별칭을 li가졌다면 li의 정의에서 암시적인 타입 안전성 약속을 깨뜨릴 것입니다. 정수 타입이기 때문에 제네릭 타입은 공변량이 될 수 없습니다.


답변

배열은 최소 두 가지 이유로 공변량입니다.

  • 공변량으로 변경되지 않는 정보를 보유한 컬렉션에 유용합니다. T의 집합이 공변량 인 경우, 백업 저장소도 공변량이어야합니다. 백킹 스토어로 T사용하지 않는 불변 컬렉션을 설계 할 수 있지만 T[](예 : 트리 또는 링크 된 목록 사용) 이러한 컬렉션은 어레이에서 지원하는 것만 큼 수행되지 않을 것입니다. 공변량 불변 컬렉션을 제공하는 더 좋은 방법은 백업 저장소를 사용할 수있는 “공변량 불변 배열”유형을 정의하는 것이었을 수도 있지만 단순히 배열 공분산을 허용하는 것이 더 쉬울 것입니다.

  • 배열은 어떤 유형의 항목이 있는지 알지 못하지만 동일한 배열에서 읽지 않은 것은 배열에 넣지 않는 코드에 의해 종종 변경됩니다. 이것의 주요 예는 정렬 코드입니다. 개념적으로 배열 유형에 요소를 교환하거나 치환하는 메소드 (이러한 메소드는 모든 배열 유형에 동일하게 적용 가능)를 포함하거나 배열 및 하나 이상의 항목에 대한 참조를 보유하는 “배열 조작기”오브젝트를 정의하는 것이 가능했을 수 있습니다. 그것에서 읽었으며 이전에 읽은 항목을 원래 배열에 저장하는 메소드를 포함 할 수 있습니다. 배열이 공변량이 아닌 경우 사용자 코드는 이러한 유형을 정의 할 수 없지만 런타임에는 특수화 된 메소드가 포함될 수 있습니다.

배열이 공변량이라는 사실은 추악한 해킹으로 간주 될 수 있지만 대부분의 경우 작업 코드를 쉽게 만들 수 있습니다.


답변

파라 메트릭 유형의 중요한 기능은 다형성 알고리즘, 즉 파라미터 값에 관계없이 데이터 구조에서 작동하는 알고리즘을 작성할 수있는 기능 Arrays.sort()입니다.

제네릭을 사용하면 와일드 카드 유형이 사용됩니다.

<E extends Comparable<E>> void sort(E[]);

실제로 유용하려면 와일드 카드 유형에는 와일드 카드 캡처가 필요하며 유형 매개 변수의 개념이 필요합니다. 배열이 Java에 추가 될 당시에는 그 중 어느 것도 사용할 수 없었으며 참조 유형 공변량의 배열을 만들면 다형성 알고리즘을 허용하는 훨씬 간단한 방법이 허용되었습니다.

void sort(Comparable[]);

그러나 이러한 단순성으로 인해 정적 유형 시스템에 허점이 생겼습니다.

String[] strings = {"hello"};
Object[] objects = strings;
objects[0] = 1; // throws ArrayStoreException

참조 유형의 배열에 대한 모든 쓰기 액세스의 런타임 검사가 필요합니다.

간단히 말해서, 제네릭에 의해 구현 된 새로운 접근 방식은 유형 시스템을 더 복잡하게 만들지 만 정적으로 유형이 더 안전하지만 이전의 접근 방식은 더 단순하고 정적으로 덜 안전합니다. 언어 설계자들은 문제를 거의 일으키지 않는 타입 시스템의 작은 허점을 막는 것보다 더 중요한 일을하는 단순한 접근 방식을 선택했습니다. 나중에, Java가 설립되고 긴급한 요구가 처리 될 때, 제네릭에 대해 올바른 작업을 수행 할 수있는 리소스가있었습니다.


답변

제네릭은 변하지 않습니다 : JSL 4.10 부터 :

하위 유형은 일반 유형을 통해 확장되지 않습니다. T <: U는 C<T><: C<U>

JLS는 또한
배열이 공변량 (첫 번째 글 머리 기호) 이라고 설명합니다 .

4.10.3 배열 유형의 하위 유형

여기에 이미지 설명을 입력하십시오


답변

배열 공변량을 만든 첫 번째 장소에서 잘못된 결정을 내렸다고 생각합니다. 여기에 설명 된대로 형식 안전성을 깨뜨리고 이전 버전과의 호환성으로 인해 일반 형식에 대해 동일한 실수를 시도하지 않았습니다. 그것이 바로 Joshua Bloch가 “Effective Java (second edition)”책 25 번 항목의 목록을 선호 하는 이유 중 하나입니다.