Java 大厂面试高频题:List<String> 到底是什么意思?一篇彻底搞懂 Java 泛型
在学习 Java 集合时我们经常会看到这样的代码ListStringnamesnewArrayList();ListIntegeragesnewArrayList();很多初学者会产生疑问什么是泛型List是泛型还是String是泛型既然写了ListString类型不是已经确定了吗不写Integer可不可以本文用通俗的方式把 Java 泛型讲清楚。一、什么是泛型泛型可以理解为在定义类、接口或方法时先使用一个“类型占位符”等真正使用时再指定具体类型。例如Java 中的List接口可以简化理解为interfaceListE{voidadd(Eelement);Eget(intindex);}这里的E不是某个具体的数据类型而是一个类型占位符。它表示这个列表要存什么类型的数据定义 List 时暂时不确定 等创建和使用列表时再决定。因此同一个List可以用来保存不同类型的数据ListStringnamesnewArrayList();ListIntegeragesnewArrayList();ListDoublescoresnewArrayList();这就是泛型的核心同一套代码可以适用于多种数据类型。二、List、String 和 List 分别是什么以这段代码为例ListStringnamesnewArrayList();其中List泛型接口 String具体类型参数 ListString指定了 String 类型后的泛型类型List在设计时并没有规定只能保存字符串。当我们写ListString相当于告诉 Java把 ListE 中的 E 替换成 String。于是原来的方法voidadd(Eelement);Eget(intindex);就可以理解为voidadd(Stringelement);Stringget(intindex);因此这个列表只能保存String。ListStringnamesnewArrayList();names.add(张三);names.add(李四);下面的代码会编译报错names.add(18);因为18是整数不是字符串。三、既然 List 已经确定类型为什么还说泛型“先不确定类型”这里需要区分两个阶段定义阶段 使用阶段1. 定义阶段在 Java 设计List接口时类型还没有确定interfaceListE{voidadd(Eelement);}E可能是String也可能是Integer、Person或其他类型。这就是“先不确定具体类型”。2. 使用阶段当程序员真正使用时再指定类型ListStringnames;此时E被确定为String。再例如ListIntegerages;此时E被确定为Integer。所以泛型并不是使用时永远不确定类型而是定义代码时先不把类型写死使用代码时再指定具体类型。可以把它理解成一个通用盒子模板设计盒子时BoxT 使用盒子时BoxString、BoxInteger四、为什么需要泛型泛型最主要有三个作用。1. 提前检查类型错误没有泛型时可以向集合中放入任何对象ListlistnewArrayList();list.add(张三);list.add(18);list.add(true);这个列表中同时存在字符串、整数和布尔值很容易出现问题。使用泛型后ListStringlistnewArrayList();list.add(张三);list.add(18);// 编译报错Java 在编译阶段就能发现错误不必等到程序运行时才出问题。2. 减少强制类型转换不使用泛型时集合取出的数据通常是ObjectListlistnewArrayList();list.add(张三);Objectvaluelist.get(0);Stringname(String)value;需要手动强制转换。使用泛型后ListStringlistnewArrayList();list.add(张三);Stringnamelist.get(0);Java 已经知道列表中保存的是String所以不需要再强制类型转换。3. 提高代码复用性假设我们要定义一个保存数据的盒子。不使用泛型可能要分别写classStringBox{privateStringvalue;}classIntegerBox{privateIntegervalue;}使用泛型后只需要定义一次classBoxT{privateTvalue;publicvoidsetValue(Tvalue){this.valuevalue;}publicTgetValue(){returnvalue;}}使用时再指定类型BoxStringstringBoxnewBox();stringBox.setValue(Java);BoxIntegerintegerBoxnewBox();integerBox.setValue(100);同一个BoxT就能适用于多种类型。五、不写泛型可以吗可以写ListagesnewArrayList();这叫使用原始类型也叫 Raw Type。它允许存入不同类型的数据ages.add(18);ages.add(二十岁);ages.add(true);但是这种写法会失去泛型带来的类型检查。例如Integerage(Integer)ages.get(1);由于索引1保存的是字符串运行时会抛出ClassCastException推荐写成ListIntegeragesnewArrayList();这样下面的代码会直接在编译阶段报错ages.add(二十岁);因此虽然 Java 允许省略泛型但实际开发中不推荐这样做。六、为什么 List 不能写成 List下面的代码是错误的ListintagesnewArrayList();因为 Java 泛型只能使用引用类型不能直接使用基本数据类型。int是基本类型Integer是对应的包装类所以要写ListIntegeragesnewArrayList();添加整数时ages.add(18);Java 会自动把int转换成Integer这个过程叫自动装箱。取出数据时intageages.get(0);Java 会自动把Integer转换成int这个过程叫自动拆箱。七、常见的泛型字母是什么意思泛型参数可以使用任意合法名称但通常使用单个大写字母TType表示类型 EElement表示元素 KKey表示键 VValue表示值 RResult表示结果例如classBoxT{privateTvalue;}集合接口常用EinterfaceListE{}Map 常用K和VMapString,IntegerscoresnewHashMap();这里String 是键的类型 K Integer 是值的类型 V表示学生姓名对应分数。八、什么是泛型类、泛型接口和泛型方法1. 泛型类在类名后声明类型参数classBoxT{privateTvalue;publicvoidsetValue(Tvalue){this.valuevalue;}publicTgetValue(){returnvalue;}}2. 泛型接口在接口名后声明类型参数interfaceContainerT{voidadd(Tvalue);Tget();}ListE就是一个泛型接口。3. 泛型方法方法自己声明类型参数publicstaticTvoidprint(Tvalue){System.out.println(value);}调用时可以传入不同类型print(Java);print(100);print(true);这里的T表示该方法是泛型方法。九、泛型和 Object 有什么区别使用Object也可以接收任何类型classBox{privateObjectvalue;publicvoidsetValue(Objectvalue){this.valuevalue;}publicObjectgetValue(){returnvalue;}}但是取出时需要强转Stringvalue(String)box.getValue();如果转换类型错误就会在运行时抛出异常。使用泛型BoxStringboxnewBox();box.setValue(Java);Stringvaluebox.getValue();不需要强转而且错误类型无法放进去。所以泛型相比Object的优势是类型更明确 编译期可以检查 减少强制转换 代码更加安全十、面试怎么回答面试时可以这样回答泛型是 Java 提供的一种类型参数化机制。在定义类、接口或方法时可以先使用类型参数作为占位符等真正使用时再指定具体类型。例如 List 是泛型接口List 表示将 E 指定为 String。泛型可以提高代码复用性在编译阶段进行类型检查并减少强制类型转换。更精炼地说泛型就是定义代码时先不写死数据类型使用时再指定类型从而实现代码复用和类型安全。十一、总结泛型的核心不是“没有类型”而是“把类型变成参数”。例如ListStringnamesnewArrayList();其中List 是泛型接口 E 是定义时的类型占位符 String 是使用时传入的具体类型参数 ListString 是指定类型后的泛型类型使用泛型有三个主要好处编译阶段检查类型错误 减少强制类型转换 让同一套代码支持不同类型最后记住一句话泛型就是在定义类、接口或方法时先用类型占位符使用时再指定具体类型。