> Java > java지도 시간 > Java 제네릭 관련 지식에 대한 자세한 설명(코드 포함)

Java 제네릭 관련 지식에 대한 자세한 설명(코드 포함)

不言
풀어 주다: 2019-02-22 13:35:25
앞으로
2359명이 탐색했습니다.

이 기사는 Java 제네릭(코드 포함)에 대한 자세한 지식을 제공합니다. 필요한 친구가 참고할 수 있기를 바랍니다.

모든 사람이 제네릭의 사용에 매우 익숙하다고 생각하지만 유형 삭제, 경계 확장 등에 대한 세부 사항은 명확하지 않을 수 있으므로 이 기사에서는 제네릭에 대한 이해와 함께 이에 대한 설명에 중점을 둘 것입니다. 실제로 볼 수 있는 것은 언어 기능의 생성 논리는 우리의 일상적인 개발에도 매우 도움이 됩니다.

1. 제네릭이 나타나는 이유

우선 제네릭은 Java의 언어 기능이 아닙니다. JDK1.5까지는 지원되지 않았습니다(구체적인 차이점은 나중에 논의됩니다). 그러면 제네릭이 나타나기 전에는 어떤 작업이 수행되었습니까?

List list = new ArrayList();
list.add("123");
String s = (String) list.get(0);
로그인 후 복사

위 코드에서 볼 수 있듯이 컬렉션에 넣은 내용을 기억한 다음 꺼낼 때 강제로 변환해야 합니다. 이렇게 하면 이 유형 변환 오류가 런타임으로 연기됩니다. 즉, 아직 안전하지 않습니다.

사용 시나리오: 제네릭 클래스, 제네릭 인터페이스, 제네릭 메서드

public class Test<T>
public interface Test<T>
public <T> void test(T t)
로그인 후 복사

2. 제네릭은 어떤 종류의 문제를 가져올까요?

위에서 언급했듯이 제네릭 유형은 다음과 같은 기능이 아닙니다. Java는 처음부터 있으므로 나중에 제네릭을 추가하려면 이전 버전과 호환되어야 합니다. Sun이 제시한 절충 솔루션은 제네릭을 의미하는 type erasure입니다. 정보는 컴파일 중에만 존재하며 모든 제네릭 정보는 런타임 중에 지워지고 아무것도 남지 않습니다.

List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass());
System.out.println(list2.getClass() == list1.getClass());
로그인 후 복사

// 인쇄:
class java.util.ArrayList
true

List<String>List<String><을 볼 수 있습니다. /code>는 실제로 런타임에 동일하며 둘 다 <code>class java.util.ArrayList입니다. 따라서 제네릭을 사용할 때 List<String>List<String> 在运行时其实都是一样的,都是class java.util.ArrayList;所以在使用泛型的时候需要牢记,在运行时期没有泛型信息,也无法获取任何有关参数类型的信息;所以凡是需要获取运行时类型的操作,泛型都不支持!

1. 不能用基本类型实例化类型参数

new ArrayList<int>();      // error
new ArrayList<Integer>();  // correct
로그인 후 복사

因为类型擦除,会擦除到他的上界也就是 Object;而 Java 的8个基本类型的直接父类是 Number,所以基本类型不不能用基本类型实例化类型参数,而必须使用基本类型的包装类;

2. 不能用于运行时类型检查

t instanceof T             // error
t instanceof List<T>       // error
t instanceof List<String>  // error
t instanceof List          // correct
로그인 후 복사

但是可以使用 clazz.isInstance(); 进行补偿;

3. 不能创建类型实例

T t = new T();  // error
로그인 후 복사

同样可以使用 clazz.newInstance(); 进行补偿;

4. 不能静态化

private static T t;                  // error
private T t;                         // correct
private static List<T> list;         // error
private static List<?> list;         // correct
private static List<String> list;    // correct

// e.g.
class Test<T> {
  private T t;
  public void set(T arg) { t = arg; }
  public T get() { return t; }
}
로그인 후 복사

