本文内容如下:

1、 什么是类型擦除
2、常用的 ?, T, E, K, V, N的含义
3、上界通配符 < ?extends E>
4、下界通配符 < ?super E>
5、什么是PECS原则
6、通过一个案例来理解 ?和 T 和 Object 的区别

一、什么是类型擦除?

我们说Java的泛型是伪泛型,那是因为泛型信息只存在于代码编译阶段,在生成的字节码中是不包含泛型中的类型信息的,使用泛型的时候加上类型参数,在编译器编译的时候会去掉,这个过程为类型擦除。

泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是因为类型擦除特性,让泛型代码能够很好地和之前版本的代码兼容。

我们来看个案例

(图1)

因为这里泛型定义为Integer类型集合,所以添加String的时候在编译时期就会直接报错。

那是不是就一定不能添加了呢?答案是否定的,我们可以通过Java泛型中的类型擦除特点及反射机制实现。

如下

  public static void main(String[] args) throws Exception {
        ArrayList<Integer> list = new ArrayList();
        list.add(6);
        //反射机制实现
        Class<? extends ArrayList> clazz = list.getClass();
        Method add = clazz.getDeclaredMethod("add", Object.class);
        add.invoke(list, "欢迎关注:后端元宇宙");
        System.out.println("list = "   list);
    }

运行结果

list = [6, 欢迎关注:后端元宇宙]

二、案例实体准备

这里先建几个实体,为后面举例用

Animal类

@Data
@AllArgsConstructor
public class Animal {

    /**
     * 动物名称
     */
    private String name;

    /**
     * 动物毛色
     */
    private String color;
}

Pig类 :Pig是Animal的子类

public class Pig  extends  Animal{
    public Pig(String name,String color){
        super(name,color);
    }
}

Dog类: Dog也是Animal的子类

public class Dog extends Animal {

    public Dog(String name,String color){
        super(name,color);
    }
}

三、常用的 ?, T, E, K, V, N的含义

我们在泛型中使用通配符经常看到T、F、U、E,K,V其实这些并没有啥区别,我们可以选 A-Z 之间的任何一个字母都可以,并不会影响程序的正常运行。

只不过大家心照不宣的在命名上有些约定:

  • T (Type) 具体的Java类
  • E (Element)在集合中使用,因为集合中存放的是元素
  • K V (key value) 分别代表java键值中的Key Value
  • N (Number)数值类型
  • ? 表示不确定的 Java 类型

四、上界通配符 < ? extends E>

语法:<? extends E>

举例:<? extends Animal> 可以传入的实参类型是Animal或者Animal的子类

两大原则

  • add:除了null之外,不允许加入任何元素!
  • get:可以获取元素,可以通过E或者Object接受元素!因为不管存入什么数据类型都是E的子类型

示例

  public static void method(List<? extends Animal> lists){
        //正确 因为传入的一定是Animal的子类
        Animal animal = lists.get(0);
        //正确 当然也可以用Object类接收,因为Object是顶层父类
        Object object = lists.get(1);
        //错误 不能用?接收
        ? t = lists.get(2);
        // 错误
        lists.add(new Animal());
        //错误
        lists.add(new Dog());
        //错误 
        lists.add(object);
        //正确 除了null之外,不允许加入任何元素!
        lists.add(null);
    }

五、下界通配符 < ? super E>

语法: <? super E>

举例 :<? super Dog> 可以传入的实参的类型是Dog或者Dog的父类类型

两大原则

  • add:允许添加E和E的子类元素!
  • get:可以获取元素,但传入的类型可能是E到Object之间的任何类型,也就无法确定接收到数据类型,所以返回只能使用Object引用来接受!如果需要自己的类型则需要强制类型转换。

示例

 public static void method(List<? super Dog> lists){
        //错误 因为你不知道?到底啥类型
        Animal animal = lists.get(0);
        //正确 只能用Object类接收
        Object object = lists.get(1);
        //错误 不能用?接收
        ? t = lists.get(2);
        //错误
        lists.add(object);
        //错误
        lists.add(new Animal());
        //正确
        lists.add(new Dog());
        //正确 可以存放null元素
        lists.add(null);
    }

六、什么是PECS原则?

PECS原则:生产者(Producer)使用extends,消费者(Consumer)使用super。

原则

  • 如果想要获取,而不需要写值则使用" ? extends T "作为数据结构泛型。
  • 如果想要写值,而不需要取值则使用" ? super T "作为数据结构泛型。

示例-

