Programing

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

lottogame 2020. 6. 8. 07:53
반응형

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


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>. 불행히도, 그런 식으로 작동하지 않습니다.

It turns out there's a good reason it doesn't work that way: It would break the type safety generics were supposed to provide. Imagine you could assign a List<Integer> to a List<Number>. Then the following code would allow you to put something that wasn't an Integer into a List<Integer>:

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

Because ln is a List<Number>, adding a Float to it seems perfectly legal. But if ln were aliased with li, then it would break the type-safety promise implicit in the definition of li -- that it is a list of integers, which is why generic types cannot be covariant.


Arrays are covariant for at least two reasons:

  • It is useful for collections that hold information which will never change to be covariant. For a collection of T to be covariant, its backing store must also be covariant. While one could design an immutable T collection which did not use a T[] as its backing store (e.g. using a tree or linked list), such a collection would be unlikely to perform as well as one backed by an array. One might argue that a better way to provide for covariant immutable collections would have been to define a "covariant immutable array" type they could use a backing store, but simply allowing array covariance was probably easier.

  • Arrays will frequently be mutated by code which doesn't know what type of thing is going to be in them, but won't put into the array anything which wasn't read out of that same array. A prime example of this is sorting code. Conceptually it might have been possible for array types to include methods to swap or permute elements (such methods could be equally applicable to any array type), or define an "array manipulator" object which hold a reference to an array and one or more things that had been read from it, and could include methods to store previously-read items into the array from which they had come. If arrays were not covariant, user code would not be able to define such a type, but the runtime could have included some specialized methods.

The fact that arrays are covariant may be viewed as an ugly hack, but in most cases it facilitates the creation of working code.


An important feature of parametric types is the ability to write polymorphic algorithms, i.e. algorithms that operate on a data structure regardless of its parameter value, such as Arrays.sort().

With generics, that's done with wildcard types:

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

To be truly useful, wildcard types require wildcard capture, and that requires the notion of a type parameter. None of that was available at the time arrays were added to Java, and makings arrays of reference type covariant permitted a far simpler way to permit polymorphic algorithms:

void sort(Comparable[]);

However, that simplicity opened a loophole in the static type system:

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

requiring a runtime check of every write access to an array of reference type.

In a nutshell, the newer approach embodied by generics makes the type system more complex, but also more statically type safe, while the older approach was simpler, and less statically type safe. The designers of the language opted for the simpler approach, having more important things to do than closing a small loophole in the type system that rarely causes problems. Later, when Java was established, and the pressing needs taken care of, they had the resources to do it right for generics (but changing it for arrays would have broken existing Java programs).


Generics are invariant: from JSL 4.10:

...Subtyping does not extend through generic types: T <: U does not imply that C<T> <: C<U> ...

and a few lines further, JLS also explains that
Arrays are covariant (first bullet):

4.10.3 Subtyping among Array Types

enter image description here


I think they made a wrong decision at the first place that made array covariant. It breaks the type safety as it described here and they got stuck with that because of backward compatibility and after that they tried to not make the same mistake for generic. And that's one of the reasons that Joshua Bloch prefers lists to arra ys in Item 25 of book "Effective Java(second edition)"


My take: When code is expecting an array A[] and you give it B[] where B is a subclass of A, there's only two things to worry about: what happens when you read an array element, and what happens if you write it. So it's not hard to write language rules to ensure that type safety is preserved in all cases (the main rule being that an ArrayStoreException could be thrown if you try to stick an A into a B[]). For a generic, though, when you declare a class SomeClass<T>, there can be any number of ways T is used in the body of the class, and I'm guessing it's just way too complicated to work out all the possible combinations to write rules about when things are allowed and when they aren't.

참고URL : https://stackoverflow.com/questions/18666710/why-are-arrays-covariant-but-generics-are-invariant

반응형