百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

六十二、探讨泛型的高级特性:通配符、反射与泛型的结合使用

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);
  }
}

在这个例子中,我们定义了两个泛型方法:

  1. printArray:该方法接受一个泛型数组作为参数,并使用增强型for循环遍历并打印数组中的每个元素。
  2. 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文档格式转换软件,...

取消回复欢迎 发表评论: