android开发之深入理解泛型extends和super的区别(代码片段)

im大钊 im大钊     2022-12-06     615

关键词:

摘要:

什么是泛型?什么是擦除边界?什么是上界限定或下界限定(子类型限定或超类型限定)?什么是类型安全?泛型extends关和super关键字结合通配符?使用的区别,两种泛型在实际Android开发中有什么用处?

一、什么是泛型?

泛型,大概的意思是指没有指定具体的类型的类或方法,以泛型的形式传入一个类中或方法中,在Java编程(Android编程)里面使用一对尖括号和一个大写字母表示,例如:

//泛型类
public interface List<E>

//泛型方法,类型参数位于返回类型之前或void之前
public static <E> boolean contains(E [] arr, E x)
   for(E val:arr)
      if(x.equal(val))
         return true;
      
   
   return false;

尖括号中的内容常用一个大写的字母表示,但也可以是任意的单词或同时传入多个泛型,它的命名规则遵循类名的命名规则(首字母大写),例如:

public interface List<AnyType,A,B,C>

在一个声明了传入泛型的类中,我们可以考虑传入,也可以考虑不传入,比如,现在我们需要一个列表存放数据(但没有明确数据的类型),那么实例化的列表,就可以存放各种类型的数据,代码如下:

//没有具体类型的列表
ArrayList<> mDataSet=new ArrayList<>(); 
        mDataSet.add(new Object());
        mDataSet.add(new String());
        mDataSet.add(new Integer(0));
        mDataSet.add(new Animal());

//指定具体类型的列表(存储其他类型,编译不通过,编译器报错)
ArrayList<String> mDataSetStr=new ArrayList<>();
        mDataSetStr.add(new String());

为了更好理解通配符的用法,现在我们先定几个继承关系的类:WhiteCuteHelloKittyCuteHelloKittyHelloKittyCatAnimal,如下图:

通配符?既可以单独使用,也可以结合Java关键字extendssuper一起使用,关于它们之间的区别在后面介绍,例如:

  //通配符单独使用
ArrayList<?> mDataSetType=new ArrayList<>();
  //通配符结合关键字extends一起使用
ArrayList<? extends Animal> mAnimalChildren = new ArrayList<>();
  //通配符结合关键字super一起使用
ArrayList<? super HelloKitty> mAnimalParents = new ArrayList<>();

这里,没有列举完泛型的用法,关于其他的一些用法可以参考其他文档,继续我们下面的介绍。

二、 什么是擦除边界?

擦除边界,指一个具体的类型,抽象成一个泛型,那么我们就可以在编写代码的时候,根据实际需要指定具体的类型,符合依赖抽象的编程思想。我们使用IDE或Eclipse开发工具编写代码时,在编译期会将传入的具体类型代替泛型,同时方便使用具体类型的属性和方法,比如,我们在一个列表中指定存储Animal这个具体类,那么我们就可以方便使用Animal内部的属性和方法,例如:

    class Animal 
        String name;

        public void setName(String name) 
            this.name = name;
        

        public String getName() 
            return name;
        
    

ArrayList<Animal> mAnimals = new ArrayList<>();
//指定ArrayList的泛型具体类为Animal,其对象可以存储Animal及其子类的对象
        mAnimals.add(new Animal());
        mAnimals.add(new Cat());
        mAnimals.add(new HelloKitty());

//方便使用Animal内部的属性和方法
        String name=mAnimals.get(0).name;
               name=mAnimals.get(1).getName();

简单地说,擦除边界就像一个正方形去掉了四边,变成一个没有固定大小没有边界的图片,在以后需要使用的时候,再给它指定具体的类型,指定的类型可以是圆形、矩形、三角形或六边形、心形等。

三、什么是上界限定或下界限定?

