java中的泛型

java中的泛型 文章目录一、为什么需要泛型二、泛型的核心概念2.1 定义2.2 核心优势三、泛型的核心用法3.1 泛型类/接口3.2 泛型方法3.3 泛型通配符四、泛型的核心规则一、为什么需要泛型在泛型出现之前JDK5之前Java集合比如HashMap、ArrayList只能存储Object类型的对象会带来两个致命问题类型不安全可以往集合里存任意类型的对象运行时可能抛出类型转换异常。需要手动强制类型转换取数据时必须手动转换成目标类型代码繁琐且容易出错反面例子无泛型的HashMappublicstaticvoidmain(String[]args){//无泛型的HashMap默认存object类型MapmapnewHashMap();//1.可以存任意类型字符串key 整数value 、字符串key 字符串Valuemap.put(年龄,20);map.put(姓名,张三);//甚至能存不相关的类型比如key是整数Value是数组map.put(100,newint[]{1,2,3});//2.取值时必须手动强转且容易出错//正确转换年龄是整数intage(int)map.get(年龄);System.out.println(年龄age);//错误转换姓名是字符串强转成整数会抛出ClassCastException//int name (int) map.get(姓名); //运行时报错}问题总结编译时无法检查类型错误比如往存“年龄”的Map里存字符串只有运行时才会报错每次取值都要强制类型转换代码冗余且容易出错。而泛型的核心作用就是在编译阶段限制集合/类的类型保证类型安全同时避免手动类型转换。二、泛型的核心概念2.1 定义泛型Generic是JDK5引入的特性允许在定义类、接口、方法时使用“类型参数”比如T来表示未知类型使用时再指定具体类型比如 StringInteger。可以把泛型理解成“类型的占位符”定义时用T KV等符号占位置T typeK keyV value只是约定俗称的命名用其他的字母也可以使用时用具体的类型比如String、Integer替换占位符。2.2 核心优势优势说明类型安全编译时检查类型避免运行时ClassCastException消除强制转换取值时自动匹配类型无需手动强转代码复用一套泛型代码可以适配多种类型不用为每种类型写重复代码三、泛型的核心用法泛型的使用场景主要分为3类泛型类/接口、泛型方法、通配符其中前两类是新手必须掌握的核心3.1 泛型类/接口泛型类的定义格式class 类名类型参数 { … }泛型接口的定义格式 interface 接口名类型参数 { … }示例1自定义泛型类模拟HashMap的核心逻辑写一个简单的泛型键值对类classMyGenericMapK,V{//成员变量类型为K和V泛型类型privateKkey;privateVvalue;//构造方法参数类型为K和VpublicMyGenericMap(Kkey,Vvalue){this.keykey;this.valuevalue;}//普通方法返回值类型为K/VpublicKgetKey(){returnkey;}publicVgetValue(){returnvalue;}//重写toString方便打印OverridepublicStringtoString(){returnkey value;}}publicclassTest{publicstaticvoidmain(String[]args){//使用泛型类指定K stringV Integer具体类型MyGenericMapString,Integermap1newMyGenericMap(年龄,20);//取值时无需强转直接是String/Integer类型Stringkey1map1.getKey();Integervalue1map1.getValue();System.out.println(map1);//输出年龄20//换一种类型K IntegerV StringMyGenericMapInteger,Stringmap2newMyGenericMap(1001,张三);Integerkey2map2.getKey();Stringvalue2map2.getValue();System.out.println(map2);//编译类型检查如果存错类型编译直接报错类型安全MapString,Integermap3newHashMap();//map3.put(姓名,20.5); //编译报错Integer类型不能赋值给Double}}关键说明定义时K,V是类型参数代表未知类型使用时String,Integer是类型实参指定具体类型编译时会检查类型比如往MapString,Integer里存Double类型直接编译报错避免运行时异常。示例2泛型集合以HashMapArrayList为例演示泛型在集合中的使用publicstaticvoidmain(String[]args){//1.泛型ArrayList限制只能存String类型ListStringstrlistnewArrayList();strlist.add(张三);strlist.add(李四);//strlist.add(123); //编译报错不能存整数只能存String//取值无需强转直接是String类型Stringnamestrlist.get(0);System.out.println(List第一个元素name);//2.泛型HashMap限制KeyStringvalueIntegerMapString,IntegerscoreMapnewHashMap();scoreMap.put(语文,90);scoreMap.put(数学,95);//scoreMap.put(英语,95); //编译报错value必须是Integer//取值无需强转直接是Integer类型intmathScorescoreMap.get(数学);System.out.println(数学成绩mathScore);}核心总结集合使用泛型后相当于给集合加了类型过滤器只能存指定类型的元素编译时就能发现类型错误取值也不需要进行强转。3.2 泛型方法泛型方法是指方法本身带有类型参数即使所在的类不是泛型类也可以定义泛型方法定义格式[修饰符]类型参数返回值类型 方法名(参数列表) {…}publicstaticTListTarrayToList(T[]array){ListTlistnewArrayList();for(Telement:array){list.add(element);}returnlist;}//自定义Person类staticclassPerson{privateStringname;privateintage;publicPerson(Stringname,intage){this.namename;this.ageage;}OverridepublicStringtoString(){returnPerson{name name ,age age};}}publicstaticvoidmain(String[]args){//测试1整数数组转ListIntegerInteger[]intAaay{1,2,3};ListIntegerintlistarrayToList(intAaay);System.out.println(整数Listintlist);//输出【123】//测试2字符串数组转ListStringString[]strArray{a,b,c};ListStringstrlistarrayToList(strArray);System.out.println(字符串Liststrlist);//输出【a,b,c】//测试3自定义类型数组转ListPerson[]people{newPerson(张三,20),newPerson(李四,25)};ListPersonpersonListarrayToList(people);System.out.println(personListpersonList);}关键说明泛型方法的T必须写在static之后返回值之前这是语法规则同一个泛型方法可以适配多种类型整数、字符串、自定义Person实现代码复用调用时无需手动指定Tjava会自动根据传入的参数类型推断比如传入Integer数组T自动就是Integer详细解析ListT 里也有 T但疑惑为什么还要在方法开头单独声明 这个问题问到了泛型语法的关键 ——“使用” 和 “声明” 是完全不同的两件事List 是「使用 T」而方法前的 是「声明 T」没有声明就无法使用。T先声明 T 是泛型类型告诉编译器 T 是我定义的通用类型符号也就是说修饰符public static后面的 T本质是告诉编译器我要在这个方法里定义一个通用的类型占位符名字叫 T。ListT/T[] array再使用 T 定义返回值和参数类型。ListT 里的 T 本质是 “引用” 了前面声明的 T没有声明引用就会报错。再用错误案例强化理解我们把代码拆成两种错误写法你就能直观看到区别// 编译报错找不到符号 TpublicstaticListTarrayToList(T[]array){ListTlistnewArrayList();for(Telement:array){list.add(element);}returnlist;}编译器看到 ListT 和 T[] array 里的 T 时会问“T 是什么是一个类名吗我没找到这个类啊”—— 因为你只 “用了 T”但没 “告诉编译器 T 是泛型符号”。3.3 泛型通配符泛型通配符用于 “灵活匹配泛型类型”解决 “泛型不协变” 的问题比如 ListString 不是 ListObject 的子类。新手先掌握核心用法//1.无界通配符打印任意类型的ListpublicstaticvoidprintList(List?list){for(Objectobj:list){System.out.println(obj );}System.out.println();}//2.上界通配符计算数字List的总和只能是Number及其子类publicstaticdoublesumlist(List?extendsNumberlist){doublesum0;for(Numbernum:list){sumnum.doubleValue();}returnsum;}publicstaticvoidmain(String[]args){//测试无界通配符ListStringstrlistnewArrayList();strlist.add(a);strlist.add(b);printList(strlist);//输出a bListIntegerintListnewArrayList();intList.add(1);intList.add(2);printList(intList);//输出1 2//测试上界通配符ListIntegernumlistnewArrayList();numlist.add(10);numlist.add(20);System.out.println(总和sumlist(numlist));//输出30.0ListStringstringListnewArrayList();stringList.add(c);stringList.add(d);System.out.println(总和: sumlist(stringList));//编译报错String不是number的子类为了保证类型安全编译报错}四、泛型的核心规则泛型只在编译阶段有效类型擦除Java 泛型是 “编译时语法糖”运行时会把泛型类型擦除成 Object或上界类型。比如编译时 List 和 List 是不同类型运行时两者都是 List 类型无法通过反射区分。不能用基本类型作为泛型参数泛型参数只能是引用类型比如 Integer、String不能是基本类型int、char、double。❌ 错误List list new ArrayList();✅ 正确List list new ArrayList();泛型类的静态成员不能使用泛型参数静态成员属于类而泛型参数属于对象创建对象时才指定所以静态方法 / 变量不能用类的泛型参数classMyClassT{// 错误静态变量不能用T// private static T value;// 正确如果静态方法需要泛型定义成泛型方法publicstaticEEgetValue(Ee){returne;}}