이 기사는 Java 제네릭(코드 포함)에 대한 자세한 지식을 제공합니다. 필요한 친구가 참고할 수 있기를 바랍니다. 모든 사람이 제네릭의 사용에 매우 익숙하다고 생각하지만 유형 삭제, 경계 확장 등에 대한 세부 사항은 명확하지 않을 수 있으므로 이 기사에서는 제네릭에 대한 이해와 함께 이에 대한 설명에 중점을 둘 것입니다. 실제로 볼 수 있는 것은 언어 기능의 생성 논리는 우리의 일상적인 개발에도 매우 도움이 됩니다. 1. 제네릭이 나타나는 이유 우선 제네릭은 Java의 언어 기능이 아닙니다. JDK1.5까지는 지원되지 않았습니다(구체적인 차이점은 나중에 논의됩니다). 그러면 제네릭이 나타나기 전에는 어떤 작업이 수행되었습니까? List list = new ArrayList(); list.add("123"); String s = (String) list.get(0);위 코드에서 볼 수 있듯이 컬렉션에 넣은 내용을 기억한 다음 꺼낼 때 강제로 변환해야 합니다. 이렇게 하면 이 유형 변환 오류가 런타임으로 연기됩니다. 즉, 아직 안전하지 않습니다. 사용 시나리오: 제네릭 클래스, 제네릭 인터페이스, 제네릭 메서드 public class Test public interface Test public void test(T t) 2. 제네릭은 어떤 종류의 문제를 가져올까요? 위에서 언급했듯이 제네릭 유형은 다음과 같은 기능이 아닙니다. Java는 처음부터 있으므로 나중에 제네릭을 추가하려면 이전 버전과 호환되어야 합니다. Sun이 제시한 절충 솔루션은 제네릭을 의미하는 type erasure입니다. 정보는 컴파일 중에만 존재하며 모든 제네릭 정보는 런타임 중에 지워지고 아무것도 남지 않습니다. List list1 = new ArrayList<>(); List list2 = new ArrayList<>(); System.out.println(list1.getClass()); System.out.println(list2.getClass() == list1.getClass());// 인쇄: class java.util.ArrayListtrue List 및 List는 실제로 런타임에 동일하며 둘 다 class java.util.ArrayList입니다. 따라서 제네릭을 사용할 때 List 和 List 在运行时其实都是一样的,都是class java.util.ArrayList;所以在使用泛型的时候需要牢记,在运行时期没有泛型信息,也无法获取任何有关参数类型的信息;所以凡是需要获取运行时类型的操作,泛型都不支持! 1. 不能用基本类型实例化类型参数new ArrayList(); // error new ArrayList(); // correct因为类型擦除,会擦除到他的上界也就是 Object;而 Java 的8个基本类型的直接父类是 Number,所以基本类型不不能用基本类型实例化类型参数,而必须使用基本类型的包装类; 2. 不能用于运行时类型检查t instanceof T // error t instanceof List // error t instanceof List // 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 list; // error private static List> list; // correct private static List list; // correct // e.g. class Test { 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 之所以是正确的,仍然是因为编译器可以在编译期间就能确定类型转换的正确性; 5. 不能抛出或捕获泛型类的实例catch (T t) // error class Test extends Throwable // error因为在捕捉异常时候需要运行时类信息,并且判断异常的继承关系,所以不能抛出或捕获泛型类的实例; 6. 不允许作为参数进行重载void test(List list) void test(List list)因为在运行时期泛型信息被擦除,重载的两个方法签名就完全一样了; 7. 不能创建泛型数组 对于一点我觉得是最重要的,关于数组的介绍可以参考,Array 相关 ;List[] lists = new ArrayList[10]; // error List[] lists1 = (List[]) new ArrayList[10]; // correct之所以不能创建泛型数组的主要原因: 数组是协变的,而泛型的不变的; 数组的Class信息是在运行时动态创建的,而运行时不能获取泛型的类信息; 根据上面的讲解可以看出所谓的擦除补偿或者擦除后的修正,其大体思路都是用额外的方法告知运行时的类型信息,可以是记录到局部变量,也可以是指定参数的确切类型(Array.newInstance(Class> componentType, int length)런타임에는 일반 정보가 없으며 매개변수 유형에 대한 정보도 없다는 점을 기억해야 합니다. 얻을 수 있습니다 따라서 런타임 유형을 얻어야 하는 작업은 제네릭에서 지원되지 않습니다! 1. 유형 매개변수는 기본 유형으로 인스턴스화할 수 없습니다.List list = new ArrayList(); // error 유형 삭제는 상위 경계인 Object를 삭제하기 때문에 Java의 8가지 기본 유형은 Number이므로 기본 유형은 유형 매개변수를 인스턴스화하기 위해 기본 유형을 사용할 수 없지만 기본 유형 래퍼 클래스를 사용해야 합니다. 2 런타임 유형 검사에는 사용할 수 없습니다. >List> list = new ArrayList(); // correct list.add("34"); // error String s = list.get(0); // error Object o = list.get(0); // correct boolean add(E e);🎜그러나 clazz.isInstance();를 사용하여 보상할 수 있습니다. 🎜🎜🎜3. 유형 인스턴스를 생성할 수 없습니다. // 泛型声明 // 声明泛型是可以确定多个上界 extends T> // 泛型使用时🎜 clazz를 사용할 수도 있습니다. .newInstance(); 보상; 🎜🎜🎜4. 정적일 수 없음List extends List> list = new 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이 정확합니다. 🎜🎜🎜class Test> {} public > T max(List list) {} 🎜Exception을 catch할 때 런타임 클래스 정보가 필요하고 예외의 상속 관계가 결정되므로 Generic 클래스의 인스턴스를 throw하거나 캡처할 수 없습니다.🎜🎜6. 매개 변수가 오버로드되어 허용되지 않습니다.public > 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(); // 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用于参数类型限定时:List extends List> list = new 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,主要用于确定泛型的下界;如图所示: 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 super HashMap>的范围之外;而编辑器为了安全泛型下界集合取出的元素只能放在 Object里面; 4. PECS 原则 PECS原则是对上界和下界使用的归纳,即producer-extends, consumer-super;结合上面的两幅图,表示: extends,只能读,相当于生产者,向外产出; super,只能写,相当于消费者,只能接收消费; 同时边界不能同时规定上界和下界,正如图所示,他们的范围其实是一样的,只是开口不一样; 5. 自限定类型 对于上面讲的泛型边界拓展,有一个很特别的用法,class Test> {} public > T max(List list) {}自限定类型可以通俗的解释,就是用自己限定自己,即自和自身相同的类进行某操作;如上面的 max 方法,就表示可以和自身进行比较的类型; 那么如果想要表达只要是同一祖先就能相互比较呢?public > T max(List extends T> list) {}>:表明只要是同一祖先就能相互比较, extends T>表明集合中装的都是同一祖先的元素;(出至《Effective Java》第 28 条) 总结 对于泛型的时候首先要很清楚的知道,在运行时没有任何泛型的信息,全部都被擦除掉了; 需要知道 Java 泛型做不到的事情; 需要知道怎么拓展边界,让泛型更加灵活;