java基础-泛型

泛型的由来

什么是泛型#

泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。

泛型的好处#

  • 好处一:编译的时候能够检查类型安全,并且所有的强制转换都是自动和隐式的。
    • 在没有泛型的情况的下,通过对类型 Object 的引用来实现参数的“任意化”;
    • “任意化”带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的;
    • 对于强制类型转换错误的情况,编译器可能不提示错误,在运行的时候才出现异常,这是本身就是一个安全隐患。

自定义泛型结构

泛型结构包括泛型类、泛型接口和泛型方法

泛型类、泛型接口#

泛型类与泛型接口的区别其实就是类与接口的区别,这里以泛型类为例:

  1. 类中声明的泛型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型,但是在静态方法中不能使用类的泛型。因为泛型类型是在创建类的时候指定的,而静态方法加载的时候,类的实例并没有创建。
  2. 异常类不能是泛型的。

泛型方法#

以JDK中的Collection接口为例,区分普通方法和泛型方法:

Copy
public interface Collection<E> extends Iterable<E> { //普通方法 boolean add(E data); //泛型方法,泛型方法的类型区别于类或接口中的类型,普通类中也可以含有泛型方法 <T> T[] toArray(T[] a); }

注意:区别于泛型类中带有类型符号的普通方法的是,泛型方法可以是静态的。具体说来就是,上面的add方法不能是静态的,因为E是在类创建时类型才确定下来;而toArray方法可以是静态的,因为T是在方法调用时就确定下来了。

泛型与面向对象

泛型与继承#

  • 在此定义三个类,Father、Son和Grandson
  • Grandson extends Son
  • Son extends Father

类的继承#

Copy
//Son不是泛型类,它继承了Father中的Integer public class Son extends Father<Integer> { } //Son<T> 仍然是泛型类 public class Son<T> extends Father<T> { }

参数类型的继承#

java中的向上转型有如下几种形式

Copy
//1.对象,编译通过 Object abj = null; String str = null; abj = str; //2.数组,编译通过 Object[] objArr = null; String[] strArr = null; objArr = strArr //3.泛型1,编译通过,其实就是平常写的List<String> list = new ArrayList<>(); List<String> list1 = null; ArrayList<String> list2 = null; list1 = list2 //4.泛型2,编译不通过,此时的objList和strList的类型不具有子父类关系 List<Object> objList = null; List<String> strList = null; objList = strList; // 第四种情况会导致一些问题,比如如下方法,遍历集合中的元素 public void foreach(List<Object> list) { } // List<String>类型的变量就无法用这个方法遍历,因为List<Object>类型和List<String>类型 不具备子父类关系,无法使用多态,这样就需要重载foreach方法

总结:List<Object>ArrayList<Object>的父类;List<Object>不是List<String>的父类。针对上述的第四种情况,泛型通配符可以解决。

泛型通配符#

示例#

泛型通配符用来表示,在泛型中,List<?>List<String>List<Object>的父类,可以声明一下方法解决上述第四种情况

Copy
public void foreach(List<?> list) { Iterator<?> iterator = list.iterator(); while (iterator.hasNext()) { Object obj = iterator.next(); System.out.println(obj); } }

通配符上下限#

  • ? extends Son,其中Son是通配符?的上限,可以理解为?的类只能是Son及其子类,即小于等于
  • ? super Son,其中Son是通配符?的下限,可以理解为?的类只能是Son及其父类,即大于等于
  • 对于通配符上下限,如List<? extends Son>List<? super Son>,声明以下对象做测试
Copy
List<? extends Son> list1 = new ArrayList<>(); List<? super Son> list2 = new ArrayList<>(); List<Grandson> list3 = new ArrayList<>(); List<Son> list4 = new ArrayList<>(); List<Father> list5 = new ArrayList<>(); //测试一 list1 = list3;//(编译成功) list1 = list4;//(编译成功) list1 = list5;//(编译失败) //结论:G<? extends A>可以作为G<A>和G<B>的父类,其中B是A的子类 //测试二 list2 = list3;//(编译失败) list2 = list4;///(编译成功) list2 = list5;///(编译成功) //结论:G<? super A>可以作为G<A>和G<B>的父类,其中B是A的父类

泛型通配符读写要求#

