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

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

yuyutoo 2024-10-12 01:23 5 浏览 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方法打印它们的内容。这样,就能够在不直接创建泛型数组的情况下,使用泛型方法来处理数组。

相关推荐

墨尔本一华裔男子与亚裔男子分别失踪数日 警方寻人

中新网5月15日电据澳洲新快网报道,据澳大利亚维州警察局网站消息,22岁的华裔男子邓跃(Yue‘Peter’Deng,音译)失踪已6天,维州警方于当地时间13日发布寻人通告,寻求公众协助寻找邓跃。华...

网络交友须谨慎!美国犹他州一男子因涉嫌杀害女网友被捕

伊森·洪克斯克(图源网络,侵删)据美国广播公司(ABC)25日报道,美国犹他州一名男子于24日因涉嫌谋杀被捕。警方表示,这名男子主动告知警局,称其杀害了一名在网络交友软件上认识的25岁女子。雷顿警...

一课译词:来龙去脉(来龙去脉 的意思解释)

Mountainranges[Photo/SIPA]“来龙去脉”,汉语成语,本指山脉的走势和去向,现比喻一件事的前因后果(causeandeffectofanevent),可以翻译为“i...

高考重要考点:range(range高考用法)

range可以用作动词,也可以用作名词,含义特别多,在阅读理解中出现的频率很高,还经常作为完形填空的选项,而且在作文中使用是非常好的高级词汇。...

C++20 Ranges:现代范围操作(现代c++白皮书)

1.引言:C++20Ranges库简介C++20引入的Ranges库是C++标准库的重要更新,旨在提供更现代化、表达力更强的方式来处理数据序列(范围,range)。Ranges库基于...

学习VBA,报表做到飞 第二章 数组 2.4 Filter函数

第二章数组2.4Filter函数Filter函数功能与autofilter函数类似,它对一个一维数组进行筛选,返回一个从0开始的数组。...

VBA学习笔记:数组:数组相关函数—Split,Join

Split拆分字符串函数,语法Split(expression,字符,Limit,compare),第1参数为必写,后面3个参数都是可选项。Expression为需要拆分的数据,“字符”就是以哪个字...

VBA如何自定义序列,学会这些方法,让你工作更轻松

No.1在Excel中,自定义序列是一种快速填表机制,如何有效地利用这个方法,可以大大增加工作效率。通常在操作工作表的时候,可能会输入一些很有序的序列,如果一一录入就显得十分笨拙。Excel给出了一种...

Excel VBA入门教程1.3 数组基础(vba数组详解)

1.3数组使用数组和对象时,也要声明,这里说下数组的声明:'确定范围的数组,可以存储b-a+1个数,a、b为整数Dim数组名称(aTob)As数据类型Dimarr...

远程网络调试工具百宝箱-MobaXterm

MobaXterm是一个功能强大的远程网络工具百宝箱,它将所有重要的远程网络工具(SSH、Telnet、X11、RDP、VNC、FTP、MOSH、Serial等)和Unix命令(bash、ls、cat...

AREX:携程新一代自动化回归测试工具的设计与实现

一、背景随着携程机票BU业务规模的不断提高,业务系统日趋复杂,各种问题和挑战也随之而来。对于研发测试团队,面临着各种效能困境,包括业务复杂度高、数据构造工作量大、回归测试全量回归、沟通成本高、测试用例...

Windows、Android、IOS、Web自动化工具选择策略

Windows平台中应用UI自动化测试解决方案AutoIT是开源工具,该工具识别windows的标准控件效果不错,但是当它遇到应用中非标准控件定义的UI元素时往往就无能为力了,这个时候选择silkte...

python自动化工具:pywinauto(python快速上手 自动化)

简介Pywinauto是完全由Python构建的一个模块,可以用于自动化Windows上的GUI应用程序。同时,它支持鼠标、键盘操作,在元素控件树较复杂的界面,可以辅助我们完成自动化操作。我在...

时下最火的 Airtest 如何测试手机 APP?

引言Airtest是网易出品的一款基于图像识别的自动化测试工具,主要应用在手机APP和游戏的测试。一旦使用了这个工具进行APP的自动化,你就会发现自动化测试原来是如此简单!!连接手机要进行...

【推荐】7个最强Appium替代工具,移动App自动化测试必备!

在移动应用开发日益火爆的今天,自动化测试成为了确保应用质量和用户体验的关键环节。Appium作为一款广泛应用的移动应用自动化测试工具,为测试人员所熟知。然而,在不同的测试场景和需求下,还有许多其他优...

取消回复欢迎 发表评论: