JAVA 泛型与通配符:从原理到实战应用

JAVA 泛型与通配符:从原理到实战应用

JAVA 泛型与通配符:从原理到实战应用

在这里插入图片描述

1.1 本章学习目标与重点

💡 掌握泛型的核心概念与设计初衷,理解泛型的编译期检查机制。
💡 熟练使用泛型类、泛型接口和泛型方法,解决数据类型安全问题。
💡 理解通配符(?)、上界通配符(? extends T)和下界通配符(? super T)的使用场景。
⚠️ 本章重点是 泛型的擦除机制通配符的灵活运用,这是提升代码通用性和安全性的关键。

1.2 泛型的核心概念与设计初衷

1.2.1 为什么需要泛型

在没有泛型的 JDK 5 之前,集合类只能存储 Object 类型的对象。获取元素时需要强制类型转换,这会带来两个严重问题:

  1. 类型不安全:可以向集合中添加任意类型的对象,运行时可能抛出 ClassCastException
  2. 代码臃肿:频繁的强制类型转换会让代码可读性和维护性变差。

💡 泛型的出现就是为了解决这些问题,它的核心思想是 将类型参数化。在定义类、接口或方法时,用一个标识符表示类型,使用时再指定具体类型。
泛型可以在编译期进行类型检查,避免运行时的类型转换异常,同时简化代码。

1.2.2 泛型的核心优势

  • 编译期类型安全:编译器会检查集合中元素的类型,不允许添加错误类型的对象。
  • 消除强制类型转换:获取元素时无需手动转换类型,代码更简洁。
  • 代码复用性高:一套泛型代码可以适配多种数据类型,无需重复编写。

✅ 核心结论:泛型是一种编译期技术,它的作用是在编译阶段保证类型安全,运行时泛型信息会被擦除。

1.3 泛型的三种使用方式

1.3.1 泛型类

💡 泛型类是在类的定义时声明类型参数,语法格式为 class 类名<T>。其中 T 是类型参数,可以是任意标识符,常用的有 T(Type)、E(Element)、K(Key)、V(Value)。

代码实操:自定义泛型集合类

我们实现一个简单的泛型顺序表 GenericArrayList,支持任意类型的数据存储:

publicclassGenericArrayList<T>{// 底层数组,存储泛型类型数据privateObject[] elementData;// 集合大小privateint size;// 默认初始容量privatestaticfinalintDEFAULT_CAPACITY=10;// 无参构造publicGenericArrayList(){ elementData =newObject[DEFAULT_CAPACITY];}// 有参构造,指定初始容量publicGenericArrayList(int initialCapacity){if(initialCapacity <0){thrownewIllegalArgumentException("初始容量不能为负数:"+ initialCapacity);} elementData =newObject[initialCapacity];}// 添加元素publicvoidadd(T t){// 扩容判断if(size == elementData.length){grow();} elementData[size++]= t;}// 获取元素@SuppressWarnings("unchecked")publicTget(int index){// 索引合法性检查if(index <0|| index >= size){thrownewIndexOutOfBoundsException("索引越界:"+ index);}// 强制类型转换,通过泛型保证类型安全return(T) elementData[index];}// 数组扩容privatevoidgrow(){int oldCapacity = elementData.length;// 扩容为原来的1.5倍int newCapacity = oldCapacity +(oldCapacity >>1); elementData =java.util.Arrays.copyOf(elementData, newCapacity);}// 获取集合大小publicintsize(){return size;}// 测试方法publicstaticvoidmain(String[] args){// 1. 存储String类型GenericArrayList<String> strList =newGenericArrayList<>(); strList.add("Java"); strList.add("泛型");// 编译期检查,不允许添加非String类型// strList.add(123); 编译报错String str = strList.get(0);System.out.println("String集合元素:"+ str);// 2. 存储Integer类型GenericArrayList<Integer> intList =newGenericArrayList<>(); intList.add(1); intList.add(2);Integer num = intList.get(1);System.out.println("Integer集合元素:"+ num);}}

输出结果

String集合元素:Java Integer集合元素:2 

⚠️ 注意事项:

  1. 泛型类的类型参数不能是基本数据类型,只能是引用数据类型。如果需要存储基本类型,需使用对应的包装类,如 Integer 代替 int
  2. 泛型类的静态方法中不能使用类的泛型参数,因为静态方法属于类,而泛型参数是在创建对象时指定的。

1.3.2 泛型接口

💡 泛型接口的定义与泛型类类似,语法格式为 interface 接口名<T>。实现泛型接口时,可以指定具体类型,也可以继续使用泛型参数。

代码实操:自定义泛型迭代器接口
// 泛型迭代器接口publicinterfaceGenericIterator<T>{// 判断是否有下一个元素booleanhasNext();// 获取下一个元素Tnext();}// 实现类1:指定具体类型为StringpublicclassStringIteratorimplementsGenericIterator<String>{privateString[] array;privateint index;publicStringIterator(String[] array){this.array = array;this.index =0;}@OverridepublicbooleanhasNext(){return index < array.length;}@OverridepublicStringnext(){return array[index++];}}// 实现类2:继续使用泛型参数publicclassArrayIterator<T>implementsGenericIterator<T>{privateT[] array;privateint index;publicArrayIterator(T[] array){this.array = array;this.index =0;}@OverridepublicbooleanhasNext(){return index < array.length;}@OverridepublicTnext(){return array[index++];}}// 测试类publicclassGenericIteratorTest{publicstaticvoidmain(String[] args){// 测试StringIteratorString[] strArray ={"A","B","C"};GenericIterator<String> strIterator =newStringIterator(strArray);while(strIterator.hasNext()){System.out.println(strIterator.next());}// 测试ArrayIteratorInteger[] intArray ={1,2,3};GenericIterator<Integer> intIterator =newArrayIterator<>(intArray);while(intIterator.hasNext()){System.out.println(intIterator.next());}}}

输出结果

A B C 1 2 3 

1.3.3 泛型方法

💡 泛型方法是在方法声明时指定类型参数,它可以定义在普通类中,也可以定义在泛型类中。语法格式为 public <T> 返回值类型 方法名(T 参数)

泛型方法的核心特点是 类型参数由方法的调用者决定,与类的泛型参数无关。

代码实操:泛型工具方法封装
importjava.util.Arrays;publicclassGenericMethodUtil{// 泛型方法:数组转集合publicstatic<T>GenericArrayList<T>arrayToList(T[] array){GenericArrayList<T> list =newGenericArrayList<>();for(T t : array){ list.add(t);}return list;}// 泛型方法:交换数组中两个元素的位置publicstatic<T>voidswap(T[] array,int i,int j){if(i <0|| i >= array.length || j <0|| j >= array.length){thrownewIndexOutOfBoundsException("索引越界");}T temp = array[i]; array[i]= array[j]; array[j]= temp;}// 测试方法publicstaticvoidmain(String[] args){// 测试数组转集合String[] strArray ={"Java","泛型","方法"};GenericArrayList<String> strList =GenericMethodUtil.arrayToList(strArray);System.out.println("数组转集合大小:"+ strList.size());// 测试交换数组元素Integer[] intArray ={10,20,30,40};System.out.println("交换前数组:"+Arrays.toString(intArray));GenericMethodUtil.swap(intArray,1,3);System.out.println("交换后数组:"+Arrays.toString(intArray));}}

输出结果

数组转集合大小:3 交换前数组:[10, 20, 30, 40] 交换后数组:[10, 40, 30, 20] 

💡 技巧:泛型方法可以配合可变参数使用,进一步提升灵活性。例如:

publicstatic<T>GenericArrayList<T>createList(T... elements){GenericArrayList<T> list =newGenericArrayList<>();for(T t : elements){ list.add(t);}return list;}// 调用方式GenericArrayList<Integer> list =GenericMethodUtil.createList(1,2,3,4);

1.4 泛型的高级特性

1.4.1 泛型的擦除机制

💡 泛型是编译期特性,在运行时 JVM 会擦除泛型信息,这个过程称为类型擦除
类型擦除的规则:

  1. 泛型类、接口和方法的类型参数会被擦除为其上限类型,如果没有指定上限,则擦除为 Object
  2. 编译器会在必要的地方插入强制类型转换代码,保证运行时的类型正确性。

例如,我们定义的 GenericArrayList<String> 在运行时会被擦除为 GenericArrayList<Object>。获取元素时,编译器会自动插入 (String) 强制类型转换代码。

类型擦除的验证

通过反射可以验证类型擦除的存在,因为反射是运行时机制,可以获取到擦除后的类型信息:

importjava.lang.reflect.Field;publicclassGenericErasureTest{publicstaticvoidmain(String[] args)throwsNoSuchFieldException{GenericArrayList<String> strList =newGenericArrayList<>();// 通过反射获取底层数组的类型Field field =GenericArrayList.class.getDeclaredField("elementData"); field.setAccessible(true);Class<?> type = field.getType().getComponentType();System.out.println("底层数组的类型:"+ type.getName());}}

输出结果

底层数组的类型:java.lang.Object 

⚠️ 注意事项:类型擦除会导致泛型不支持重载。例如,以下两个方法在编译后会变成相同的签名,编译器会报错:

// 编译报错:方法重复定义publicvoidmethod(GenericArrayList<String> list){}publicvoidmethod(GenericArrayList<Integer> list){}

1.4.2 泛型的上下界限定

💡 在定义泛型时,可以通过 extends 关键字限制类型参数的上限,通过 super 关键字限制类型参数的下限(仅在通配符中使用)。
上限限定的语法:<T extends 类型>,表示 T 必须是该类型的子类或本身。
多个上限的语法:<T extends 类型1 & 类型2>,表示 T 必须同时实现多个接口(类只能有一个,且放在最前面)。

代码实操:泛型上限限定

我们实现一个泛型方法,用于计算数组中元素的总和,要求元素类型必须是 Number 的子类:

publicclassGenericBoundsTest{// 泛型上限限定:T必须是Number的子类publicstatic<TextendsNumber>doublesum(T[] array){double sum =0.0;for(T t : array){ sum += t.doubleValue();}return sum;}publicstaticvoidmain(String[] args){Integer[] intArray ={1,2,3,4,5};System.out.println("Integer数组总和:"+sum(intArray));Double[] doubleArray ={1.1,2.2,3.3};System.out.println("Double数组总和:"+sum(doubleArray));// String类型不是Number的子类,编译报错// String[] strArray = {"1", "2"};// sum(strArray);}}

输出结果

Integer数组总和:15.0 Double数组总和:6.6 

1.5 通配符的使用与场景

1.5.1 通配符(?)的基本使用

💡 通配符 ? 表示任意类型,它可以用来解决泛型的类型不兼容问题。
例如,GenericArrayList<Object> 不能接收 GenericArrayList<String> 对象,因为泛型是不协变的。此时可以使用通配符 GenericArrayList<?> 来接收任意类型的泛型实例。

代码实操:通配符的基本应用
publicclassWildcardBasicTest{// 使用通配符接收任意类型的GenericArrayListpublicstaticvoidprintList(GenericArrayList<?> list){for(int i =0; i < list.size(); i++){System.out.println("元素:"+ list.get(i));}}publicstaticvoidmain(String[] args){GenericArrayList<String> strList =newGenericArrayList<>(); strList.add("Hello"); strList.add("Wildcard");GenericArrayList<Integer> intList =newGenericArrayList<>(); intList.add(100); intList.add(200);// 可以接收任意类型的泛型集合printList(strList);printList(intList);}}

输出结果

元素:Hello 元素:Wildcard 元素:100 元素:200 

⚠️ 注意事项:使用无界通配符 <?> 的集合,只能读取元素,不能添加元素(除了 null)。因为编译器无法确定集合的具体类型,添加任何类型的元素都可能破坏类型安全。

1.5.2 上界通配符(? extends T)

💡 上界通配符 ? extends T 表示任意 T 的子类或 T 本身。它的核心作用是 限定读取的上限,适合只读的场景。

代码实操:上界通配符的应用

我们实现一个方法,用于获取泛型集合中的最大值,要求元素类型必须实现 Comparable 接口:

publicclassWildcardExtendsTest{// 上界通配符:T必须实现Comparable接口publicstatic<TextendsComparable<T>>TgetMax(GenericArrayList<?extendsT> list){if(list.size()==0){returnnull;}T max = list.get(0);for(int i =1; i < list.size(); i++){T current = list.get(i);if(current.compareTo(max)>0){ max = current;}}return max;}publicstaticvoidmain(String[] args){GenericArrayList<Integer> intList =newGenericArrayList<>(); intList.add(5); intList.add(12); intList.add(3);System.out.println("Integer集合最大值:"+getMax(intList));GenericArrayList<String> strList =newGenericArrayList<>(); strList.add("Apple"); strList.add("Banana"); strList.add("Orange");System.out.println("String集合最大值:"+getMax(strList));}}

输出结果

Integer集合最大值:12 String集合最大值:Orange 

✅ 核心结论:上界通配符 ? extends T 适合读取数据的场景,它可以接收 T 及其子类的泛型实例。

1.5.3 下界通配符(? super T)

💡 下界通配符 ? super T 表示任意 T 的父类或 T 本身。它的核心作用是 限定写入的下限,适合只写的场景。

代码实操:下界通配符的应用

我们实现一个方法,用于向泛型集合中添加指定类型的元素:

publicclassWildcardSuperTest{// 下界通配符:向集合中添加Integer类型的元素publicstaticvoidaddIntegers(GenericArrayList<?superInteger> list){ list.add(1); list.add(2); list.add(3);// 可以添加Integer及其子类的元素// list.add(new Number()); 编译报错,Number是Integer的父类}publicstaticvoidmain(String[] args){// 情况1:集合类型为IntegerGenericArrayList<Integer> intList =newGenericArrayList<>();addIntegers(intList);System.out.println("Integer集合大小:"+ intList.size());// 情况2:集合类型为Number(Integer的父类)GenericArrayList<Number> numList =newGenericArrayList<>();addIntegers(numList);System.out.println("Number集合大小:"+ numList.size());}}

输出结果

Integer集合大小:3 Number集合大小:3 

✅ 核心结论:下界通配符 ? super T 适合写入数据的场景,它可以接收 T 及其父类的泛型实例。

1.5.4 通配符的使用原则:PECS 原则

💡 PECS 原则是通配符使用的黄金法则,全称是 Producer Extends, Consumer Super

  • Producer Extends:如果泛型对象是生产者(提供数据,只读),使用 ? extends T
  • Consumer Super:如果泛型对象是消费者(消费数据,只写),使用 ? super T

例如:

  • 读取数据的 getMax 方法,使用 ? extends T
  • 写入数据的 addIntegers 方法,使用 ? super T
  • 既读又写的场景,不使用通配符,直接使用具体的泛型类型。

1.6 泛型的常见问题与解决方案

1.6.1 泛型数组的创建问题

⚠️ 不能直接创建泛型数组,例如 new T[10] 会编译报错。因为类型擦除后,JVM 无法确定数组的具体类型。
解决方案

  1. 创建 Object 数组,然后强制类型转换。
  2. 使用反射创建泛型数组。
// 方式1:Object数组强制转换T[] array =(T[])newObject[10];// 方式2:反射创建泛型数组publicstatic<T>T[]createArray(Class<T> clazz,int size){return(T[])Array.newInstance(clazz, size);}// 调用方式String[] strArray =createArray(String.class,5);

1.6.2 泛型与异常的问题

⚠️ 泛型不能用于异常类的定义和捕获,例如 class GenericException<T> extends Exception 会编译报错。
原因:异常的处理是运行时机制,而泛型是编译期机制,类型擦除后无法区分不同的泛型异常类型。
解决方案:如果需要传递泛型数据,可以将泛型类型作为异常类的成员变量。

publicclassGenericExceptionextendsException{privateObject data;public<T>GenericException(T data){this.data = data;}@SuppressWarnings("unchecked")public<T>TgetData(){return(T) data;}}

1.6.3 泛型与静态方法的问题

⚠️ 泛型类的静态方法不能使用类的泛型参数,因为静态方法属于类,在类加载时就已经确定,而泛型参数是在创建对象时指定的。
解决方案:将静态方法定义为泛型方法,使用自己的类型参数。

publicclassGenericClass<T>{// 错误写法:静态方法使用类的泛型参数// public static void method(T t) {}// 正确写法:使用泛型方法的类型参数publicstatic<E>voidmethod(E e){}}

1.7 实战案例:泛型集合工具类封装

1.7.1 需求分析

💡 封装一个通用的泛型集合工具类 GenericCollectionUtil,提供以下功能:

  1. 集合去重:去除泛型集合中的重复元素,保留插入顺序。
  2. 集合过滤:根据自定义规则过滤集合中的元素。
  3. 集合转换:将一种类型的集合转换为另一种类型的集合。

1.7.2 代码实现

importjava.util.ArrayList;importjava.util.LinkedHashSet;importjava.util.List;importjava.util.function.Predicate;importjava.util.function.Function;publicclassGenericCollectionUtil{/** * 泛型集合去重,保留插入顺序 * @param list 待去重的集合 * @param <T> 集合元素类型 * @return 去重后的集合 */publicstatic<T>List<T>distinct(List<T> list){if(list ==null|| list.isEmpty()){returnnewArrayList<>();}returnnewArrayList<>(newLinkedHashSet<>(list));}/** * 泛型集合过滤 * @param list 待过滤的集合 * @param predicate 过滤规则 * @param <T> 集合元素类型 * @return 过滤后的集合 */publicstatic<T>List<T>filter(List<T> list,Predicate<T> predicate){if(list ==null|| list.isEmpty()|| predicate ==null){returnnewArrayList<>();}List<T> result =newArrayList<>();for(T t : list){if(predicate.test(t)){ result.add(t);}}return result;}/** * 泛型集合转换 * @param list 源集合 * @param function 转换规则 * @param <T> 源元素类型 * @param <R> 目标元素类型 * @return 转换后的集合 */publicstatic<T,R>List<R>convert(List<T> list,Function<T,R> function){if(list ==null|| list.isEmpty()|| function ==null){returnnewArrayList<>();}List<R> result =newArrayList<>();for(T t : list){R r = function.apply(t); result.add(r);}return result;}// 测试方法publicstaticvoidmain(String[] args){// 测试去重List<Integer> numList =newArrayList<>(); numList.add(1); numList.add(2); numList.add(1); numList.add(3);List<Integer> distinctList =GenericCollectionUtil.distinct(numList);System.out.println("去重后的集合:"+ distinctList);// 测试过滤:过滤出偶数List<Integer> evenList =GenericCollectionUtil.filter(distinctList, num -> num %2==0);System.out.println("过滤后的偶数集合:"+ evenList);// 测试转换:将Integer转换为StringList<String> strList =GenericCollectionUtil.convert(evenList,String::valueOf);System.out.println("转换后的字符串集合:"+ strList);}}

输出结果

去重后的集合:[1, 2, 3] 过滤后的偶数集合:[2] 转换后的字符串集合:[2] 

1.7.3 案例总结

✅ 这个泛型工具类充分利用了泛型的特性,实现了对任意类型集合的通用操作。通过结合函数式接口(PredicateFunction),进一步提升了代码的灵活性和复用性。在实际开发中,这样的工具类可以大幅减少重复代码,提升开发效率。

1.8 本章总结

  1. 泛型的核心是类型参数化,它在编译期保证类型安全,运行时会发生类型擦除。
  2. 泛型的三种使用方式:泛型类、泛型接口、泛型方法,其中泛型方法的灵活性最高。
  3. 泛型的上下界限定可以限制类型参数的范围,提升代码的安全性。
  4. 通配符分为无界通配符(?)、上界通配符(? extends T)和下界通配符(? super T),遵循 PECS 原则使用。
  5. 泛型的常见问题包括泛型数组创建、泛型与异常、泛型与静态方法,需要通过特定方案解决。
  6. 泛型可以大幅提升代码的复用性和安全性,是 JAVA 进阶开发的必备技能。

Read more

vue3+python基于python的球类体育赛事发布和在线购票选座系统60576715

vue3+python基于python的球类体育赛事发布和在线购票选座系统60576715

目录 * 技术栈与项目概述 * 系统架构设计 * 数据库模型设计 * 前端关键技术实现 * 后端核心逻辑 * 安全与性能优化 * 测试与部署 * 扩展方向 * 开发技术路线 * 源码lw获取/同行可拿货,招校园代理 :文章底部获取博主联系方式! 技术栈与项目概述 * 前端框架: Vue 3 (Composition API + TypeScript) * 后端语言: Python (FastAPI/Django 选型分析) * 数据库: PostgreSQL/MySQL 与 Redis 缓存 * 核心功能: 赛事发布、在线选座购票、支付集成、实时数据更新 系统架构设计 * 前后端分离: RESTful API 接口设计规范 * 微服务模块划分: 用户服务、赛事管理、订单支付、座位库存 * WebSocket 应用: 实时推送座位锁定状态与赛事更新

By Ne0inhk

【Python】python-can使用记录

前言 之前用python做了一些汽车领域CAN模块相关自动化,主要集中在软件的代码开发阶段。随着功能的成熟,逐渐想把测试的工作也自动化起来,后来就写了个简单的上位机,支持Trace、Graph、Log、UDS、刷写等功能,可加载自定义py测试脚本。 近期突然想到能不能用canoe软件做上位机,再结合其他总线设备的桥接方式,从网上真找到了方案,尝试过后发现很棒。 不论是一开始的上位机,还是后面的桥接工具,都是基于这个python-can去做的开发,所以在此记录和分享下这个模块。 1 安装 1. 设备的CAN驱动需要自行手动安装。 直接使用pip安装python-can pip install python-can 可在: pypi.org搜索“python-can”,查阅手册(链接),里面有详细的功能讲解以及支持的硬件设备。 2 功能说明 2.1 bus的实例化(创建) 2.1.1 Bus类 can.interface.Bus是比较关键的一个类,用于创建bus实体控制总线的行为。通俗地讲就是打开硬件设备,

By Ne0inhk
Ubuntu系统下Python连接国产KingbaseES数据库实现增删改查

Ubuntu系统下Python连接国产KingbaseES数据库实现增删改查

摘要:本文将介绍Ubuntu系统下如何使用Python连接国产金仓数据库KingbaseES,并实现基本的增删改查操作。文中将通过具体代码示例展示连接数据库、执行SQL语句以及处理结果的全过程。这里把Python连接KingbaseES的经验整理一下,希望能帮到同样踩坑的兄弟。 目录 1.环境准备与驱动安装 1.1 科普ksycopg2知识 1.2 官方下载ksycopg2驱动 1.3 安装ksycopg2驱动 2. 连接KingbaseES数据库 3. 创建数据表 4. 实现增删改查功能 4.1 新增 4.2 查询 4.3 修改 4.4 删除 4.5 封装一个类crud方便复用 5.总结 1.环境准备与驱动安装 KingbaseES提供了专门的Python驱动包ksycopg2,它是基于Python DB API 2.0规范实现的线程安全数据库适配器!

By Ne0inhk
Python第六课:从零理解面向对象

Python第六课:从零理解面向对象

文章目录 * 引言 * 为什么需要面向对象? * 什么是面向对象? * 1. 先聊聊 面向过程 * 2. 面向对象 * 3. 面向过程 vs 面向对象 * 4. 举个例子 * 5. 小结 * 类与对象:模具与产品 * 1. 创建你的第一个类 * 2. 用类创建对象 * 3. 类与对象的本质区别 * 4. 小结 * 给类添加属性 * 给类添加方法 引言 如果你做过蛋糕,一定知道模具的作用:一按一扣,形状就出来了,不用每个蛋糕都手工捏 Python 里的 类(class) 就是那个模具,对象(object) 就是烤出来的蛋糕 想批量生产结构相似的数据?想给每个数据都配上专属的函数?用面向对象就对了 本文会用最生活化的例子,带你亲手做一个“模具”,再做出几个“

By Ne0inhk