  • 对于普通的通配符List<?>
Copy
List<String> listStr = new ArrayList<>(); List<Integer> listInteger = new ArrayList<>(); List<?> list = null; list = listStr ; list = listInteger ; //不能添加(只能添加null),编译失败 list.add("1"); list.add(1); //只能读取,读取的是object类型的对象 Object o = list.get(0);
  • 对于通配符上限List<? extends Son>
Copy
//读取数据 list1 = list4;//用list1 = list3同理 //由上面的测试可知,list1中的类型最大为Son,这里用了最大的类型,保证了多态的特性(父类引用指向子类对象) Son s = list1.get(0); //写入数据 //无法确定list1的下限,传入的Son类型不能确定是?的子类还是父类,不满足多态特性 list1.add(new Son());//(编译失败)

无法写入数据,那具体怎么使用呢?通配符在声明局部变量时没什么意义,但是作为形参,限制传入的参数类型时,它非常重要

  • 对于通配符下限List<? super Son>
Copy
//读取数据 list2 = list4; //只能返回Object,因为list2中的对象最小类型是Son,但是上限没有定,只能用顶级父类Object才能保证多态特性 Object o = list2.get(0); //写入数据 //list2的下限是Son,可以添加Son及其子类,以满足多态特性 list2.add(new Son()); list2.add(new Grandson());//(编译成功)

使用案例:TreeSet的构造器中传入的比较器使用了Comparator<? super E>

类型擦除

定义#

  • 泛型是java1.5版本才引入的概念,在这之前没有泛型那个。但是,泛型代码可以很好的兼容以前的版本,那是因为泛型信息只存在于代码编译阶段。在进入JVM之前,与泛型相关的信息会被擦除,我们称之为类型擦除

演示#

Copy
ArrayList<String> strList = new ArrayList<>(); ArrayList<Integer> intList = new ArrayList<>(); System.out.println(strList.getClass().getSimpleName());//ArrayList System.out.println(intList.getClass().getSimpleName());//ArrayList System.out.println(strList.getClass() == intList.getClass());//true

种类#

  • 无限制的类型擦除
    在这里插入图片描述
  • 有限制的类型擦除
    • 类的类型擦除
      在这里插入图片描述
    • 方法的类型擦除
      在这里插入图片描述
  • 桥接方法
    在这里插入图片描述
    在jvm中,对接口进行类型擦除后,生成的info方法,在实现类中为了保持接口和类的实现关系,也需要有这个方法

类型擦除中的一些坑#

//todo

泛型与反射

一些区别#

?和T的区别#

  • ?是一个不确定的类型,通常用于接收返回值或者作为形参
  • T是一个确定的类型,通常用于泛型类和泛型方法的定义

class<?>和class<T>的区别#

  • class<?>作为通配泛型,当我们不知道确切类型的时候,可以做以下声明:
Copy
public class<?> clazz;
  • class<T>List<T>类似,一般用在反射中,具体用法如下:
Copy
// 通过反射的方式生成 Son // 对象,这里比较明显的是,我们需要使用强制类型转换,强制类型转换可能在运行期报ClassCastException Son son= (Son) Class.forName("com.test.model.Son").newInstance(); //使用class<T>优化 //当传入的类型不是T时,无法创建T类型的对象,且在编译期就会报错 public static <T> T createInstance(class<T> clazz) throw Exception { return clazz.newInstance(); }

泛型数组

创建泛型数组#

  • 可以声明带泛型数组的应用,但是不能直接创建带泛型的数组对象
Copy
//编译失败 new ArrayList<String>[5]; //编译成功 ArrayList[] list = new ArrayList[5]; ArrayList<String>[] listArr = list;
  • 不能使用new E[],但是可以E[] elements = (E[]) new Object[capacity],参考ArrayList源码中声明Object[] elementData,而非泛型数组。
Copy
public class Fruit<T> { //编译成功, 可以声明T[] private T[] array; //public Fruit(int length) { //编译失败 //array = new T[length]; //} //可以使用反射加类型强转 public Fruit(Class<T> clazz, int length) { array = (T[]) Array.newInstance(clazz, length); } public void put(int index, T item) { array[index] = item; } }

泛型的使用场景

DAO#

数据库中的一张表对应java中的一个类型,对数据库数据的增删改查,都可以归结为操作这个类,针对多张不同的表,映射到多个类,所要进行的操作都相同(CRUD),因此可以使用泛型。具体用法体现在:

Copy
public class BaseDAO<T> { //带有类型参数T的增删改查 public T get(Query query) { ... } ... } public class XXXDAO extends BaseDAO<XXX> { //直接继承增删改查方法 }
posted @   hierarch_yang  阅读(36)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示
CONTENTS