因为静态变量在类中共享,而泛型类型是不确定的,所以泛型不能静态化;但是非静态的时候,编译期可以根据上下文推断出T是什么,例如:

Test l = new Test();
System.out.println(l.get());
l.set("123");
System.out.println(l.get());

// javap -v 反编译
12: invokevirtual #15         // Method JDK/Test14_genericity$Test.get:()Ljava/lang/Object;
15: invokevirtual #16         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: ldc       #17             // String 123
21: invokevirtual #18         // Method JDK/Test14_genericity$Test.set:(Ljava/lang/Object;)V
24: getstatic   #6            // Field java/lang/System.out:Ljava/io/PrintStream;

// ---------------------------
Test l = new Test();
System.out.println(l.get());
l.set("123");
System.out.println(l.get());

// javap -v 反编译
12: invokevirtual #15         // Method JDK/Test14_genericity$Test.get:()Ljava/lang/Object;
15: invokevirtual #16         // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
18: aload_1
19: bipush    123
21: invokestatic  #17         // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
로그인 후 복사

根据上面的代码,可以很清楚的看到,编译器对非静态类型的推导;

另外 List<?> 和 List<String> 之所以是正确的,仍然是因为编译器可以在编译期间就能确定类型转换的正确性;

5. 不能抛出或捕获泛型类的实例

catch (T t)                        // error
class Test<T> extends Throwable    // error
로그인 후 복사

因为在捕捉异常时候需要运行时类信息,并且判断异常的继承关系,所以不能抛出或捕获泛型类的实例;

6. 不允许作为参数进行重载

void test(List<Integer> list)
void test(List<String> list)
로그인 후 복사

因为在运行时期泛型信息被擦除,重载的两个方法签名就完全一样了;

7. 不能创建泛型数组

对于一点我觉得是最重要的,关于数组的介绍可以参考,Array 相关 ;

List<String>[] lists = new ArrayList<String>[10];             // error
List<String>[] lists1 = (List<String>[]) new ArrayList[10];   // correct
로그인 후 복사

之所以不能创建泛型数组的主要原因:

  • 数组是协变的,而泛型的不变的;

  • 数组的Class信息是在运行时动态创建的,而运行时不能获取泛型的类信息;