在Java程序中,类与类之间最重要的一种关系——继承关系,简单的描述是一个纵向的排列关系(如上面Animal的UML图)。上界,指的是往上能够找到的最顶端的类(忽略Object类);下界,指的是外下能够找到的最末端的类。泛型<? extends HelloKitty>表示的是所有继承自HelloKitty的子类,那么通配符表示的上界是HelloKitty,但我们不知道它的下界;泛型<? super CuteHelloKitty>表示的是所有CuteHelloKitty的超类,通配符?表示的下界是CuteHelloKitty,同理我们不知道它的上界。于是,初学者在看一些介绍extendssuper区别的文章时,容易将extends的上界和super的下界混到一起记忆,实际上它们彼此拥有自己的上界与下界,这是特别需要注意的!

四、什么是类型安全?

类型安全,指的是在编码阶段,编译器自动对代码进行检查,检查变量或方法的调用是否符合当前类型,如果有不符合的情况,Java编译器就会提示错误,比如,一个HelloKitty的对象mHelloKitty允许调用自身声明或继承的属性和方法,这是符合类型安全的;但是,一个HelloKitty的对象mHelloKitty不允许调用子类CuteHelloKittyWhiteCuteHelloKitty声明的属性和方法,如果强行调用了,编译器会提示错误,这是不符合类型安全的。

简单地说,上溯造型是符合类型安全的,下溯造型是不符合类型安全的。

五、 通配符?与extends、super关键字使用的区别

从上面的学习中,我们知道<? extends T>表示的含义是继承自T的一组类,在一个泛型类List<E>(或方法)中传入泛型<? extends T>,编译器会自动转换成如下代码:

//泛型类
public interface List<? extends T>

    //泛型方法
    boolean add(? extends T e);

下面演示的例子,钊林将T用具体的类Animal代替,在一个泛型类ArrayList<E>传入<? extends Animal>,然后尝试往mAnimalChildren列表中存入子类的对象,最后查看ArrayList的add方法,如下图:

你会发现,编译器报错了,不允许往一个列表中存储Animal本身及其子类的对象,难道我理解错误了吗?这到底是为什么?

在开发者的头脑里,自然很容易理解AnimalCat是符合泛型<? extends Animal>规则的,但是对于编译器来说,它只知道上面泛型表示的是一组类,但不清楚是否是具体的Animal类或其子类,在ArrayList类中,传入的泛型,编译器会自动将类中的泛型替换成<? extends Animal>,比如add方法,变成了boolean add(? extends Animal e),这时候试图传入具体的类AnimalCatHelloKittyCuteHelloKitty,编译器会很生气地告诉你说:“你是不是听不懂我说的话,我要求的是? extends Animal,你却给我一个Animal或其子类!!产生了疑惑:我应该把它当成Animal处理呢?还是应该当成HelloKitty处理呢?抑或是当成CuteHelloKitty处理呢?”因为存在类型不清楚的情况,所以编译器禁止开发者传入具体的类型,

这就是为什么钊林会花一些篇幅提前介绍什么是类型安全的原因,对于编译器要求的是一个A类型,开发者给了一个B类型,这是不符合类型安全的!

? extends Animal表示一种类型,AnimalCatHelloKitty表示另一种类型,所以会报错!

既然不允许我们往里面添加数据,那么,对于泛型? extends Animal,对于开发者到底有什么用处?虽然编译器不允许你往add方法传入数据。

但是对于编译器来说泛型类? extends Animal,可以肯定其继承了Animal的属性和方法,那么在我们封装类的时候,就可以方便地调用继承的属性和方法,例如:

    class Animal 
        String name;

        public void setName(String name) 
            this.name = name;
        

        public String getName() 
            return name;
        
    
    class CuteHelloKitty extends HelloKitty

        public String feature()
            return "This is a cute HelloKitty !";
        
    
    /**
     * 封装的一个类
     * @param <T>
     */
    class AnimalName<T>
        T e;

        public T get() 
            return e;
        

        public T print(T e) 
            return e;
        
    

    public void print()
        //一、绑定为一组继承自Animal的类,允许使用继承的属性和方法
        AnimalName<? extends Animal> animalName=new AnimalName();
        Animal animal=animalName.get();
        System.out.print(animal.getName());

        //二、绑定为一组CuteHelloKitty的类,允许使用继承的属性和方法
        AnimalName<? extends CuteHelloKitty> cuteHelloKittyName=new AnimalName();
        CuteHelloKitty cuteHelloKitty=cuteHelloKittyName.get();
        System.out.print(cuteHelloKitty.feature());
    

既然泛型<? extends Animal>不合适往里面添加数据,那么开发者在设计程序的时候,对于传入泛型<? extends Animal>的类,尽量避免调用往写入数据的方法,只调用读取数据的方法。泛型类<? extends Animal>表示以Animal为上界的所有子类,不确定具体的类型,可能会下溯造型,不符合类型安全!!

但是,如果我想要调用写入数据的方法呢,那该怎么办?那么,你可以考虑使用泛型<? super T>,该泛型表示的含义是以T为下界的一组类,如下图:

看一下下面的例子,编译器允许开发者这样子操作,代码如下:

        //三、绑定为以CuteHelloKitty为下界的一组类
        AnimalName<? super CuteHelloKitty> helloKitty = new AnimalName<>();

        helloKitty.print(new CuteHelloKitty());
        helloKitty.print(new WhiteCuteHelloKitty());

类中传入泛型<? super T>,对于传入的CuteHelloKitty类本身及其子类,在这里编译器会自动上溯造型为CuteHelloKitty,上溯造型符合类型安全的,因此可以调用写入数据的print方法)。编译器允许传入的是T自身或T子类的对象,最终上溯造型为T,符合类型安全!!

总结:

在一个封装类中传入泛型,可以在编码的时候指定泛型为某个具体的类,也可以指定为一组类,指定为一组类可以考虑使用通配符?extendssuper关键字结合,泛型类<? extends T>表示以T为上界的一组子类,适合读取数据,不建议调用写入数据的方法,否则编译器会报错;泛型<? super T>表示以T为下界的一组超类,适合调用写入数据的方法,不适合读取数据。

在开发中,根据实际的需要合理选择传入<? extends T><? super T>或者不使用通配符,符合类型安全!!

深入理解dart中的类型系统和泛型(代码片段)

...以在Dart类型也是可选的。然后动态语言类型系统松散对开发者并不是一件好事,程序逻辑一旦复杂,松散的类型可能就变得混乱,分析起来非常痛苦,但是有静态类型检查可以在编译的时候就快速定位问题所在。其实,Dart类型... 查看详情

android开发之深入理解android7.0系统权限更改相关文档(代码片段)

摘要:Android6.0之后的版本增加了运行时权限,应用程序在执行每个需要系统权限的功能时,需要添加权限请求代码(默认权限禁止),否则应用程序无法响应;Android7.0在Android6.0的基础上,对系统... 查看详情

浅显理解java泛型的super和extends(代码片段)

目录概念简单理解代码样例解读关于List<?superT>add方面返回值方面关于List<?extendsT>add方面返回值方面总结概念简单理解List<?extendsT>表示该集合中存在的都是类型T的子类,包括T自己List<?superT>表示该集合中存的... 查看详情

java泛型疑问之super和extends

...看下去你一定见过或用过List<?extendsT>吧?为什么我说理解成一个集合是错呢?如果理解成一个集合那为什么不用List<T>来表示?所以<?extendsT>不是一个集合,而是T的某一种子类的意思,记住是一种,单一的一种,问... 查看详情

swift之深入解析“泛型”的底层原理(代码片段)

一、泛型简介①Swift泛型Swift提供了泛型可以写出灵活且可重用的函数和类型。Swift标准库是通过泛型代码构建出来的,Swift的数组和字典类型都是泛型集。泛型可以创建一个Int数组,也可创建一个String数组,或者甚至... 查看详情

css移动前端开发之viewport的深入理解

  做移动开发最近遇到了viewport的问题特转载    本文转载自: http://www.cnblogs.com/2050/p/3877280.html   版权归作者所有感谢原作者 移动前端开发之viewport的深入理解在移动设备上进行网页的重构或开发,首先得搞明白的... 查看详情

