Java가 우리를 허용하지 않는 이유는 무엇입니까?
private T[] elements = new T[initialCapacity];
.NET에서 런타임에 다른 크기를 가질 수있는 값 유형이 있기 때문에 .NET이 그렇게 할 수 없다는 것을 이해할 수 있었지만 Java에서는 모든 종류의 T가 객체 참조이므로 동일한 크기 ( 틀 렸으면 말해줘).
이유가 무엇입니까?
Java가 우리를 허용하지 않는 이유는 무엇입니까?
private T[] elements = new T[initialCapacity];
.NET에서 런타임에 다른 크기를 가질 수있는 값 유형이 있기 때문에 .NET이 그렇게 할 수 없다는 것을 이해할 수 있었지만 Java에서는 모든 종류의 T가 객체 참조이므로 동일한 크기 ( 틀 렸으면 말해줘).
이유가 무엇입니까?
답변:
제네릭과 달리 Java의 배열에는 런타임에 구성 요소 유형에 대한 정보가 포함되어 있기 때문입니다. 따라서 배열을 만들 때 구성 요소 유형을 알아야합니다. T
런타임에 무엇인지 모르므로 배열을 만들 수 없습니다.
ArrayList <SomeType>
합니까?
new ArrayList<SomeType>()
? 일반 유형은 런타임시 유형 매개 변수를 포함하지 않습니다. type 매개 변수는 작성에 사용되지 않습니다. 에 의해 생성 된 코드에는 차이가 없다 new ArrayList<SomeType>()
거나 new ArrayList<String>()
또는 new ArrayList()
전혀은.
ArrayList<T>
그것의 작동 방식 에 대해 더 묻고 있었다 private T[] myArray
. 코드 어딘가에 제네릭 형식 T의 배열이 있어야합니다. 어떻게?
T[]
. 런타임 유형의 배열이 Object[]
있으며 1) 소스 코드에 변수가 들어 있습니다 Object[]
(이것이 최신 Oracle Java 소스에있는 방법입니다). 또는 2) 소스 코드에 유형의 변수가 포함되어 있습니다.이 변수 T[]
는 거짓말이지만 T
클래스 범위 내에서 지워져서 문제가 발생하지 않습니다 .
인용문:
제네릭 형식의 배열은 소리가 나지 않으므로 사용할 수 없습니다. 문제는 정적으로 건전하지 않지만 동적으로 확인되는 Java 배열과 정적으로 건전하고 동적으로 확인되지 않은 제네릭을 사용하여 상호 작용하기 때문입니다. 허점을 활용하는 방법은 다음과 같습니다.
class Box<T> { final T x; Box(T x) { this.x = x; } } class Loophole { public static void main(String[] args) { Box<String>[] bsa = new Box<String>[3]; Object[] oa = bsa; oa[0] = new Box<Integer>(3); // error not caught by array store check String s = bsa[0].x; // BOOM! } }
Tiger에서는 거부 된 정적 안전 배열 (일명 Variance) 버트를 사용하여이 문제를 해결하도록 제안했습니다.
- 사후
(나는 그것이 Neal Gafter 라고 생각 하지만 확실하지 않습니다)
컨텍스트에서 참조하십시오 : http://forums.sun.com/thread.jspa?threadID=457033&forumID=316
new T()
. Java의 각 배열은 설계 상으로 구성 요소 유형 (예 :)을 저장 T.class
합니다. 따라서 이러한 배열을 만들려면 런타임에 T 클래스가 필요합니다.
new Box<?>[n]
의 예제에서는 도움이되지 않지만 때때로 충분할 수도 있습니다.
Box<String>[] bsa = new Box<String>[3];
java-8에서 아무것도 바뀌지 않았다.
적절한 솔루션을 제공하지 않으면 더 나쁜 IMHO로 끝납니다.
일반적인 해결 방법은 다음과 같습니다.
T[] ts = new T[n];
(T가 다른 클래스가 아닌 Object를 확장한다고 가정)
T[] ts = (T[]) new Object[n];
나는 첫 번째 예를 선호하지만 더 많은 학업 유형이 두 번째 예를 선호하거나 생각하지 않는 것을 선호합니다.
Object []를 사용할 수없는 이유의 대부분의 예는 List 또는 Collection (지원되는)에 동일하게 적용되므로 매우 나쁜 인수로 간주합니다.
참고 : 이것이 Collections 라이브러리 자체가 경고없이 컴파일되지 않는 이유 중 하나입니다. 이 사용 사례를 경고없이 지원할 수없는 경우 제네릭 모델 IMHO에서 근본적으로 문제가 발생합니다.
String[]
(또는 공개적으로 액세스 가능한 유형의 필드에 저장하고 검색하는 필드) 에 이러한 방식으로 작성된 배열을 리턴하면 T[]
ClassCastException이 발생합니다.
T[] ts = (T[]) new Object[n];
: 나쁜 생각 stackoverflow.com/questions/21577493/...
T[] ts = new T[n];
유효한 사례 라고 말했지만 . 나는 그 답변이 다른 개발자들에게 문제와 혼란을 야기 할 수 있고 주제를 벗어 났기 때문에 투표를 계속할 것입니다. 또한 이것에 대한 언급을 중단합니다.
배열은 공변량입니다
배열은 공변량이라고하며 기본적으로 Java의 하위 유형 지정 규칙에서 유형의 배열은 유형의
T[]
요소T
또는의 하위 유형을 포함 할 수 있습니다T
. 예를 들어
Number[] numbers = new Number[3];
numbers[0] = newInteger(10);
numbers[1] = newDouble(3.14);
numbers[2] = newByte(0);
그러나 Java의 하위 유형 지정 규칙은 또한
S[]
의 하위 유형 인 경우 배열이 배열 의 하위 유형이라고T[]
명시 하므로 다음과 같은 것도 유효합니다.S
T
Integer[] myInts = {1,2,3,4};
Number[] myNumber = myInts;
Java의 하위 유형 지정 규칙에 따라 배열
Integer[]
은 배열 의 하위 유형이므로Number[]
정수는 숫자의 하위 유형이므로 의 하위 유형입니다.그러나이 하위 유형 규칙은 흥미로운 질문으로 이어질 수 있습니다. 우리가 이것을 시도하면 어떻게 될까요?
myNumber[0] = 3.14; //attempt of heap pollution
이 마지막 줄은 잘 컴파일되지만이 코드를 실행하면 ArrayStoreException
하면 정수 배열에 double을 넣으려고하기 때문에 있습니다. 여기서 Number 참조를 통해 배열에 액세스한다는 사실은 중요하지 않습니다. 중요한 것은 배열이 정수 배열이라는 것입니다.
이는 컴파일러를 속일 수는 있지만 런타임 유형 시스템을 속일 수는 없다는 것을 의미합니다. 배열은 우리가 reifiable 타입이라고 부르기 때문입니다. 이는 런타임시 Java가이 배열이 실제로 유형의 참조를 통해 액세스되는 정수 배열로 인스턴스화되었음을 알고 있음을 의미합니다.Number[]
.
보시다시피, 하나는 객체의 실제 유형이고 다른 하나는 객체에 액세스하는 데 사용하는 참조의 유형입니다.
자바 제네릭의 문제점
이제 Java에서 제네릭 형식의 문제점은 코드 컴파일이 완료된 후 형식 매개 변수의 형식 정보가 컴파일러에 의해 삭제된다는 것입니다. 따라서이 유형 정보는 런타임에 사용할 수 없습니다. 이 과정을 유형 삭제 라고 합니다 . Java에서 이와 같은 제네릭을 구현하는 데는 합당한 이유가 있지만, 이는 오랜 이야기이며 기존 코드와 바이너리 호환성과 관련이 있습니다.
여기서 중요한 점은 런타임에 유형 정보가 없기 때문에 힙 오염을 저 지르지 않도록 보장 할 방법이 없다는 것입니다.
다음과 같은 안전하지 않은 코드를 살펴 보겠습니다.
List<Integer> myInts = newArrayList<Integer>();
myInts.add(1);
myInts.add(2);
List<Number> myNums = myInts; //compiler error
myNums.add(3.14); //heap polution
Java 컴파일러가이를 수행하지 못하게하는 경우 런타임 유형 시스템은 런타임시이 목록이 정수 목록으로 만 가정 된 방법이 없기 때문에 중지 할 수 없습니다. Java 런타임은 정수만 포함해야 할 때이 목록에 원하는 것을 넣을 수 있습니다. 생성 될 때 정수 목록으로 선언 되었기 때문입니다. 그렇기 때문에 컴파일러는 라인 번호 4를 안전하지 않기 때문에 거부합니다.
따라서 Java 디자이너는 컴파일러를 속일 수 없도록했습니다. 우리가 컴파일러를 속일 수 없다면 (배열로 할 수있는 것처럼) 런타임 타입 시스템을 속일 수 없습니다.
따라서 런타임에 일반 유형의 실제 특성을 판별 할 수 없으므로 일반 유형은 수정할 수 없다고 말합니다.
이 답변의 일부를 건너 뛰었습니다. https://dzone.com/articles/covariance-and-contravariance
이것이 불가능한 이유는 Java가 컴파일러 레벨에서 순수하게 Generics를 구현하고 각 클래스마다 하나의 클래스 파일 만 생성되기 때문입니다. 이것을 유형 소 거라고합니다. 합니다.
런타임시 컴파일 된 클래스는 모든 사용을 동일한 바이트 코드로 처리해야합니다. 따라서 new T[capacity]
어떤 유형을 인스턴스화해야하는지 전혀 모를 것입니다.
대답은 이미 제공되었지만 이미 인스턴스가 T 인 경우 다음을 수행 할 수 있습니다.
T t; //Assuming you already have this object instantiated or given by parameter.
int length;
T[] ts = (T[]) Array.newInstance(t.getClass(), length);
희망, 나는 도울 수 있었다, Ferdi265
T[] ts = t.clone(); for (int i=0; i<ts.length; i++) ts[i] = null;
..
T[] t
한 것이이면 그렇습니다 (T[]) Array.newInstance(t.getClass().getComponentType(), length);
. 나는 알아 내기 위해 몇 시간을 보냈다 getComponentType()
. 이것이 다른 사람들을 돕기를 바랍니다.
t.clone()
는 반환하지 않습니다 T[]
. t
이 답변에서 배열이 아니기 때문 입니다.
주된 이유는 Java의 배열이 공변량이기 때문입니다.
여기에 좋은 개요가 있습니다 .
String[]
에 Object
와 저장 Integer
거기에. 따라서 ArrayStoreException
컴파일 타임에 문제를 잡을 수 없으므로 배열 저장소 ( )에 대한 런타임 유형 검사를 추가해야했습니다 . (그렇지 않으면, Integer
실제로는에 갇힐 수 있으며 String[]
, 검색하려고 할 때 오류가 발생하여 끔찍할 수 있습니다.) ...
Object
있었어야 Object[]
했어요
나는 Gafter에 의해 간접적으로 주어진 대답을 좋아한다 . 그러나 나는 그것이 틀렸다고 제안한다. 나는 Gafter의 코드를 약간 변경했습니다. 컴파일하고 잠시 동안 실행 한 다음 Gafter가 예상 한 곳에서 폭탄을 터트립니다.
class Box<T> {
final T x;
Box(T x) {
this.x = x;
}
}
class Loophole {
public static <T> T[] array(final T... values) {
return (values);
}
public static void main(String[] args) {
Box<String> a = new Box("Hello");
Box<String> b = new Box("World");
Box<String> c = new Box("!!!!!!!!!!!");
Box<String>[] bsa = array(a, b, c);
System.out.println("I created an array of generics.");
Object[] oa = bsa;
oa[0] = new Box<Integer>(3);
System.out.println("error not caught by array store check");
try {
String s = bsa[0].x;
} catch (ClassCastException cause) {
System.out.println("BOOM!");
cause.printStackTrace();
}
}
}
출력은
I created an array of generics.
error not caught by array store check
BOOM!
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at Loophole.main(Box.java:26)
그래서 자바에서 일반 배열 유형을 만들 수있는 것처럼 보입니다. 질문을 오해 했습니까?
나는 여기서 파티에 조금 늦었다는 것을 알고 있지만,이 답변 중 어느 것도 내 문제를 해결하지 못했기 때문에 미래의 Google 직원을 도울 수있을 것이라고 생각했습니다. Ferdi265의 답변은 엄청난 도움이되었습니다.
내 자신의 연결 목록을 만들려고하므로 다음 코드가 저에게 효과적입니다.
package myList;
import java.lang.reflect.Array;
public class MyList<TYPE> {
private Node<TYPE> header = null;
public void clear() { header = null; }
public void add(TYPE t) { header = new Node<TYPE>(t,header); }
public TYPE get(int position) { return getNode(position).getObject(); }
@SuppressWarnings("unchecked")
public TYPE[] toArray() {
TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());
for(int i=0 ; i<size() ; i++) result[i] = get(i);
return result;
}
public int size(){
int i = 0;
Node<TYPE> current = header;
while(current != null) {
current = current.getNext();
i++;
}
return i;
}
toArray () 메서드에는 제네릭 형식의 배열을 만드는 방법이 있습니다.
TYPE[] result = (TYPE[])Array.newInstance(header.getObject().getClass(),size());
필자의 경우 단순히 다음과 같은 스택 배열을 원했습니다.
Stack<SomeType>[] stacks = new Stack<SomeType>[2];
이것이 불가능했기 때문에 다음을 해결 방법으로 사용했습니다.
추악하지만 Java는 행복합니다.
참고 : 질문에 대한 의견에서 BrainSlugs83에서 언급했듯이 .NET에서 제네릭 배열을 가질 수 있습니다.
에서 오라클 튜토리얼 :
매개 변수화 된 유형의 배열을 작성할 수 없습니다. 예를 들어 다음 코드는 컴파일되지 않습니다.
List<Integer>[] arrayOfLists = new List<Integer>[2]; // compile-time error
다음 코드는 다른 유형이 배열에 삽입 될 때 발생하는 상황을 보여줍니다.
Object[] strings = new String[2]; strings[0] = "hi"; // OK strings[1] = 100; // An ArrayStoreException is thrown.
일반 목록으로 같은 것을 시도하면 문제가 있습니다.
Object[] stringLists = new List<String>[]; // compiler error, but pretend it's allowed stringLists[0] = new ArrayList<String>(); // OK stringLists[1] = new ArrayList<Integer>(); // An ArrayStoreException should be thrown, // but the runtime can't detect it.
매개 변수화 된 목록의 배열이 허용 된 경우 이전 코드는 원하는 ArrayStoreException을 발생시키지 않습니다.
나에게 그것은 매우 약하게 들린다. 나는 제네릭을 충분히 이해하고 있다면 완벽하게 괜찮을 것이라고 생각하며 심지어 그러한 경우 ArrayStoredException이 발생하지 않을 것이라고 생각합니다.
분명히 그 주위에 좋은 방법이 있어야합니다 (반사를 사용하고있을 수도 있습니다) ArrayList.toArray(T[] a)
. 나는 인용한다 :
public <T> T[] toArray(T[] a)
이리스트 내의 모든 요소를 포함한 순서를 올바른 순서로 돌려줍니다. 리턴 된 배열의 런타임 유형은 지정된 배열의 런타임 유형입니다. 목록이 지정된 배열에 맞는 경우 반환됩니다. 그렇지 않으면 지정된 배열의 런타임 유형과이 목록의 크기로 새 배열이 할당됩니다.
따라서 한 가지 방법은이 함수를 사용하는 것입니다. 즉 ArrayList
배열에 원하는 객체를 만든 다음toArray(T[] a)
실제 배열을 만드는 데 합니다. 빠르지는 않지만 요구 사항에 대해서는 언급하지 않았습니다.
누구나 toArray(T[] a)
구현 방법을 알고 있습니까?
Array.newInstance()
. 컴파일 타임에 알 수없는 유형의 배열을 만드는 방법을 묻는 많은 질문에 언급 된 것을 찾을 수 있습니다. 그러나 OP는 구문을 사용할 수없는 이유 를 구체적으로 묻고있었습니다 new T[]
. 이는 다른 질문입니다
일반 배열을 인스턴스화 할 수없는 경우 왜 언어에 일반 배열 유형이 있습니까? 객체가없는 유형의 요점은 무엇입니까?
내가 생각할 수있는 유일한 이유는 varargs foo(T...)
입니다. 그렇지 않으면 일반 배열 유형을 완전히 제거 할 수 있습니다. varargs가 1.5 이전에는 존재하지 않았기 때문에 varargs에 실제로 배열을 사용할 필요는 없었습니다. . 아마도 다른 실수 일 것입니다.
거짓말이야, 넌 할 수있어 varargs를 통해 일반 배열을 인스턴스화 !
물론 일반적인 배열의 문제는 여전히 실제입니다.
static <T> T[] foo(T... args){
return args;
}
static <T> T[] foo2(T a1, T a2){
return foo(a1, a2);
}
public static void main(String[] args){
String[] x2 = foo2("a", "b"); // heap pollution!
}
이 예제를 사용하여 실제로 일반 배열 의 위험을 보여줄 수 있습니다 .
반면에, 우리는 10 년 동안 일반적인 varargs를 사용해 왔으며 하늘은 아직 떨어지지 않습니다. 따라서 우리는 문제가 과장되었다고 주장 할 수 있습니다. 큰 문제가 아닙니다. 명시적인 일반 배열 생성이 허용되면 여기저기서 버그가 발생합니다. 그러나 우리는 소거의 문제에 익숙해 져 있으며, 그와 함께 살 수 있습니다.
그리고 우리는 foo2
스펙이 우리를 막아주는 문제로부터 우리를 지켜 준다는 주장을 반박 할 수 있습니다 . 만약 썬이 1.5 시간 동안 더 많은 시간과 자원을 가지고 있다면 더 만족스러운 해결책에 도달 할 수 있다고 생각합니다.
다른 사람들이 이미 언급했듯이 물론 몇 가지 트릭을 통해 만들 수 있습니다. .
그러나 권장하지 않습니다.
때문에 유형 삭제 더 중요한 것은 covariance
단지 하위 배열을 허용 배열은 런타임의 원인이 값 다시 얻을하려고 할 때 명시 적 유형 캐스트를 사용하는 힘 슈퍼 배열에 할당 할 수 있습니다 ClassCastException
주요 목표 중 하나입니다 그 제네릭은 제거하려고합니다 : 컴파일시 더 강력한 유형 검사 .
Object[] stringArray = { "hi", "me" };
stringArray[1] = 1;
String aString = (String) stringArray[1]; // boom! the TypeCastException
보다 직접적인 예제는 Effective Java : Item 25 에서 찾을 수 있습니다 .
공분산 : S [] 유형의 배열은 S가 T의 하위 유형 인 경우 T []의 하위 유형입니다.
클래스가 매개 변수화 된 형식으로 사용되는 경우 T [] 형식의 배열을 선언 할 수 있지만 이러한 배열을 직접 인스턴스화 할 수는 없습니다. 대신 일반적인 접근 방식은 Object [] 유형의 배열을 인스턴스화 한 후 다음과 같이 T [] 유형으로 범위를 좁히는 것입니다.
public class Portfolio<T> {
T[] data;
public Portfolio(int capacity) {
data = new T[capacity]; // illegal; compiler error
data = (T[]) new Object[capacity]; // legal, but compiler warning
}
public T get(int index) { return data[index]; }
public void set(int index, T element) { data[index] = element; }
}