public class PESC {
    ArrayList<? extends Animal> exdentAnimal;
    ArrayList<? super Animal> superAnimal;
    Dog dog = new Dog("小黑", "黑色");

    private void test() {
        //正确 
        Animal a1 = exdentAnimal.get(0);
        //错误 
        Animal a2 = superAnimal.get(0);

        //错误 
        exdentAnimal.add(dog);
        //正确 
        superAnimal.add(dog);
    }
}

示例二

Collections集合工具类有个copy方法,我们可以看下源码,就是PECS原则。

 public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i  )
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i  ) {
                di.next();
                di.set(si.next());
            }
        }
    }

我们按照这个源码简单改造下

public class CollectionsTest {
    
    /**
     * 将源集合数据拷贝到目标集合
     *
     * @param dest 目标集合
     * @param src  源集合
     * @return 目标集合
     */
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        for (int i = 0; i < srcSize; i  ) {
            dest.add(src.get(i));
        }
    }
    
    public static void main(String[] args) {
        ArrayList<Animal> animals = new ArrayList();
        ArrayList<Pig> pigs = new ArrayList();
        pigs.add(new Pig("黑猪", "黑色"));
        pigs.add(new Pig("花猪", "花色"));

        CollectionsTest.copy(animals, pigs);
        System.out.println("dest = "   animals);
    }
    
}

运行结果

dest = [Animal(name=黑猪, color=黑色), Animal(name=花猪, color=花色)]

七、通过一个案例来理解 ?和 T 和 Object 的区别

1、实体转换

我们在实际开发中,经常进行实体转换,比如SO转DTO,DTO转DO等等,所以需要一个转换工具类。

如下示例

/**
 *  实体转换工具类
 *  
 *  TODO 说明该工具类不能直接用于生产,因为为了代码看去清爽点,我少了一些必要检验,所以如果直接拿来使用可以会在某些场景下会报错。
 */
public class EntityUtil {
  
    /**
     * 集合实体转换
     *
     * @param target 目标实体类
     * @param list   源集合
     * @return 装有目标实体的集合
     */
    public static <T> List<T> changeEntityList(Class<T> target, List<?> list) throws Exception {
        if (list == null || list.size() == 0) {
            return null;
        }
        List<T> resultList = new ArrayList<T>();
        //用Object接收
        for (Object obj : list) {
            resultList.add(changeEntityNew(target, obj));
        }
        return resultList;
    }
    /**
     * 实体转换
     *
     * @param target 目标实体class对象
     * @param baseTO 源实体
     * @return 目标实体
     */
    public static <T> T changeEntity(Class<T> target, Object baseTO) throws Exception{
        T obj = target.newInstance();
        if (baseTO == null) {
            return null;
        }
        BeanUtils.copyProperties(baseTO, obj);
        return obj;
    }
}

使用工具类示例

 private void  changeTest() throws Exception {
        ArrayList<Pig> pigs = new ArrayList();
        pigs.add(new Pig("黑猪", "黑色"));
        pigs.add(new Pig("花猪", "花色"));
        //实体转换
        List<Animal> animals = EntityUtil.changeEntityList(Animal.class, pigs);
    }

这是一个很好的例子,从这个例子中我们可以去理解 ?和 T 和 Object的使用场景。

我们先以集合转换来说

public static <T> List<T> changeEntityListNew(Class<T> target, List<?> list);

首先其实我们并不关心传进来的集合内是什么对象,我们只关系我们需要转换的集合内是什么对象,所以我们传进来的集合就可以用List<?>表示任何对象的集合都可以。

返回呢,这里指定的是Class<T>,也就是返回最终是List<T>集合。

再以实体转换方法为例

public static <T> T changeEntityNew(Class<T> target, Object baseTO)

同样的,我们并不关心源对象是什么,我们只关心需要转换的对象,只需关心需要转换的对象为T

那为什么这里用Object上面用?呢,其实上面也可以改成List<Object> list,效果是一样的,上面List<?> list在遍历的时候最终不就是用Object接收的吗

?和Object的区别

?类型不确定和Object作用差不多,好多场景下可以通用,但?可以缩小泛型的范围,如:List<? extends Animal>,指定了范围只能是Animal的子类,但是用List<Object>,没法做到缩小范围。

总结

  • 只用于读功能时,泛型结构使用<? extends T>
  • 只用于写功能时,泛型结构使用<? super T>
  • 如果既用于写,又用于读操作,那么直接使用<T>
  • 如果操作与泛型类型无关,那么使用<?>

