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

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

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

相关推荐

MySQL5.5+配置主从同步并结合ThinkPHP5设置分布式数据库

前言:本文章是在同处局域网内的两台windows电脑,且MySQL是5.5以上版本下进行的一主多从同步配置,并且使用的是集成环境工具PHPStudy为例。最后就是ThinkPHP5的分布式的连接,读写...

thinkphp5多语言怎么切换(thinkphp5.1视频教程)

thinkphp5多语言进行切换的步骤:第一步,在配置文件中开启多语言配置。第二步,创建多语言目录。相关推荐:《ThinkPHP教程》第三步,编写语言包。视图代码:控制器代码:效果如下:以上就是thi...

基于 ThinkPHP5 + Bootstrap 的后台开发框架 FastAdmin

FastAdmin是一款基于ThinkPHP5+Bootstrap的极速后台开发框架。主要特性基于Auth验证的权限管理系统支持无限级父子级权限继承,父级的管理员可任意增删改子级管理员及权限设置支持单...

Thinkphp5.0 框架实现控制器向视图view赋值及视图view取值操作示

本文实例讲述了Thinkphp5.0框架实现控制器向视图view赋值及视图view取值操作。分享给大家供大家参考,具体如下:Thinkphp5.0控制器向视图view的赋值方式一(使用fetch()方...

thinkphp5实现简单评论回复功能(php评论回复功能源码下载)

由于之前写评论回复都是使用第三方插件:畅言所以也就没什么动手,现在证号在开发一个小的项目,所以就自己动手写评论回复,没写过还真不知道评论回复功能听着简单,但仔细研究起来却无法自拔,由于用户量少,所以...

ThinkPHP框架——实现定时任务,定时更新、清理数据

大家好,我是小蜗牛,今天给大家分享一下,如何用ThinkPHP5.1.*版本实现定时任务,例如凌晨12点更新数据、每隔10秒检测过期会员、每隔几分钟发送请求保证ip的活性等本次分享,主要用到一个名为E...

BeyongCms系统基于ThinkPHP5.1框架的轻量级内容管理系统

BeyongCms内容管理系统(简称BeyongCms)BeyongCms系统基于ThinkPHP5.1框架的轻量级内容管理系统,适用于企业Cms,个人站长等,针对移动App、小程序优化;提供完善简...

YimaoAdminv3企业建站系统,使用 thinkphp5.1.27 + mysql 开发

介绍YimaoAdminv3.0.0企业建站系统,使用thinkphp5.1.27+mysql开发。php要求5.6以上版本,推荐使用5.6,7.0,7.1,扩展(curl,...

ThinkAdmin-V5开发笔记(thinkpad做开发)

前言为了快速开发一款小程序管理后台,在众多的php开源后台中,最终选择了基于thinkphp5的,轻量级的thinkadmin系统,进行二次开发。该系统支持php7。文档地址ThinkAdmin-V5...

thinkphp5.0.9预处理导致的sql注入复现与详细分析

复现先搭建thinkphp5.0.9环境...

thinkphp5出现500错误怎么办(thinkphp页面错误)

thinkphp5出现500错误,如下图所示:相关推荐:《ThinkPHP教程》require():open_basedirrestrictionineffect.File(/home/ww...

Thinkphp5.0极速搭建restful风格接口层

下面是基于ThinkPHPV5.0RC4框架,以restful风格完成的新闻查询(get)、新闻增加(post)、新闻修改(put)、新闻删除(delete)等server接口层。1、下载Thin...

基于ThinkPHP5.1.34 LTS开发的快速开发框架DolphinPHP

DophinPHP(海豚PHP)是一个基于ThinkPHP5.1.34LTS开发的一套开源PHP快速开发框架,DophinPHP秉承极简、极速、极致的开发理念,为开发集成了基于数据-角色的权限管理机...

ThinkPHP5.*远程代码执行高危漏洞手工与升级修复解决方法

漏洞描述由于ThinkPHP5框架对控制器名没有进行足够的安全检测,导致在没有开启强制路由的情况下,黑客构造特定的请求,可直接GetWebShell。漏洞评级严重影响版本ThinkPHP5.0系列...

Thinkphp5代码执行学习(thinkphp 教程)

Thinkphp5代码执行学习缓存类RCE版本5.0.0<=ThinkPHP5<=5.0.10Tp框架搭建环境搭建测试payload...

取消回复欢迎 发表评论: