泛型

1、泛型

把明确类型的工作推迟到创建对象或调用方法的时候,可以把运行时的问题提前到编译时期。提供了编译期的类型安全,确保把正确类型的对象放入集合中,避免了在运行时出现 ClassCastException 异常

  • 可以明确集合中的数据类型,提高安全性、可读性,可以使用增强 for 循环遍历
  • 代码更加简洁,不需要强制转换
  • 程序更加健壮,只要编译时期没有警告,那么运行时期就不会出现 ClassCastException 异常

1.1 通配符

本质上这些都是通配符,没有区别,只有字面意思,只是一个约定的规范,Test<T>Test<A> 在执行效果上并没有什么区别

  • T:代表一个具体的 Java 类型
  • E:代表 Element 的意思,或者 Exception 异常的意思
  • K:代表 Key 的意思,通常与 V 一起配合使用
  • V:代表 Value 的意思,通常与 K 一起配合使用
  • S:代表 Subtype 的意思
  • ?:无限定的通配符,代表不确定的 Java 类型
    • 主要针对泛型类的限制, 无法像 T 类型参数一样单独存在
  • ? extends E设定通配符上限,接收 E 类型或者 E 的子类型
  • ? super E设定通配符下限,接收 E 类型或者 E 的父类型

1.2 泛型的使用

1.2.1 泛型类

用于类的定义中,用户使用该类的时候,才把类型明确下来。通过泛型可以完成对一组类的操作对外开放相同的接口,如 List、Set、Map 等容器类。泛型类的子类,如果有明确的类型参数,需将用泛型的地方全部替换成传入的实参类型,否则需将泛型的声明也一起加到类中。类上声明的泛型只对非静态成员有效,如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法

public class Test<T> { private T t; public T getT() { return t; } public void setT(T t) { this.t = t; } public static void main(String[] args) { Test<Integer> test = new Test<>(); test.setT(1); System.out.println(test.getT()); } }
  • 继承泛型类的子类,未传入泛型实参时,在声明类的时候,需将泛型的声明也一起加到类中。如果不声明泛型,编译器会报错
class One<T> extends Test<T> {} class Two<String> extends Test<T> {} // 报错 class Three extends Test<T> {} // 报错 class Four<T> extends Test<String> {} class Five<String> extends Test<String> {} class Six extends Test<String> {} class Seven extends Test {}

1.2.2 泛型接口

与泛型类相似

public interface Test<T> { T doAnything(); } public class One<T> implements Test<T> { T doAnything(); } // 当实现泛型接口的类,传入泛型实参时,需将用泛型的地方全部替换成传入的实参类型 public class One<String> implements Test<String> { String doAnything(); }

1.2.3 泛型方法

在调用方法的时候指明泛型的具体类型,类型参数写在返回值前面,声明的类型参数,还可以当作返回值的类型

public class Test { // 只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法 public <T> T method(T t){ return t; } // 静态方法使用泛型,必须将静态方法也定义成泛型方法 public static <T> void staticMethod(T t) { System.out.println("static方法"); } public static void main(String[] args) { Test test = new Test(); System.out.println(test.method("fuck")); System.out.println(test.method(12)); staticMethod("fuck"); staticMethod(12); } }

1.3 泛型擦除

泛型是通过类型擦除来实现的,泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除

List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass() == l2.getClass()); // true

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型(raw type)。原始类型就是擦除了泛型信息,最后在字节码中的类型变量的真正类型。擦除(erased)类型变量,并替换为限定类型(无限定的变量用 Object)

1.3.1 局限性

泛型擦除,是泛型能够与之前的 Java 版本代码兼容共存的原因。但也因为类型擦除,它会抹掉很多继承相关的特性,这是它带来的局限性

List<Integer> list = new ArrayList<>(); list.add(1); // 因为泛型的限制,以下代码会报错,基于对类型擦除的了解,利用反射,就可以绕过这个限制 // list.add("String");list.add(27.12);会报错 Method method = list.getClass().getDeclaredMethod("add", Object.class); method.invoke(list, "String"); method.invoke(list, 27.12); for (Object o : list) { System.out.println(o); // 成功打印 }

1.4 可以把 List<String> 传递给一个接受 List<Object> 参数的方法吗

会导致编译错误,因为 List<Object> 可以存储任何类型的对象包括 String、Integer 等,而 List<String> 却只能用来存储 String

public class Test { public static void main(String[] args) { Test test = new Test(); List<String> list = new ArrayList<>(); // 编译错误 test.demo(list); } public void demo(List<Object> list) { System.out.println("success"); } }
  • 有两种解决办法
    • 将 Test 设为泛型类
    • 将 demo 方法中的泛型 <Object> 改为 <?>

1.5 Class<T>Class<?> 的区别

Class<T> 在实例化的时候,T 要替换成具体类,Class<?> 是个通配泛型,? 可以代表任何类型,主要用于声明时的限制情况

1.6 PECS 原则

  • 如果要从集合中读取类型 T 的数据,并且不能写入,可以使用 ? extends 通配符(Producer Extends)
  • 如果要从集合中写入类型 T 的数据,并且不需要读取,可以使用 ? super 通配符(Consumer Super)
  • 如果既要存又要取,那么就不要使用任何通配符

1.7 泛型数组

Java 不能创建具体类型的泛型数组

// 这两行代码是无法在编译器中编译通过的。原因是类型擦除带来的影响 List<Integer>[] list = new ArrayList<Integer>[]; List<Boolean> list = new ArrayList<Boolean>[];

List<Integer>List<Boolean> 在 JVM 中等同于 List<Object>,所有的类型信息都被擦除,程序也无法分辨一个数组中的元素类型具体是 List<Integer> 类型还是 List<Boolean> 类型

List<?>[] list = new ArrayList<?>[10]; list[1] = new ArrayList<String>(); List<?> v = list[1];

借助于无限定通配符却可以,?代表未知类型,所以它涉及的操作都基本上与类型无关,因此 JVM 不需要针对它对类型作判断,因此它能编译通过

更多:泛型就这么简单java 泛型详解Java 泛型,你了解类型擦除吗JAVA泛型通配符T,E,K,V区别面试常问的PECS原则,到底是什么鬼?Java泛型解惑之上下通配符


__EOF__

本文作者holyholic704
本文链接https://www.cnblogs.com/holyholic/p/13670289.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   holyholic704  阅读(166)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
阅读排行:
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· DeepSeek在M芯片Mac上本地化部署
点击右上角即可分享
微信分享提示