到此这篇关于Java 泛型中的通配符的文章就介绍到这了,更多相关Java 泛型通配符内容请搜索Devmax以前的文章或继续浏览下面的相关文章希望大家以后多多支持Devmax!

最新Java 泛型中的通配符讲解的更多相关文章

  1. ios – 如何在Swift中使用没有类型参数的泛型类?

    解决方法Swift还不像Java那样支持wildcard-stylegenerics(即Animal

  2. ios – 如何在Swift中向下转换/转换结构的泛型类型

    我是否必须将这些存储为Any的数组,然后每次都将它们转换为或者我只是误解某些(或两者)?

  3. ios – 在XCTestCase子类中使用泛型有效吗?

    我有一个XCTestCase子类,看起来像这样.为了简洁起见,我已经删除了setup()和tearDown方法:它的子类看起来像这样:在理论上,这应该按预期工作–编译器不会抱怨任何事情.但是只是当我运行测试用例时,setup()方法甚至没有被调用.但是,它表明当testName()方法应该失败时,测试已经过去了.使用泛型是一个问题吗?我可以很容易地想到很多非通用的方法,但是我很想写这样的测试用例.这是XCTest在Objective-C和Swift之间的互操作性?Ergo您的通用XCTestCase子类不

  4. 泛型 – Xcode构建错误时,我添加枚举到泛型类?

    为什么在将泛型类添加到枚举时会收到错误:错误:但是当我这样做时,我没有收到错误:或这个:解决方法您不能将任何类型嵌套在通用的类型中,反之亦然.换句话说,你不能像类,结构和枚举这样做的事情:和乃至苹果人explained的限制原因:It’sanimplementationlimitation.We’llremovetherestrictiononceourcompilerandruntimearea

  5. ios – 如何通过Swift中的泛型类型构造一个属性?

    我在swift中创建了一个泛型类,我想使用“AdaptorType”类型初始化一个适配器,但是我收到一个编译错误我也尝试在init()中初始化它,但是同样的问题在于使用通用类型AdaptorType初始化适配器属性的正确方法是什么?

  6. ios – Equatable实现似乎不适用于泛型

    我仍然在与Swift仿制药作斗争.今天我发现我的Equatable协议实现不起作用,如果它是从泛型类调用的.我的模特课:类,使用泛型类型:它的子类:当我调用TrackingCache实例的removeEntities方法时,我总是在输出中得到相等的:false,即使id是相同的.但是,如果我直接将方法移动到TrackingCache类,它似乎工作正常!

  7. 泛型 – MonoTouch和支持变体通用接口

    如果是这样,MonoTouch中针对这种情况的推荐解决方法是什么?解决方法这实际上取决于编译器而不是Mono版本.IOW有些东西可能适用于Mono2.10而不适用于MonoTouch6.x.当前版本的MonoTouch附带了smcs编译器和基于2.1的配置文件.较新的功能,如协方差,需要一个完整的4.0编译器和运行时.未来版本的MonoTouch将提供4.0/4.5运行时和编译器.

  8. 什么是我的iOS配置门户中的(Xcode:通配符AppID)?

    突然之间,我在我的中看到了一个新的AppID,其中包含的描述,Apple最近是否添加了它?此应用程序没有(配置)链接,只有链接,我无法配置任何东西!问候解决方法Xcode现在可以自动创建配置文件,因此您不必这样做.此通配符应用程序ID是该配置文件的一部分.

  9. 寒城攻略:Listo 教你 25 天学会 Swift 语言 - 24 Generics

    它可以避免重复的代码,用一种清晰和抽象的方式来表达代码的意图//泛型是Swift强大特征中的一个,许多Swift标准库都是通过泛型代码构建出来的。{forinenumerate{//遍历索引固定字符串的下标ifvalue==valuetoFind{returnindex}}returnnil}letstrings=["cat","dog","llama","parakeet"]ifletfoundindex=findStringIndex{p

  10. Swift语法基础:7 - Swift的Generics

    在前面,我们知道了Swift中的Protocol和Extensions,现在我们来看看另一个东西:Generics(泛型)1.泛型的声明以及简单使用PS:所谓的泛型其实就是一个比较特殊的数组,它可以存储不同类型的数据,这样子我们在写方法的时候,就不需要再写多一个相同功能而类型不同的方法了.2.枚举类型中的泛型3.特定需求的泛型PS:如果你需要某个指定样式的泛型,那么就必须得在泛型里加上where这

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部