>  기사  >  Java  >  Java Generics 요약(2) - Generics 및 배열

Java Generics 요약(2) - Generics 및 배열

黄舟
黄舟원래의
2017-03-22 10:23:431360검색

배열과 제네릭 간의 관계는 여전히 다소 복잡합니다. Java에서는 제네릭 배열을 직접 생성하는 것이 허용되지 않습니다. 이 문서에서는 이유를 분석하고 일반 배열을 만드는 몇 가지 방법을 요약합니다. 매우 좋은 참조 값을 가지고 있습니다. 아래 에디터로 살펴보겠습니다

소개

이전 글에서는 제네릭의 기본 사용법과 타입 삭제 문제에 대해 소개했으니 이제 살펴보겠습니다. 제네릭과 배열의 관계. Java 클래스 라이브러리의 컨테이너 클래스와 비교할 때 배열은 특별하며 주로 세 가지 측면에서 반영됩니다.

  • 배열이 생성된 후 크기는 고정되지만 크기는 고정됩니다. 효율성이 높다

  • 배열은 내부에 저장된 요소의 특정 유형을 추적할 수 있으며 삽입된 요소 유형은 컴파일 타임에 확인됩니다

  • 배열은 기본 유형(int, float 등)을 보유할 수 있지만 오토박싱을 사용하면 컨테이너 클래스가 기본 유형을 보유할 수 있는 것처럼 보입니다.

그래서 배열이 일반적인? 일반 배열을 만들 수 있나요? 이 글의 주요 내용입니다.

이 시리즈의 또 다른 두 기사:

  • Java Generics 요약(1): 기본 사용법 및 유형 삭제

  • Java Generics 요약(3): 와일드카드

일반 배열

사용 방법 일반 배열을 생성하려면

다음과 같은 클래스가 있는 경우:

 class Generic<T> {
 
}

일반 배열을 생성하려면 다음과 같아야 합니다. Genericc0f559cc8d56b43654fcbe4aa9df7b4a ga = new Genericc0f559cc8d56b43654fcbe4aa9df7b4a[] 그러나 코드 줄은 없습니다. 오류가 보고됩니다. 이는 일반 배열을 직접 만들 수 없음을 의미합니다.

그럼 일반 배열을 사용하려면 어떻게 해야 할까요? 한 가지 해결책은 다음 예와 같이 ArrayList을 사용하는 것입니다.

public class ListOfGenerics<T> {
 private List<T> array = new ArrayList<T>();
 public void add(T item) { array.add(item); }
 public T get(int index) { return array.get(index); }
}

실제 일반 배열을 만드는 방법은 무엇입니까? 직접 생성할 수는 없지만 일반 배열의 참조를 정의할 수 있습니다. 예를 들어

public class ArrayOfGenericReference {
 static Generic<Integer>[] gia;
}

gia 는 일반 배열에 대한 참조이며 이 코드를 컴파일할 수 있습니다. 그러나 우리는 이 정확한 유형의 배열을 생성할 수 없습니다. 즉, new Genericc0f559cc8d56b43654fcbe4aa9df7b4a[]을 사용할 수 없습니다. 자세한 내용은 다음 예를 참조하십시오.

public class ArrayOfGeneric {
 static final int SIZE = 100;
 static Generic<Integer>[] gia;
 @SuppressWarnings("unchecked")
 public static void main(String[] args) {
 // Compiles; produces ClassCastException:
 //! gia = (Generic<Integer>[])new Object[SIZE];
 // Runtime type is the raw (erased) type:
 gia = (Generic<Integer>[])new Generic[SIZE];
 System.out.println(gia.getClass().getSimpleName());
 gia[0] = new Generic<Integer>();
 //! gia[1] = new Object(); // Compile-time error
 // Discovers type mismatch at compile time:
 //! gia[2] = new Generic<Double>();
 Generic<Integer> g = gia[0];
 }
} /*输出:
Generic[]
*///:~

배열은 설정된 요소의 실제 유형을 추적할 수 있습니다. 배열이 생성될 때. 위의 에서 주석 처리한 코드 줄: gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Object[SIZE], 배열은 생성될 때 객체 배열이며 변환되면 오류가 보고됩니다. 일반 배열을 성공적으로 생성하는 유일한 방법은 유형이 지워진 배열을 생성한 다음 코드에서와 같이 캐스팅하는 것입니다. gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE], gia의 클래스 객체 의 출력 이름은 Generic[]입니다.

개인적으로 이해하기로는 유형 삭제로 인해 Genericc0f559cc8d56b43654fcbe4aa9df7b4a는 초기 유형 Generic과 동일하므로 gia = (Genericc0f559cc8d56b43654fcbe4aa9df7b4a[])new Generic[SIZE] 의 변환은 실제로 Generic[]으로 변환되며 이는 변환이 없는 것처럼 보입니다. , 그러나 컴파일러의 매개변수 확인 및 자동 변환을 사용하면 new Object()new Genericeafb63d086dd6c9bd19609d76bcc2869()을 배열에 삽입하면 오류가 보고되며 gia[0]을 Genericc0f559cc8d56b43654fcbe4aa9df7b4a으로 가져오면 수동으로 변환할 필요가 없습니다.

T[] 배열 사용

