沉舟侧畔千帆进,病树前头万木春。这篇文章主要讲述Android开发之深入理解泛型extends和super的区别相关的知识,希望能为你提供帮助。
摘要:什么是泛型?什么是擦除边界?什么是上界限定或下界限定(子类型限定或超类型限定)?什么是类型安全?泛型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());
文章图片
为了更好理解通配符的用法,现在我们先定几个继承关系的类:
WhiteCuteHelloKitty
、CuteHelloKitty
、HelloKitty
、Cat
、Animal
,如下图:文章图片
通配符
?
既可以单独使用,也可以结合Java关键字extends
或super
一起使用,关于它们之间的区别在后面介绍,例如://通配符单独使用
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
,同理我们不知道它的上界。于是,初学者在看一些介绍extends
和super
区别的文章时,容易将extends的上界和super的下界混到一起记忆,实际上它们彼此拥有自己的上界与下界,这是特别需要注意的!四、什么是类型安全?类型安全,指的是在编码阶段,编译器自动对代码进行检查,检查变量或方法的调用是否符合当前类型,如果有不符合的情况,Java编译器就会提示错误,比如,一个
HelloKitty
的对象mHelloKitty
允许调用自身声明或继承的属性和方法,这是符合类型安全的;但是,一个HelloKitty
的对象mHelloKitty
不允许调用子类CuteHelloKitty
或WhiteCuteHelloKitty
声明的属性和方法,如果强行调用了,编译器会提示错误,这是不符合类型安全的。简单地说,上溯造型是符合类型安全的,下溯造型是不符合类型安全的。
五、 通配符
?
与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
本身及其子类的对象,难道我理解错误了吗?这到底是为什么?在开发者的头脑里,自然很容易理解
Animal
、Cat
是符合泛型<
? extends Animal>
规则的,但是对于编译器来说,它只知道上面泛型表示的是一组类,但不清楚是否是具体的Animal
类或其子类,在ArrayList类中,传入的泛型,编译器会自动将类中的泛型替换成<
? extends Animal>
,比如add方法,变成了boolean add(? extends Animal e)
,这时候试图传入具体的类Animal
、Cat
、HelloKitty
或CuteHelloKitty
,编译器会很生气地告诉你说:“你是不是听不懂我说的话,我要求的是? extends Animal,你却给我一个Animal或其子类!!产生了疑惑:我应该把它当成Animal处理呢?还是应该当成HelloKitty处理呢?抑或是当成CuteHelloKitty处理呢?”因为存在类型不清楚的情况,所以编译器禁止开发者传入具体的类型,这就是为什么钊林会花一些篇幅提前介绍什么是类型安全的原因,对于编译器要求的是一个A类型,开发者给了一个B类型,这是不符合类型安全的!
? extends Animal
表示一种类型,Animal
、Cat
或HelloKitty
表示另一种类型,所以会报错!既然不允许我们往里面添加数据,那么,对于泛型
? 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,符合类型安全!!总结:在一个封装类中传入泛型,可以在编码的时候指定泛型为某个具体的类,也可以指定为一组类,指定为一组类可以考虑使用通配符
?
与extends
、super
关键字结合,泛型类<
? extends T>
表示以T为上界的一组子类,适合读取数据,不建议调用写入数据的方法,否则编译器会报错;泛型<
? super T>
表示以T为下界的一组超类,适合调用写入数据的方法,不适合读取数据。【Android开发之深入理解泛型extends和super的区别】在开发中,根据实际的需要合理选择传入
<
? extends T>
、<
? super T>
或者不使用通配符,符合类型安全!!推荐阅读
- Android Monkey 介绍
- Mybatis 和Spring整合之mapper代理开发
- 简易安卓APP
- Vue和Vux开发WebApp日志继续完善gulp任务,增加命令行参数
- react-native 安卓模拟器安卓_windows
- 《android开发艺术探索》读书笔记--WindowManager
- 启动Android模拟器问题集锦
- 安卓开发设置沉浸式标题栏
- Android中保存静态秘钥实践(转)