移动前端开发之viewport的深入理解

在移动设备上进行网页的重构或开发,首先得搞明白的就是移动设备上的viewport了,只有明白了viewport的概念以及弄清楚了跟viewport有关的meta标签的使用,才能更好地让我们的网页适配或响应各种不同分辨率的移动设备。一、viewp... 查看详情

深入java泛型(四rxjava中深入理解泛型)(代码片段)

第一章深入Java泛型四、RxJava中深入理解泛型4.1响应式编程4.2观察者模式4.3RxJava是对观察者模式的一种高级运用,或者说是一种升级,它把观察者模式具体化,更加明确了各个对象之间的关系四、RxJava中深入理解泛型4.... 查看详情

android开发之深入理解androidstudio构建文件build.gradle配置(代码片段)

摘要:每周一次,深入学习Android教程,TeachCourse今天带来的一篇关于AndroidStudio构建文件build.gradle的相关配置,重点学习几个方面的内容:1、applicationId和package属性值的关系,2、怎么配置安全的自定义签名&... 查看详情

深入理解java泛型

泛型是什么一说到泛型,大伙肯定不会陌生,我们代码里面有很多类似这样的语句:List<String>list=newArrayList<>();ArrayList就是个泛型类,我们通过设定不同的类型,可以往集合里面存储不同类型的数据类型(而且只能存储... 查看详情

android编程好书推荐

...员修炼之道Java性能优化权威指南Java虚拟机规范(JavaSE7版)Android推荐书籍Android群英传深入理解AndroidAndroid开发艺术探索Android应用性能优化Android游戏开发详解Android安全架构深究GradleForAndroid中文版Android软件安全与逆向分析Android开发... 查看详情

java中泛型的深入理解(代码片段)

文章目录泛型深入泛型基本介绍自定义泛型类自定义泛型方法自定义泛型接口泛型通配符和上下限泛型深入泛型基本介绍泛型的概述:泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查。泛... 查看详情

进入快速通道的委托(深入理解c#)

1.方法组:所有的名称相同的重载方法合在一起就成为一个方法组。2.协变性和逆变性:协变性指的是——泛型类型参数可以从一个派生类隐式转化为基类。逆变性指的是——泛型类型参数可以从一个基类隐式转化为派生类。注... 查看详情

深入理解stream之原理剖析

...然是JDK1.8。所以,我们有必要聊一聊Java8的一些新特性。深入理解lambda的奥秘深入理解Stream之原理剖析深入理解Stream之foreach源码解析深入浅出NPE神器Optional谈谈接口默认方法与静态方法深入浅出重复注解与类型注解深入浅出JVM元... 查看详情

css深入理解之absolute

一、absolute和float有相同的特性,包裹性和破坏性1、absolute和float的相似1<!doctypehtml>2<html>3<head>4<metacharset="utf-8">5<metaname="viewport"content="width=device-width">6<title>absolu 查看详情

java泛型lt;supert>中super怎么理解?与extends有何不同

super表示调用父类的方法或者属性,有点像this,extends表示继承,通常你需要继承了父类先可以使用super当你子类继承了父类了,使用父类的构造方法,super()要放在构造函数的第一行。参考技术A和父类一样,是泛型的~~~ 查看详情

深入理解stream之foreach源码解析

...然是JDK1.8。所以,我们有必要聊一聊Java8的一些新特性。深入理解lambda的奥秘深入理解Stream之原理剖析深入理解Stream之foreach源码解析深入浅出NPE神器Optional谈谈接口默认方法与静态方法深入浅出重复注解与类型注解深入浅出JVM元... 查看详情

10深入理解c指针之---指针和常量

  该系列文章源于《深入理解C指针》的阅读与理解,由于本人的见识和知识的欠缺可能有误,还望大家批评指教。  指针作为C语言的左膀右臂,使用方便,修改容易,引用数据快速都是很有前景的应用。C语言中常量是值不... 查看详情