JAVA基础进阶版链接
https://pdai.tech/md/java/basic/java-basic-x-generic.html
五种机制
泛型机制
用处,提高类型安全性和代码重用
- 泛型在编写代码中使用【类型占位符】,而不是具体的类型,泛型是通过“类型擦除”来实现的
- 类型安全性,在运行过程中设置错误检查。
- 代码重用,编写通用的代码,在多个类型中使用。
- 方法的通用性,允许在同一方法中更换使用不同的类型。
三种泛型
泛型类、接口、方法三者的关系比喻:对象:汽车,类:汽车工厂,接口:汽车的操作规范和设计图,方法:汽车的具体操作。方法是类或接口的一部分。类实现接口,接口规定哪些方法的实现,接口定义方法,但没有实现。类里面有方法,提供具体的方法实现。泛型类可以继承泛型接口。
-
泛型方法:方法的签名中包含一个或多个类型参数,允许方法在调用时使用不同的类型。
- public
<U> void printInfo(U data)
-
public <T> T getObject(Class<T> c){ T t =c.newInstance();}//创建对象 //调用指定泛型类 Generic generic=new Generic();//类名实例化对象 Object obj=generic.getObject(Class.forName("com.cnblogs.test.User"));//对象调用方法 //此时obj是User类的实例
- public
-
泛型类:该类在创建实例时接受不同类型的参数。
- class GenericClass
<T>
- class GenericClass
-
泛型接口,只定义了方法的接口和常量
- interface Info
<T>
{ // 在接口上定义泛型
public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型
}
- interface Info
-
泛型类和泛型方法
-
class GenericClass<T> { private T value; //构造方法,创建类的实例并初始化对象的属性,无返回类型 public GenericClass(T value) { this.value = value; } //返回值是类的泛型参数T //Getter方法,获取对象属性值的方法,公共的非静态方法,不接受任何参数。 //public 返回值类型 getXxx() public T getValue() { return value; } //方法没有返回值 public void setValue(T value) { this.value = value; } //泛型方法,方法级别的泛型参数 public <U> void printInfo(U data) { System.out.println("Data: " + data); } } public class ex1_class { public static void main(String[] args) { // 1. 创建 GenericClass 的实例,并指定泛型参数为 String //右边的<>菱形语法,类型参数与左侧声明的泛型参数类型相同,在实例化时参数为("Hello, Generics!") GenericClass<String> genericString = new GenericClass<>("Hello, Generics!"); // 2. 使用 getValue 方法获取属性值 System.out.println("Value: " + genericString.getValue()); // 输出: Value: Hello, Generics! // 3. 使用 setValue 方法修改属性值 genericString.setValue("Updated Value"); System.out.println("Updated Value: " + genericString.getValue()); // 输出: Updated Value: Updated Value // 4. 调用泛型方法 printInfo,传入不同类型的数据 genericString.printInfo(123); // 输出: Data: 123 (Integer 类型) genericString.printInfo(45.67); // 输出: Data: 45.67 (Double 类型) genericString.printInfo("Hello!"); // 输出: Data: Hello! (String 类型) // 5. 创建另一个 GenericClass 实例,泛型参数为 Integer GenericClass<Integer> genericInteger = new GenericClass<>(100); // 6. 获取和修改 Integer 类型的属性值 System.out.println("Value: " + genericInteger.getValue()); // 输出: Value: 100 genericInteger.setValue(200); System.out.println("Updated Value: " + genericInteger.getValue()); // 输出: Updated Value: 200 // 7. 使用泛型方法 printInfo,传入不同类型的数据 genericInteger.printInfo("Generics are powerful!"); // 输出: Data: Generics are powerful! } }
-
-
泛型接口
-
、interface Info<T>{ // 在接口上定义泛型 public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型 } class InfoImpl<T> implements Info<T>{ // 定义泛型接口的子类 private T var ; // 定义属性 public InfoImpl(T var){ // 通过构造方法设置属性内容 this.setVar(var) ; } public void setVar(T var){ this.var = var ; } public T getVar(){ return this.var ; } } public class GenericsDemo24{ public static void main(String arsg[]){ Info<String> i = null; // 声明接口对象 i = new InfoImpl<String>("汤姆") ; // 通过子类实例化对象 System.out.println("内容:" + i.getVar()) ; } }
-
组成
- 泛型类 class ClassName
<T> {}
- 泛型类型参数
<T>
- 类体 {…} 成员变量、方法等
- 泛型类型参数
- 泛型方法
- public
<U> void methodName(U data){}
- (U data)方法参数
- {…}方法体
- public
- 泛型接口
- interface InterfaceName
<T> {}}
- {…}接口体
- interface InterfaceName
泛型的上下限
- 上下限
- extends表示类型的上界,代表参数的类型可以是此类型或者此类型的【子类】
- super表示类型的下界,代表参数的类型可以是此类型或者此类型的【父类】super父
- 泛型不具有协变性(Covariance),泛型类型参数之间没有继承关系。
- List <? extend A> 编译擦除到类型A
-
class A{} class B extends A {} //B是A的子类 如下funD方法会报错 public static void funC(List<A> listA) { // ... } public static void funD(List<B> listB) { funC(listB); // Unresolved compilation problem: The method doPrint(List<A>) in the type test is not applicable for the arguments (List<B>) // ... }
- 修改方法
public static void funC(List<? extend A> listA) { // ... } public static void funD(List<B> listB) { funC(listB); // Unresolved compilation problem: The method doPrint(List<A>) in the type test is not applicable for the arguments (List<B>) // ... }
泛型擦除
- 在编译的时候,删除<>及其包围的部分
- 向后兼容
- 根据参数类型的上下界,推断并替换类型为原生态类型。
- 当类定义中的类型参数存在限制(上下界)时,在类型擦除中替换为类型参数的上界或下界。
- 没有限制的,替换为Object类型
如何证明泛型擦除
- 判断类型是否相等?
-
publicclassTest { publicstaticvoidmain(String[] args) { ArrayList < String > list1 = newArrayList < String > (); list1.add("abc"); ArrayList < Integer > list2 = newArrayList < Integer > (); list2.add(123); System.out.println(list1.getClass() == list2.getClass()); // true,则泛型都被擦除了 } }
-
- 通过反射添加元素
- 在程序中定义了一个ArrayList泛型类型实例化为Integer对象,如果直接调用add()方法,那么只能存储整数数据,不过当我们利用反射调用add()方法的时候,却可以存储字符串,这说明了Integer泛型实例在编译之后被擦除掉了,只保留了原始类型。
-
public class Test { public static void main(String[] args) throws Exception { ArrayList < Integer > list = new ArrayList < Integer > (); list.add(1); //这样调用 add 方法只能存储整形,因为泛型类型的实例为 Integer list.getClass().getMethod("add", Object.class).invoke(list, "asd"); for (int i = 0; i < list.size(); i++) { System.out.println(list.get(i)); } } }
如何理解类型擦除后保留的原始类型?
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
如何理解泛型的编译期检查?(类型检查)
- Java编译器是通过先检查代码中泛型的类型和实际使用的类型是否一致→类型擦除→编译。
- 类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
- 举个例子:
class Box<T> { private T value; public void set(T value) { this.value = value; } public T get() { return value; } } Box<String> stringBox = new Box<>(); stringBox.set("Hello"); // 正确 String str = stringBox.get(); // 正确 Box<Integer> intBox = new Box<>(); intBox.set(123); // 正确 Integer num = intBox.get(); // 正确 // 错误用法:类型不匹配,编译时会报错 stringBox.set(123); // 错误:不能把 Integer 类型放到 Box<String> 中
- 举个例子:
如何理解泛型的多态?泛型的桥接方法
- 桥接方法
- 在父类、子类的继承场景中出现的。父类是泛型类、且在该类中存在泛型方法。子类继承父类,并实现泛型方法。如果在子类实现中不包含父类经过类型擦除后生成的原始类型方法,则编译器会自动将该原始类型方法添加到子类中。这个被添加的原始类型方法叫做桥接方法。
- 假设你有一个泛型类或接口,子类要重写父类的泛型方法。但是,由于泛型的擦除机制,子类重写的泛型方法和父类的泛型方法签名就会发生不一致,这时 Java 编译器就会创建一个“桥接方法”(Bridge Method)来解决这个问题,确保方法签名一致。
- 问题:在编译时,泛型类型会被擦除,变成他们的原始类型或边界类型。泛型的这种设计保证了向后兼容,但是有一些继承和重写方法会出现问题。
- 例子:https://blog.csdn.net/claram/article/details/105383998
-
// 泛型接口 interface GenericInterface<T> { T getValue(); } // 实现类 class MyClass implements GenericInterface<String> { @Override public String getValue() { return "Hello, World!"; } }
- 父类getValue是泛型方法,签名是T getValue()。子类重写方法,签名变成了String getValue(),因为在Myclass中T被替换成了String。
- 由于泛型的擦除机制,父类的getValue在编译时变成了Object getValue(),这样产生问题,子类的getValue方法签名和父类不一致了。因此需要桥接方法,在子类中生成一个额外的签名object。
- 好处
- 父类和子类的方法签名一致,满足 Java 的方法重写原则。
- 在运行时,真正的调用会指向子类的实现,保证功能的正确性。
如何理解泛型类中的静态方法和静态变量
- 静态方法和静态变量不可以使用泛型类参数。
- 因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
如何理解异常中使用泛型?
- Java 不允许在异常类型中使用泛型。
- 不使用泛型异常类
- 捕捉父类异常
- 在异常处理过程中,Java 的泛型类型已经被擦除。所以,Java 编译器在 运行时 无法知道泛型类型的具体信息。
- 异常的类型系统:Java 中的异常是 类层次结构 (Inheritance Hierarchy)的一部分。所有异常类都继承自
Throwable
类,其中常用的异常类有Exception
和RuntimeException
。当捕捉异常时,Java 会根据异常类型进行匹配。 - 在编译时,
BoxException<T>
被擦除成BoxException
,而Box<T>
也被擦除成Box
。因此,当我们在异常处理中捕获这个异常时,编译器无法准确地知道异常的具体类型,无法进行 精确的类型检查 。
如何获取泛型的参数类型?
在 Java 中,由于泛型 类型擦除 的机制,不能直接获取泛型类型的真实类型。但是可以通过 反射 和 匿名类 的方式来间接获取泛型类型。具体方法是:
- 使用
ParameterizedType
接口获取 类 或 方法 的泛型类型信息。 - 使用匿名类来保留泛型类型,允许通过反射访问泛型的类型参数。
【!等学完反射机制再回来看一遍】