六十二、探讨泛型的高级特性:通配符、反射与泛型的结合使用
yuyutoo 2024-10-12 01:23 2 浏览 0 评论
泛型的高级特性如通配符类型、泛型与数组的结合使用以及泛型与反射的结合,进一步拓宽了泛型的应用场景,增强了代码的通用性与表达能力。
通配符类型(?、? extends Type、? super Type)
通配符是泛型中的一个核心概念,允许以更加灵活的方式处理参数化类型。
主要有三种形式:
通配符 | 形式 | 描述 |
无界 通配符 | (?) | 表示可以接受任何类型的泛型参数。在不需要具体类型信息或希望接受多种泛型参数类型时非常有用。例如,List<?> 可以指向任何类型的 List。 |
上界 通配符 | (? extends Type) | 表示只能接受类型 Type 或其子类作为泛型参数。这在方法需要向集合中读取元素但不添加新元素时非常有用,确保类型的安全性。 |
下界 通配符 | (? super Type) | 接受类型 Type 或其超类作为泛型参数。适用于需要向集合中添加元素而不关心具体类型,同时可能从集合中读取到 Type 类型或其父类型的情况。 |
无界通配符(?)案例
假设我们有一个方法,需要打印出列表中所有元素的值,但不知道这个列表的元素具体是什么类型。为了编写一个通用的打印方法,可以使用无界通配符。
import java.util.List;
public class GenericPrintExample {
// 使用无界通配符来定义一个可以接收任何类型列表的打印方法
public static void printList(List<?> list) {
for (Object item : list) {
// 因为不知道具体的类型,所以这里只能将元素当作Object类型处理
// 可以通过调用toString()方法获取元素的字符串表示
System.out.println(item.toString());
}
}
public static void main(String[] args) {
// 创建一个Integer类型的列表
List<Integer> intList = java.util.Arrays.asList(1, 2, 3, 4, 5);
printList(intList); // 调用printList方法,打印整数列表
// 创建一个String类型的列表
List<String> stringList = java.util.Arrays.asList("a", "b", "c", "d", "e");
printList(stringList); // 调用printList方法,打印字符串列表
}
}
在上面的代码中,定义一个printList方法,接受一个类型为List<?>的参数。这里的?表示任意类型,所以这个方法可以接受任何类型的列表作为参数。在方法内部,遍历列表并打印出每个元素的值。由于不知道具体的类型,所以这里只能将元素当作Object类型处理,并调用toString()方法获取元素的字符串表示。
在main方法中,创建一个Integer类型的列表和一个String类型的列表,并分别调用printList方法来打印。printList方法使用无界通配符,可以接受这两种类型的列表作为参数,并成功打印出它们的值。
上界通配符(? extends Type)案例
假设有一个方法,需要接收一个动物(Animal)类型的列表,但列表中的元素可能是动物的具体子类(如Dog或Cat)。由于不确定具体的子类类型,但可以确定它们都是Animal的子类,因此可以使用上界通配符来定义这个方法。
import java.util.List;
class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
}
class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("The dog barks");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("The cat meows");
}
}
public class UpperBoundWildcardExample {
// 使用上界通配符来定义一个可以接收Animal类型或其子类类型列表的方法
public static void makeAnimalSounds(List<? extends Animal> animals) {
for (Animal animal : animals) {
// 因为类型参数是Animal或其子类,所以这里可以直接调用Animal的方法
animal.makeSound();
}
}
public static void main(String[] args) {
// 创建一个Dog类型的列表
List<Dog> dogList = java.util.Arrays.asList(new Dog(), new Dog());
makeAnimalSounds(dogList); // 调用makeAnimalSounds方法,打印Dog的声音
// 创建一个Cat类型的列表
List<Cat> catList = java.util.Arrays.asList(new Cat(), new Cat());
makeAnimalSounds(catList); // 调用makeAnimalSounds方法,打印Cat的声音
// 注意:我们不能向animals列表中添加元素,因为编译器不知道具体的类型
// animals.add(new Dog()); // 这会编译错误
}
}
在上面的代码中,定义一个Animal类以及它的两个子类Dog和Cat。然后,定义了一个makeAnimalSounds方法,接受一个类型为List<? extends Animal>的参数。这里的? extends Animal表示列表中的元素类型是Animal或其子类。在方法内部,可以安全地调用Animal类的方法,因为列表中的所有元素都保证是Animal或其子类。
在main方法中,创建了一个Dog类型的列表和一个Cat类型的列表,并分别调用makeAnimalSounds方法来打印它们的声音。由于makeAnimalSounds方法使用了上界通配符,所以可以接受这两种类型的列表作为参数。但是,由于编译器不知道列表中的具体类型,所以不能向该列表中添加元素(如注释中的animals.add(new Dog());),这会导致编译错误。
下界通配符(? super Type)案例
假设有一个方法,用于将一个特定类型的元素添加到一个集合中,但不知道这个集合的具体类型,只知道它能够存储想要添加的元素类型或其父类型。为编写这样的方法,可以使用下界通配符。
import java.util.Collection;
class Fruit {}
class Apple extends Fruit {}
class Orange extends Fruit {}
public class LowerBoundWildcardExample {
// 使用下界通配符来定义一个可以向集合中添加Fruit或其子类元素的方法
public static void addFruits(Collection<? super Apple> collection) {
collection.add(new Apple()); // 可以添加Apple,因为它是集合类型的具体类型或其子类
// collection.add(new Orange()); // 这可能会编译错误,取决于集合的实际类型
}
public static void main(String[] args) {
// 创建一个可以存储Fruit的集合
Collection<Fruit> fruitCollection = new java.util.ArrayList<>();
addFruits(fruitCollection); // 可以,因为Fruit是Apple的父类型
// 创建一个只能存储Apple的集合
Collection<Apple> appleCollection = new java.util.ArrayList<>();
addFruits(appleCollection); // 也可以,因为集合的实际类型就是Apple
// 创建一个只能存储Orange的集合
// 注意:这个集合不能传递给addFruits方法,因为Orange不是Apple的父类型
// Collection<Orange> orangeCollection = new ArrayList<>();
// addFruits(orangeCollection); // 这会编译错误
// 现在fruitCollection和appleCollection都包含至少一个Apple实例
}
}
在上面的代码中,定义一个Fruit类以及它的两个子类Apple和Orange。然后,定义了一个addFruits方法,接受一个类型为Collection<? super Apple>的参数。这里的? super Apple表示集合中的元素类型是Apple或其父类型。在方法内部,可以安全地向集合中添加Apple实例,因为集合保证能够存储Apple类型或其父类型的元素。
在main方法中,创建了一个可以存储Fruit的集合和一个只能存储Apple的集合。这两个集合都可以传递给addFruits方法,因为它们的类型都是Apple的父类型或Apple本身。但是,如果我们尝试传递一个只能存储Orange的集合给addFruits方法,那么编译器会报错,因为Orange不是Apple的父类型。
泛型与反射的结合使用
反射是Java中一种强大的工具,用于在运行时动态获取和操作类的信息。当泛型遇上反射,可以解锁更多高级功能,如动态创建泛型实例、访问泛型类型信息等。由于Java的类型擦除机制,直接通过反射获取泛型类型参数是不可能的,但通过一些技巧可以间接实现。
示例:
假设有一个需求,需要编写一个通用的工厂方法,该方法能够根据传入的类型参数创建对象实例,并根据提供的属性名和对应的值来设置对象的属性。由于泛型信息在编译后会被擦除,无法直接通过泛型获取类型信息,这时就需要借助反射来动态操作。
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Map;
/**
* 定义一个简单的实体类
*/
class User {
private String name;
private int age;
public User() {
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
class GenericFactory {
/**
* 泛型方法,根据类型T创建对象实例,并根据属性Map设置属性值
* @param clazz 类型T的Class对象
* @param fieldValues 属性名和对应值的映射
* @param <T> 泛型类型
* @return 实例化的对象
*/
public static <T> T createInstanceWithFields(Class<T> clazz, Map<String, Object> fieldValues)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
// 创建对象实例
T instance = clazz.getDeclaredConstructor().newInstance();
// 遍历属性映射,使用反射设置属性值
for (Map.Entry<String, Object> entry : fieldValues.entrySet()) {
String fieldName = entry.getKey();
Object fieldValue = entry.getValue();
// 获取字段
Field field = clazz.getDeclaredField(fieldName);
field.setAccessible(true); // 允许访问私有字段
// 设置字段值
field.set(instance, fieldValue);
}
return instance;
}
}
public class GenericFactoryMain {
public static void main(String[] args)
throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
Map<String, Object> userFields = Map.of("name", "Alice", "age", 30);
User user = GenericFactory.createInstanceWithFields(User.class, userFields);
System.out.println(user.getName()); // 输出: Alice
System.out.println(user.getAge()); // 输出: 30
}
}
泛型与数组的结合使用
因为数组的协变性与泛型的不变性冲突,导致泛型和数组在某些方面是互斥的。但在实际应用中,两者仍可以巧妙结合。例如,可能需要创建一个可以持有某种类型数组的泛型类或方法。尽管直接创建泛型数组是受限的,但可以通过以下方式间接实现
示例:使用泛型方法和反射来遍历和操作数组:
import java.lang.reflect.Array;
public class GenericArrayExample {
// 泛型方法,用于打印数组内容
public static <T> void printArray(T[] array) {
for (T item : array) {
System.out.print(item + " ");
}
System.out.println();
}
// 泛型方法,用于创建一个指定类型和长度的数组
@SuppressWarnings("unchecked")
public static <T> T[] createArray(Class<T> clazz, int length) {
return (T[]) Array.newInstance(clazz, length);
}
public static void main(String[] args) {
// 使用泛型方法创建并初始化一个Integer数组
Integer[] intArray = createArray(Integer.class, 5);
for (int i = 0; i < intArray.length; i++) {
intArray[i] = i;
}
// 使用泛型方法打印数组内容
printArray(intArray);
// 使用泛型方法创建并初始化一个String数组
String[] stringArray = createArray(String.class, 3);
stringArray[0] = "Hello";
stringArray[1] = "World";
stringArray[2] = "!";
// 使用泛型方法打印数组内容
printArray(stringArray);
}
}
在这个例子中,我们定义了两个泛型方法:
- printArray:该方法接受一个泛型数组作为参数,并使用增强型for循环遍历并打印数组中的每个元素。
- createArray:该方法使用Java的反射API中的Array.newInstance方法来创建一个指定类型和长度的数组。注意这里使用了@SuppressWarnings("unchecked")注解来抑制编译器关于未检查类型转换的警告,因为在运行时进行了类型转换。
在main方法中,分别使用createArray方法创建了一个Integer数组和一个String数组,并使用printArray方法打印它们的内容。这样,就能够在不直接创建泛型数组的情况下,使用泛型方法来处理数组。
相关推荐
- 建筑福利-pdf转dwg格式转换器,再也不用描图-极客青年
-
作为一名经常熬夜画图的建筑狗或者cad用户,你体验过pdf图纸描图到cad吗?前几天一个老同学找我,说他的毕业设计需要我帮忙,发给我一份pdf图纸文件,问我怎么把pdf图纸转换成dwg格式。机智的我灵...
- 想学 HTML,不知从何入手?看完这篇文章你就知道了
-
很多人都说HTML是一门很简单的语言,看看书,看看视频就能读懂。但是,如果你完全没有接触过,就想通过看一遍教程,背背标签,想要完全了解HTML,真的有点太天真了。HTML中文...
- 「前端」HTML之结构
-
今天继续为大家分享前端的知识,如果对前端比较感兴趣的小伙伴,可以关注我,我会更大家继续分享更多与前端相关的内容,当然如果内容中又不当的或者文字错误的,欢迎大家在评论区留言,我会及时修改纠正。1.初识H...
- 手把手教你使用Python网络爬虫下载一本小说(附源码)
-
大家好,我是Python进阶者。前言前几天【磐奚鸟】大佬在群里分享了一个抓取小说的代码,感觉还是蛮不错的,这里分享给大家学习。...
- 用于处理pdf文件格式的转换器
-
在上传过程中如果单个文件太大则容易中断,而且文件太大的话对与存储也有些弊端。那么我们应该想到将文件进行压缩(注意这里压缩指的是不改变文件格式的压缩,而不是用变成压缩文件。这里就将以下用专门的软件压缩P...
- 乐书:在线 Kindle 电子书制作和转换工具
-
之前Kindle伴侣曾推荐过可以在Windows和Mac系统平台上运行的kindle电子书制作软件Sigil(教程),用它可以制作出高质量的的ePub格式电子书,当然最后还需要通...
- 付费文档怎么下载?教你5种方法,任意下载全网资源
-
网上查资料的时候,经常遇到需要注册登录或者付费的才能复制或者是下载,遇到这种情况大多数人都会选择重新查。...
- 捡来的知识!3种方法随便复制网页内容,白嫖真香呀
-
网上的资源真的多,所以许多人常常会从网上找资料。我们看到感兴趣的内容,第一时间可能会想要收入囊中。比如说截个图啊,或者挑选有意思的句子复制粘贴,记录下来。可是,有些时候,却会遇到这样的情况:1、内容不...
- AI的使用,生成HTML网页。
-
利用deepseek,豆包,kimi以及通义千问,写入相同的需求。【写一个网页,实现抽奖功能,点击“开始”,按键显示“停止”,姓名开始显示在屏幕上,人员包括:“张三”,“里斯”,“Bool”,“流水废...
- pdf转换成jpg转换器 4.1 官方正式版
-
pdf转换成jpg工具软件简介pdf转换成jpg转换器是一款界面简洁,操作方便的pdf转换成jpg转换器。pdf转换成jpg转换器可以将PDF文档转换为JPG,BMP,GIF,PNG,TIF图片文件。...
- 办公必备的office转换成pdf转换器怎么用?
-
2016-02-2415:53:37南方报道网评论(我要点评)字体刚从校园走出社会,对于快节奏的办公环境,难免会觉得有些吃力。在起步阶段力求将手头上的事情按时完工不出错,但是渐渐的你会发现,别人只...
- 为什么PDF转Word大多要收费?
-
PDF转Word大多都要收费?并非主要是因为技术上的难度,而是基于多方面的商业和版权考虑的,下面给大家浅分析下原因:...
- 如何用python生成简单的html report报告
-
前提:用python写了一个简单的log分析,主要也就是查询一些key,value出来,后面也可以根据需求增加。查询出来后,为了好看,搞个html表格来显示。需要的组件:jinja2flask...
- 学用系列|如何搞定word批量替换修改和格式转换?这里一站搞定
-
想必不少朋友都会碰到批量修改word文档内容、压缩文档图片、文件格式转换等重复性文档处理工作的需要,今天胖胖老师就推荐给大家一个免费工具XCLWinKits,一站搞定你所有的需要。什么是XCLWinK...
- 这款PDF文档转换神器,能帮你解决PDF使用中的许多难点
-
不管是平时的学习还是工作,相信许多朋友都经常接触PDF文件。可以说,PDF文件在我们的日常办公学习过程中的重要性和Word文档一样重要。在之前的更新中,小编介绍了几款非常不错的PDF文档格式转换软件,...
你 发表评论:
欢迎- 一周热门
-
-
前端面试:iframe 的优缺点? iframe有那些缺点
-
带斜线的表头制作好了,如何填充内容?这几种方法你更喜欢哪个?
-
漫学笔记之PHP.ini常用的配置信息
-
其实模版网站在开发工作中很重要,推荐几个参考站给大家
-
推荐7个模板代码和其他游戏源码下载的网址
-
[干货] JAVA - JVM - 2 内存两分 [干货]+java+-+jvm+-+2+内存两分吗
-
正在学习使用python搭建自动化测试框架?这个系统包你可能会用到
-
织梦(Dedecms)建站教程 织梦建站详细步骤
-
【开源分享】2024PHP在线客服系统源码(搭建教程+终身使用)
-
2024PHP在线客服系统源码+完全开源 带详细搭建教程
-
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)