泛型
- 基本概念
- 为什么我们需要泛型
- 泛型类型
- 泛型类
- 简单泛型类
- 多元泛型类
- 泛型接口
- 泛型方法
- 为什么要使用泛型方法呢?
- 使用方法
- 泛型的上下限
- 上限
- 下限
- 加点难度的例子
- 例子一
- 例子二
- 例子三
- 深入理解泛型
- 什么是泛型擦除后保留的原始类型
- 泛型类型擦除原则
- 如何进行擦除的?
- 怎么证明存在类型擦除呢
- 如何理解泛型的编译期检查
- 泛型中参数化类型为什么不考虑继承关系
- 如何理解泛型的多态和桥接方法
- 如何理解基本类型不能作为泛型类型
- 如何理解泛型类型不能实例化
- 如何理解泛型类中的静态方法和静态变量
基本概念
Java泛型是JDK5引入的一个新特性,它提供编译时类型安全检测机制.
该机制可以在编译时检测到非法的类型.
泛型的本质是参数化类型(所操作的数据类型被指定为一个参数)
换句话说:由原来具体的类型,转换为类似方法中的变量参数(形参
为什么我们需要泛型
我们举个例子来看一下:
List arrayList = new ArrayList(); arrayList.add("string"); arrayList.add(1); for(int i = 0; i
这段代码在编译时没问题,但一运行就会报错,报错日志如下:
java.lang.Integer cannot be cast to java.lang.String
在JDK1.5之前,没有范型的情况下,通过对类型Object的引用来实现参数的“任意化”,“任意化”带来的缺点是要做显式的强制类型的转换,这需要开发者对实际参数类型可以预知才能进行.
对于强转类型的转换作物,编译器在编译期可能不会提示错误,在运行时才出现异常,所以这是一个安全隐患.
- 提高代码的利用率
范型使得数据的类别可以像参数一样由外部传递进来.它提供了一种扩展能力.更符合面向抽象开发的软件编程宗旨
- 把一些运行时错误提前到了编译时,提高安全性.
只有相匹配的数据才可以正常赋值,否则编译起不通过,它属于类型安全检测机制,提高了软件的安全性,防止出现低级失误
我们再来一个例子来体验一下泛型的好处:
private static int add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static float add(float a, float b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static double add(double a, double b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; }
如果没有泛型的话,要实现这种不同类型的加法,每种类型都需要重载一个add方法;
通过泛型,我们可以复用为一个方法:
private static double add(T a, T b) { System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue())); return a.doubleValue() + b.doubleValue(); }
泛型类型
泛型有三种存在方式:
- 泛型类
- 泛型接口
- 泛型方法
泛型类
泛型用于类的定义中,通过泛型可以完成对一组类向外提供相同的接口
我们先从一个简单泛型类看起
简单泛型类
class Point{ // 此处可以随便写标识符号,T是type的简称 private T var ; // var的类型由T指定,即:由外部指定 public T getVar(){ // 返回值的类型由外部决定 return var ; } public void setVar(T var){ // 设置的类型也由外部决定 this.var = var ; } } public class GenericsDemo06{ public static void main(String args[]){ Point p = new Point() ; // 里面的var类型为String类型 p.setVar("it") ; // 设置字符串 System.out.println(p.getVar().length()) ; // 取得字符串的长度 } }
多元泛型类
class Notepad{ // 此处指定了两个泛型类型 private K key ; // 此变量的类型由外部决定 private V value ; // 此变量的类型由外部决定 public K getKey(){ return this.key ; } public V getValue(){ return this.value ; } public void setKey(K key){ this.key = key ; } public void setValue(V value){ this.value = value ; } } public class GenericsDemo09{ public static void main(String args[]){ Notepad t = null ; // 定义两个泛型类型的对象 t = new Notepad() ; // 里面的key为String,value为Integer t.setKey("汤姆") ; // 设置第一个内容 t.setValue(20) ; // 设置第二个内容 System.out.print("姓名;" + t.getKey()) ; // 取得信息 System.out.print(",年龄;" + t.getValue()) ; // 取得信息 } }
泛型接口
与泛型类的定义及使用基本相同,允许在接口定义中使用类型参数.
实现该接口的类可以为这些类型参数提供具体的类型.
增加了代码的灵活性和重用性
常被用在各种类的生产器中.
举个例子来说:
interface Info{ // 在接口上定义泛型 public T getVar() ; // 定义抽象方法,抽象方法的返回值就是泛型类型 } class InfoImpl implements Info{ // 定义泛型接口的子类 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 i = null; // 声明接口对象 i = new InfoImpl("汤姆") ; // 通过子类实例化对象 System.out.println("内容:" + i.getVar()) ; } }
泛型方法
为什么要使用泛型方法呢?
因为泛型类需要在实例化的时候就指明类型,一旦实例化完成,这个对象的泛型类型就固定下来了。如果你想使用另一种类型的泛型对象,你需要重新实例化这个类,并指定新的类型。不太灵活;
而泛型方法允许在调用方法的时候才指定泛型的具体类型。这意味着可以在不重新实例化类的情况下,多次调用同一个泛型方法,并每次指定不同的类型。
举个例子来说:
- 泛型类
public class Box { private T t; public void set(T t) { this.t = t; } public T get() { return t; } } // 使用时需要指定具体类型,并实例化 Box integerBox = new Box(); integerBox.set(10); Integer value1 = integerBox.get(); // 获取时类型已经确定为Integer Box stringBox = new Box(); // 需要重新实例化来存储字符串类型 stringBox.set("Hello"); String value2 = stringBox.get(); // 获取时类型已经确定为String
- 泛型方法
public class Utility { // 泛型方法,可以在调用时指定类型 public static T echo(T input) { return input; } } // 使用时直接调用方法,并指定类型(类型推断) Integer intValue = Utility.echo(10); // 这里T被推断为Integer类型 String strValue = Utility.echo("Hello"); // 这里显式指定了T为String类型,但实际上可以省略,因为编译器可以推断出来
使用方法
在调用方法的时候指明泛型的具体类型.泛型方法的定义比泛型类和接口要复杂.
泛型类是指实例化类的时候指明泛型的具体类型;
泛型方法是在调用方法的时候指明泛型的具体类型.
只有声明了 的方法才是泛型方法,在泛型类中使用了泛型的成员方法并不是泛型方法.
举个例子来说明基本语法:
/** * 泛型方法介绍 * @param url * @param result * @param 只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法 * 与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。 * @return T 返回值为T类型 */ public static T doGetRequst(String url,Result result){ }
或者拿一张网图来解释:
调用泛型方法语法格式
Class的作用是指明泛型的具体类型,而Class类型的变量c,可以用来创建泛型类的对象.
为什么要用变量c来创建对象呢?
因为既然是泛型方法,就代表我们不知道具体的类型是什么,也不知道构造方法是什么,因此没有办法去new一个对象
但是我们可以利用变量c的newInstance方法区创建对象,也就是利用反射创建对象.
看一个简单的实例帮助理解:
public class GenericMethodExample { public static void printArray(T[] array) { for (T element : array) { System.out.print(element + " "); } System.out.println(); } public static void main(String[] args) { Integer[] intArray = {1, 2, 3, 4, 5}; String[] strArray = {"Hello", "World"}; // 调用泛型方法并传入整型数组 printArray(intArray); // 调用泛型方法并传入字符串数组 printArray(strArray); } }
泛型的上下限
泛型上下限是用来约束泛型类型参数的范围/
上限
泛型的上限使用extends来定义,表示参数化的类型必须是制定类型或其子类.
eg: 表示T可以是Number类或其任何子类(如Integer,Double等)
所以它也称为上界通配符,它定义了泛型类型的上界,在泛型方法或泛型类中使用上限时,可以确保类型安全,并且只允许在泛型容器中添加符合约束条件的对象.
上限例子
class Info{ // 此处泛型只能是数字类型 private T var ; // 定义泛型变量 public void setVar(T var){ this.var = var ; } public T getVar(){ return this.var ; } public String toString(){ // 直接打印 return this.var.toString() ; } } public class demo1{ public static void main(String args[]){ Info i1 = new Info() ; // 声明Integer的泛型对象 } }
下限
泛型的下限使用super关键字来定义,表示参数化的类型必须是制定类型或其超类(父类).
泛型系统并不支持下界通配符(? super T) 作为类型参数来声明泛型类或接口.
但是在使用泛型方法的时候,可以使用下界通配符(? super T)来放宽对类型的约束,允许方法接受更广泛的类型参数.
实际上在泛型方法的参数中使用下界通配符的情况相对较少见,因为Java的类型推断通常足够智能,可以推断出正确的类型参数而无需显式指定下界。
我们在使用通配符(如 ? , ? extends T , ? super T)来表示未知类型或类型范围,但是要遵循一定的规则和最佳实践,确保代码的类型安全和可读性.
例如,在PECS原则(Product Extends, Consumer Super)中建议,当泛型容器用于提供(produce) 元素时,应使用上限通配符;
当用于消费(consume)元素时,应使用无界通配符或下限通配符(尽管Java不支持直接作为类型参数约束的下限通配符)
这样可以确保类型兼容性并避免不必要的类型转换错误。
下限例子
class Info{ private T var ; // 定义泛型变量 public void setVar(T var){ this.var = var ; } public T getVar(){ return this.var ; } public String toString(){ // 直接打印 return this.var.toString() ; } } public class GenericsDemo21{ public static void main(String args[]){ Info i1 = new Info() ; // 声明String的泛型对象 Info i2 = new Info() ; // 声明Object的泛型对象 i1.setVar("hello") ; i2.setVar(new Object()) ; fun(i1) ; fun(i2) ; } public static void fun(Info
- 泛型方法
- 提高代码的利用率