위 예에서 요소의 유형은 일반 클래스입니다. 요소의 유형 자체가 일반 매개변수인 예를 살펴보겠습니다.

public class GenericArray<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArray(int sz) {
 array = (T[])new Object[sz]; // 创建泛型数组
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

위 코드에서 일반 배열의 생성은 객체 배열을 생성한 다음 이를 T[]로 변환하는 것입니다. 그러나 배열의 실제 유형은 여전히 ​​Object[]입니다. Rep() 메서드를 호출하면 Object[]를 Integer[]로 변환할 수 없기 때문에 ClassCastException이 보고됩니다.

그럼 일반 배열을 생성하는 코드는 array = (T[])new Object[sz] 왜 오류가 보고되지 않나요? 이전에 소개한 내용과 비슷합니다. 유형 삭제로 인해 Object[]로의 변환과 동일하지만, 추가 매개변수 확인 및 자동 변환이 있습니다. 컴파일러. 그리고 일반 매개변수를 ba95df6fbb265e67dd0723036aced316으로 변경하면 유형이 첫 번째 경계까지 지워지기 때문에 array = (T[])new Object[sz]Integer[]로 변환하는 것과 동일하며 오류가 발생합니다. 다음은 실험 코드입니다.

public class GenericArray<T extends Integer> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArray(int sz) {
 array = (T[])new Object[sz]; // 创建泛型数组
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Method that exposes the underlying representation:
 public T[] rep() { return array; } //返回数组 会报错
 public static void main(String[] args) {
 GenericArray<Integer> gai =
 new GenericArray<Integer>(10);
 // This causes a ClassCastException:
 //! Integer[] ia = gai.rep();
 // This is OK:
 Object[] oa = gai.rep();
 }
}

원래 버전과 비교했을 때 위 코드는 첫 번째 줄만 수정하여  8742468051c85b06f0a0af9e3e506b5cba95df6fbb265e67dd0723036aced316으로 변경했습니다. 그러면 rep()을 호출할 필요가 없습니다. 팬형 배열을 생성할 때 오류가 보고됩니다. 실행 결과는 다음과 같습니다.

Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at GenericArray.<init>(GenericArray.java:15)

Object[] 배열 사용

由于擦除,运行期的数组类型只能是 Object[],如果我们立即把它转型为 T[],那么在编译期就失去了数组的实际类型,编译器也许无法发现潜在的错误。因此,更好的办法是在内部最好使用 Object[] 数组,在取出元素的时候再转型。看下面的例子:

public class GenericArray2<T> {
 private Object[] array;
 public GenericArray2(int sz) {
 array = new Object[sz];
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 @SuppressWarnings("unchecked")
 public T get(int index) { return (T)array[index]; }
 @SuppressWarnings("unchecked")
 public T[] rep() {
 return (T[])array; // Warning: unchecked cast
 }
 public static void main(String[] args) {
 GenericArray2<Integer> gai =
 new GenericArray2<Integer>(10);
 for(int i = 0; i < 10; i ++)
 gai.put(i, i);
 for(int i = 0; i < 10; i ++)
 System.out.print(gai.get(i) + " ");
 System.out.println();
 try {
 Integer[] ia = gai.rep();
 } catch(Exception e) { System.out.println(e); }
 }
} /* Output: (Sample)
0 1 2 3 4 5 6 7 8 9
java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*///:~

现在内部数组的呈现不是 T[] 而是 Object[],当 get() 被调用的时候数组的元素被转型为 T,这正是元素的实际类型。不过调用 rep() 还是会报错, 因为数组的实际类型依然是Object[],终究不能转换为其它类型。使用 Object[] 代替 T[] 的好处是让我们不会忘记数组运行期的实际类型,以至于不小心引入错误。

使用类型标识

其实使用 Class 对象作为类型标识是更好的设计:

public class GenericArrayWithTypeToken<T> {
 private T[] array;
 @SuppressWarnings("unchecked")
 public GenericArrayWithTypeToken(Class<T> type, int sz) {
 array = (T[])Array.newInstance(type, sz);
 }
 public void put(int index, T item) {
 array[index] = item;
 }
 public T get(int index) { return array[index]; }
 // Expose the underlying representation:
 public T[] rep() { return array; }
 public static void main(String[] args) {
 GenericArrayWithTypeToken<Integer> gai =
 new GenericArrayWithTypeToken<Integer>(
 Integer.class, 10);
 // This now works:
 Integer[] ia = gai.rep();
 }
}

在构造器中传入了 Class8742468051c85b06f0a0af9e3e506b5c 对象,通过 Array.newInstance(type, sz) 创建一个数组,这个方法会用参数中的 Class 对象作为数组元素的组件类型。这样创建出的数组的元素类型便不再是 Object,而是 T。这个方法返回 Object 对象,需要把它转型为数组。不过其他操作都不需要转型了,包括 rep() 方法,因为数组的实际类型与 T[] 是一致的。这是比较推荐的创建泛型数组的方法。

总结

数组与泛型的关系还是有点复杂的,Java 中不允许直接创建泛型数组。本文分析了其中原因并且总结了一些创建泛型数组的方式。其中有部分个人的理解,如果错误希望大家指正。下一篇会总结通配符的使用。

위 내용은 Java Generics 요약(2) - Generics 및 배열의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.