09Java 泛型 ✨博客主页 https://blog.csdn.net/m0_63815035?typeblog《博客内容》大数据、AI开发、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识博客专栏https://blog.csdn.net/m0_63815035/category_11954877.html欢迎点赞 收藏 ⭐留言 本文为学习笔记资料如有侵权请联系我删除疏漏之处还请指正大厦之成非一木之材也大海之阔非一流之归也✨目录前言泛型的历史与设计权衡一、为什么需要泛型1.1 没有泛型的时代1.2 泛型带来的好处二、泛型类与泛型接口2.1 定义泛型类2.2 使用泛型类2.3 泛型接口2.4 多类型参数三、泛型方法3.1 定义泛型方法3.2 泛型方法与普通方法的区别3.3 为什么类的泛型参数不能用于静态方法四、泛型通配符 ?4.1 为什么要通配符4.2 无界通配符 ?4.3 上界通配符 ? extends T4.4 下界通配符 ? super T4.5 PECS 原则Producer Extends, Consumer Super五、类型擦除Type Erasure5.1 什么是类型擦除5.2 验证类型擦除5.3 通过反射绕开泛型约束5.4 类型擦除与多态的冲突——桥方法六、泛型使用注意事项6.1 不能实例化泛型类型6.2 不能声明静态的泛型成员6.3 不能使用基本类型作为类型参数6.4 不能 instanceof 具体泛型类型6.5 不能创建泛型数组6.6 异常与泛型七、泛型与反射7.1 获取泛型父类的参数化类型文章摘要核心内容概述文章特色八、总结与最佳实践九、代码示例集锦9.1 泛型方法交换数组元素9.2 类型边界限制9.3 多个边界同时继承类和接口9.4 通配符捕获辅助方法十、扩展阅读前言泛型的历史与设计权衡泛型是 Java 5 引入的核心特性之一本质是“参数化类型”——将类型作为参数传递给类、接口或方法。本文章从基础到原理全面覆盖泛型语法、通配符、类型擦除、桥方法以及实际开发中的注意事项。一、为什么需要泛型1.1 没有泛型的时代在 Java 5 之前集合类如ArrayList内部统一存储Object类型。使用时需要手动强制转换很容易在运行时抛出ClassCastException。ListlistnewArrayList();list.add(hello);list.add(123);// 编译器允许Strings(String)list.get(0);// OKStringt(String)list.get(1);// 运行时 ClassCastException问题类型不安全任何类型都可加入破坏了集合的预期。强制转换繁琐每次取出都要向下转型。错误后置到运行时编译期无法发现。1.2 泛型带来的好处编译时类型检查非法类型无法加入。消除强制转换取出时自动是正确类型。实现通用算法同一套代码处理不同数据类型。ListStringlistnewArrayList();list.add(hello);// list.add(123); // 编译错误Stringslist.get(0);// 无需强转二、泛型类与泛型接口2.1 定义泛型类在类名后使用尖括号T声明一个或多个类型参数type parameter。publicclassBoxT{privateTcontent;publicvoidset(Tcontent){this.contentcontent;}publicTget(){returncontent;}}T是一个占位符使用时被具体类型替换。常见的类型参数名称TType、EElement、KKey、VValue、RReturn。2.2 使用泛型类BoxStringstringBoxnewBox();stringBox.set(Hello);StringsstringBox.get();// 自动为 StringBoxIntegerintBoxnewBox();intBox.set(100);IntegeriintBox.get();注意泛型参数必须是引用类型不能是基本类型。如果需要存储int使用Integer。2.3 泛型接口publicinterfaceGeneratorT{Tnext();}实现类可以选择保留或指定具体类型。// 保留泛型publicclassNumberGeneratorTimplementsGeneratorT{OverridepublicTnext(){...}}// 指定具体类型publicclassStringGeneratorimplementsGeneratorString{OverridepublicStringnext(){returnabc;}}2.4 多类型参数publicclassPairK,V{privateKkey;privateVvalue;// 构造器、getter/setter 省略}三、泛型方法3.1 定义泛型方法泛型方法独立于类是否泛型只需在返回值前声明类型参数。publicclassUtil{// 泛型方法publicstaticTTgetMiddle(T...arr){returnarr[arr.length/2];}}调用时编译器自动推断类型StringmiddleUtil.getMiddle(a,b,c);也可以显式指定类型很少需要Util.StringgetMiddle(a,b,c);3.2 泛型方法与普通方法的区别特性泛型方法普通方法类型参数作用域当前方法整个类静态方法可定义泛型静态方法静态方法不能使用类的泛型参数调用方式自动推断或显式指定普通调用3.3 为什么类的泛型参数不能用于静态方法静态方法在类加载时就存在而泛型类的具体类型在创建对象时才确定。因此静态方法不能依赖类的泛型参数除非自己定义为泛型方法。publicclassMyClassT{// 错误静态方法不能使用类的泛型参数 T// public static T get() { return null; }// 正确静态泛型方法使用自己声明的类型参数publicstaticEEget(Ee){returne;}}四、泛型通配符?4.1 为什么要通配符BoxString和BoxObject之间没有继承关系即使String继承自Object。下面的代码是错误的BoxStringstrBoxnewBox();BoxObjectobjBoxstrBox;// 编译错误如果需要编写一个方法参数可以接受任何类型的Box就需要通配符。4.2 无界通配符?publicvoidprintBox(Box?box){System.out.println(box.get());}Box?表示“某种特定类型的Box”但不知道具体是什么。不能向其中添加元素除了null因为类型未知。可以读取但只能赋值给Object。Box?boxnewBoxString();Objectobjbox.get();// 只能读到 Object// box.set(hello); // 编译错误4.3 上界通配符? extends T表示类型参数必须是T或T的子类。publicdoublesumOfList(List?extendsNumberlist){doublesum0;for(Numbern:list){sumn.doubleValue();}returnsum;}可以传入ListInteger、ListDouble等。读取时得到Number类型上界。不能添加除null因为具体子类型未知。4.4 下界通配符? super T表示类型参数必须是T或T的父类。publicvoidaddNumbers(List?superIntegerlist){list.add(1);list.add(2);}可以传入ListInteger、ListNumber、ListObject。可以添加Integer或它的子类型因为? super Integer保证列表能接受Integer。读取时只能得到Object因为具体父类型未知。4.5 PECS 原则Producer Extends, Consumer Super? extends T如果集合是生产者提供数据给消费者使用extends。只能读不能写。? super T如果集合是消费者接收数据使用super。可以写但不能精确读。既要读又要写就不要用通配符直接用具体类型参数。// 生产者copy 方法从 src 读取数据publicstaticTvoidcopy(List?extendsTsrc,List?superTdest){for(Titem:src){dest.add(item);}}五、类型擦除Type Erasure5.1 什么是类型擦除Java 泛型是编译时特性在生成的字节码中泛型类型信息会被移除擦除替换为原始类型raw type。这是为了兼容旧版本 JVM 和已有代码。无限制类型参数T→ 替换为Object。有限制类型参数T extends Number→ 替换为Number第一个边界。// 源码classBoxT{privateTvalue;}// 擦除后原始类型classBox{privateObjectvalue;}5.2 验证类型擦除ListStringlist1newArrayList();ListIntegerlist2newArrayList();System.out.println(list1.getClass()list2.getClass());// true两者运行时都是ArrayList.class没有任何类型信息。5.3 通过反射绕开泛型约束因为擦除后字段为Object反射可以绕过编译期检查ListIntegerlistnewArrayList();list.getClass().getMethod(add,Object.class).invoke(list,abc);System.out.println(list.get(0));// abc5.4 类型擦除与多态的冲突——桥方法classParentT{publicvoidset(Tvalue){}publicTget(){returnnull;}}classChildextendsParentString{Overridepublicvoidset(Stringvalue){}OverridepublicStringget(){returnchild;}}擦除后Parent变成classParent{publicvoidset(Objectvalue){}publicObjectget(){returnnull;}}子类Child中的set(String)和get():String不再重写父类方法参数类型和返回类型不同。编译器为了解决这个问题会在Child中自动生成桥方法// 桥方法publicvoidset(Objectvalue){set((String)value);// 调用实际重写的方法}publicObjectget(){returnget();// 调用 String 返回的 get然后自动装箱实际是协变返回}可以通过javap -c Child查看字节码验证。协变返回类型子类重写方法可返回父类方法返回类型的子类型这也是通过桥方法实现的。六、泛型使用注意事项6.1 不能实例化泛型类型TobjnewT();// 错误T[]arraynewT[10];// 错误解决方案通过ClassT反射创建。publicTTcreate(ClassTclazz)throwsException{returnclazz.newInstance();}6.2 不能声明静态的泛型成员publicclassHolderT{privatestaticTvalue;// 编译错误}因为静态成员属于类而泛型类型在实例化时才确定。6.3 不能使用基本类型作为类型参数必须用包装类ListInteger而非Listint。自动装箱/拆箱可以减轻不便。6.4 不能 instanceof 具体泛型类型if(listinstanceofListString)// 错误只能检查原始类型if (list instanceof List?)6.5 不能创建泛型数组T[]arrnewT[5];// 错误ListString[]arrnewListString[5];// 错误可以创建通配符数组但需要类型安全转换List?[]arrnewList?[5];arr[0]newArrayListString();6.6 异常与泛型不能抛出或捕获泛型类型的异常。不能定义catch (T e)。可以在异常声明中使用类型变量throws T不允许但可以throws Exception。七、泛型与反射7.1 获取泛型父类的参数化类型通过反射可以读取泛型信息类定义层面而非运行时的对象。publicclassGenericDaoT{}classUserDaoextendsGenericDaoUser{}// 获取 UserDao 父类的实际类型参数ParameterizedTypetype(ParameterizedType)UserDao.class.getGenericSuperclass();TypeactualTypetype.getActualTypeArguments()[0];System.out.println(actualType);// class User这在许多框架如 Spring、MyBatis底层用于实现通用 DAO文章摘要本文是一篇全面深入的Java泛型教程从基础概念到高级原理系统讲解了泛型的各个方面核心内容概述泛型基础介绍了泛型作为Java 5引入的参数化类型特性解决了类型安全问题消除了强制类型转换的繁琐。泛型类与接口详细讲解了如何定义和使用泛型类、泛型接口包括多类型参数的实现方式。泛型方法阐述了泛型方法的定义、与普通方法的区别以及为什么类的泛型参数不能用于静态方法。泛型通配符深入解析了?、? extends T、? super T三种通配符的使用场景并介绍了PECSProducer Extends, Consumer Super原则。类型擦除机制揭示了Java泛型的底层实现原理——类型擦除包括桥方法的产生原因和反射绕过泛型约束的方法。使用注意事项总结了泛型使用中的各种限制如不能实例化泛型类型、不能声明静态泛型成员、不能使用基本类型作为类型参数等。泛型与反射讲解了如何通过反射获取泛型父类的参数化类型信息。最佳实践提供了泛型使用的实用建议包括优先使用泛型、合理使用通配符、清晰的类型参数命名等。文章特色理论与实践结合包含大量代码示例从历史背景到现代应用全面覆盖深入解析底层原理类型擦除、桥方法提供实际开发中的注意事项和最佳实践本文适合Java初学者系统学习泛型也适合有经验的开发者深入理解泛型原理和高级用法。。八、总结与最佳实践优先使用泛型提供编译时类型安全消除强制转换。合理使用通配符理解 PECSProducerextendsConsumersuper。类型参数命名清晰单字母T, E, K, V是惯例也可使用T_ID等。避免使用原始类型List不加泛型会失去类型安全。方法返回类型尽量使用泛型方法而非通配符保持 API 清晰。理解擦除机制才能正确应对泛型与继承、多态、反射的关系。九、代码示例集锦9.1 泛型方法交换数组元素publicstaticTvoidswap(T[]arr,inti,intj){Ttemparr[i];arr[i]arr[j];arr[j]temp;}9.2 类型边界限制publicstaticTextendsComparableTTmax(Ta,Tb){returna.compareTo(b)0?a:b;}9.3 多个边界同时继承类和接口publicclassMyClassTextendsNumberComparableT{}类必须写在前面接口在后。最多一个类多个接口。9.4 通配符捕获辅助方法publicvoidswap(Box?box){swapHelper(box);}privateTvoidswapHelper(BoxTbox){Ttbox.get();box.set(t);// 现在可以安全地设置}十、扩展阅读《Java 核心技术 卷I》泛型章节《Effective Java》第5条优先使用泛型JDK 源码ArrayList,Collections,Comparable等泛型实现泛型是现代 Java 编程的基石之一熟练掌握它不仅是编写类型安全代码的前提也是深入理解集合框架、Stream API 等高级特性的关键。多写多练结合字节码查看工具javap才能真正理解类型擦除背后的设计权衡。今天这篇文章就到这里了大厦之成非一木之材也大海之阔非一流之归也。感谢大家观看本文