根据上面的讲解可以看出所谓的擦除补偿或者擦除后的修正,其大体思路都是用额外的方法告知运行时的类型信息,可以是记录到局部变量,也可以是指定参数的确切类型(Array.newInstance(Class<?> componentType, int length)런타임에는 일반 정보가 없으며 매개변수 유형에 대한 정보도 없다는 점을 기억해야 합니다. 얻을 수 있습니다
따라서 런타임 유형을 얻어야 하는 작업은 제네릭에서 지원되지 않습니다!

1. 유형 매개변수는 기본 유형으로 인스턴스화할 수 없습니다.

List<Object> list = new ArrayList<String>();    // error
로그인 후 복사

유형 삭제는 상위 경계인 Object를 삭제하기 때문에 Java의 8가지 기본 유형은 Number이므로 기본 유형은 유형 매개변수를 인스턴스화하기 위해 기본 유형을 사용할 수 없지만 기본 유형 래퍼 클래스를 사용해야 합니다.

2 런타임 유형 검사에는 사용할 수 없습니다. >
List<?> list = new ArrayList<String>();    // correct
list.add("34");                            // error
String s = list.get(0);                    // error
Object o = list.get(0);                    // correct

boolean add(E e);
로그인 후 복사
로그인 후 복사
🎜그러나 clazz.isInstance();를 사용하여 보상할 수 있습니다. 🎜🎜🎜

3. 유형 인스턴스를 생성할 수 없습니다.

<T extends Test>                             // 泛型声明
<T extends Test & interface1 & interface2>   // 声明泛型是可以确定多个上界
<? extends T>                                // 泛型使用时
로그인 후 복사
로그인 후 복사
🎜 clazz를 사용할 수도 있습니다. .newInstance(); 보상; 🎜🎜🎜

4. 정적일 수 없음

List<? extends List> list = new ArrayList<ArrayList>();  // correct
list.add(new ArrayList());                               // error
List l = list.get(0);                                    // correct
ArrayList l = list.get(0);                               // error
로그인 후 복사
로그인 후 복사
🎜정적 변수는 클래스에서 공유되고 일반 유형은 정의되지 않으므로 제네릭은 정적일 수 없습니다. 비정적이면 컴파일러는 컨텍스트를 기반으로 T가 무엇인지 추론할 수 있습니다. 예: 🎜
List<? super HashMap> list = new ArrayList<>();   // correct
LinkedHashMap m = new LinkedHashMap();            // correct
HashMap m1 = m;                                   // correct
Map m2 = m;                                       // correct
list.add(m);                                      // correct
list.add(m1);                                     // correct
list.add(m2);                                     // error

Map mm = list.get(0);                             // error
LinkedHashMap mm1 = list.get(0);                  // error
로그인 후 복사
로그인 후 복사
🎜위 코드에 따르면 컴파일러가 비정적 유형을 처리한다는 것을 분명히 알 수 있습니다. 파생 🎜🎜 또한 컴파일러가 컴파일 중에 유형 변환의 정확성을 확인할 수 있으므로 List<?> 및 List<String>이 ​​정확합니다. 🎜🎜🎜
class Test<T extends Test<T>> {}
public <T extends Comparable<T>> T max(List<T> list) {}
로그인 후 복사
로그인 후 복사
🎜Exception을 catch할 때 런타임 클래스 정보가 필요하고 예외의 상속 관계가 결정되므로 Generic 클래스의 인스턴스를 throw하거나 캡처할 수 없습니다.🎜🎜

6. 매개 변수가 오버로드되어 허용되지 않습니다.

public <T extends Comparable<? super>> T max(List<? extends T> list) {}
로그인 후 복사
로그인 후 복사
🎜때문에 일반 정보는 런타임 중에 삭제되며 오버로드된 두 메서드의 서명은 정확히 동일합니다.🎜🎜

7. 일반 배열을 만들 수 없습니다

🎜 제가 생각하는 가장 중요한 점은 배열 소개입니다. , 배열 관련을 참조하세요. 🎜rrreee🎜일반 배열을 만들 수 없는 주된 이유: 🎜
  • 🎜 배열은 공변적이지만 일반 배열은 불변입니다. >
  • 🎜배열의 클래스 정보는 런타임에 동적으로 생성되지만 제네릭은 런타임에 얻을 수 없습니다. 🎜
🎜위 설명에 따르면, 소위 삭제 보상 또는 삭제 후 수정을 볼 수 있습니다. 일반적인 아이디어는 추가 메소드를 사용하여 런타임 유형 정보를 알리는 것입니다. 이 정보는 로컬 변수에 기록될 수 있으며 지정된 매개변수의 정확한 유형일 수도 있습니다( Array.newInstance(Class<?> componentType, int length)) 🎜🎜🎜🎜3. 경계 확장🎜🎜🎜 보안에 기반 🎜rrreee🎜그래서 컬렉션 클래스를 사용할 때 각 컬렉션에 정확한 유형을 지정하도록 하는 것은 약간 불편합니다. 예를 들어 이 경우에는 컬렉션 저장소 A와 확장을 지정하고 싶습니다. super,?는 제네릭의 경계를 확장하고 관리하기 위해 도입되었습니다.

1. 无界通配符 <?>

通配符主要用于泛型的使用场景(泛型一般有“声明”和“使用”两种场景);
通常情况下 <?> 和原生类型大致相同,就像 List 和 List<?> 的表现大部分都是一样的;但是要注意他们其实是有本质去别的,<?> 代表了某一特定的类型,但是编译器不知道这种类型是什么;而原生的表示可以是任何 Object,其中并没有类型限制;

List<?> list = new ArrayList<String>();    // correct
list.add("34");                            // error
String s = list.get(0);                    // error
Object o = list.get(0);                    // correct

boolean add(E e);
로그인 후 복사
로그인 후 복사

上面的代码很明确的反应了这一点(<?> 代表了某一特定的类型,但是编译器不知道这种类型是什么),

  • 因为编译器不知道这种类型是什么,所以在添加元素的时候,当然也就不能确认添加的这个类型是否正确;当使用<?>的时候,代码中的 add(E e) 方法,此时的 E 会被替换为 <?>实际上编译器为了安全起见,会直接拒绝参数列表中涉及通配符的方法调用;就算这个方法没有向集合中添加元素,也会被直接拒绝;

  • List<?> 取出元素的时候,同样因为不知道这个特定的类型是什么,所以只能将取出的元素放在Object中;或者在取出后强转;

2. 上界 <extends>

extends,主要用于确定泛型的上界;

<T extends Test>                             // 泛型声明
<T extends Test & interface1 & interface2>   // 声明泛型是可以确定多个上界
<? extends T>                                // 泛型使用时
로그인 후 복사
로그인 후 복사

界定的范围如图所示:

Java 제네릭 관련 지식에 대한 자세한 설명(코드 포함)

应当注意的是当extends用于参数类型限定时:

List<? extends List> list = new ArrayList<ArrayList>();  // correct
list.add(new ArrayList());                               // error
List l = list.get(0);                                    // correct
ArrayList l = list.get(0);                               // error
로그인 후 복사
로그인 후 복사

上面的分析同无界通配符类似,只是 List l = list.get(0); 是正确的,是因为 <? extends List> 界定了放入的元素一定是 List 或者 list 的子类,所以取出的元素能放入 List 中,但是不能放入 ArrayList 中;

3. 下界 <super>

super,主要用于确定泛型的下界;如图所示:

Java 제네릭 관련 지식에 대한 자세한 설명(코드 포함)

List<? super HashMap> list = new ArrayList<>();   // correct
LinkedHashMap m = new LinkedHashMap();            // correct
HashMap m1 = m;                                   // correct
Map m2 = m;                                       // correct
list.add(m);                                      // correct
list.add(m1);                                     // correct
list.add(m2);                                     // error

Map mm = list.get(0);                             // error
LinkedHashMap mm1 = list.get(0);                  // error
로그인 후 복사
로그인 후 복사

根据图中的范围对照代码,就能很快发现Map在List的范围之外;而编辑器为了安全泛型下界集合取出的元素只能放在 Object里面;

4. PECS 原则

PECS原则是对上界和下界使用的归纳,即producer-extends, consumer-super;结合上面的两幅图,表示:

  • extends只能读,相当于生产者,向外产出;

  • super只能写,相当于消费者,只能接收消费;

  • 同时边界不能同时规定上界和下界,正如图所示,他们的范围其实是一样的,只是开口不一样;

5. 自限定类型

对于上面讲的泛型边界拓展,有一个很特别的用法,

class Test<T extends Test<T>> {}
public <T extends Comparable<T>> T max(List<T> list) {}
로그인 후 복사
로그인 후 복사

自限定类型可以通俗的解释,就是用自己限定自己,即自和自身相同的类进行某操作;如上面的 max 方法,就表示可以和自身进行比较的类型;

那么如果想要表达只要是同一祖先就能相互比较呢?

public <T extends Comparable<? super>> T max(List<? extends T> list) {}
로그인 후 복사
로그인 후 복사

>:表明只要是同一祖先就能相互比较, extends T>表明集合中装的都是同一祖先的元素;(出至《Effective Java》第 28 条)

总结

  • 对于泛型的时候首先要很清楚的知道,在运行时没有任何泛型的信息,全部都被擦除掉了;

  • 需要知道 Java 泛型做不到的事情;

  • 需要知道怎么拓展边界,让泛型更加灵活;

위 내용은 Java 제네릭 관련 지식에 대한 자세한 설명(코드 포함)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:cnblogs.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