android 判读泛型类型,Android基础-Java泛型

泛型的定义

泛型:参数化的类型。很简单的一句话,那么什么叫做“参数化的类型”呢?。。。。。

为什么需要泛型?

假设现在有这样一个需求:把两个整数进行相加并返回计算结果。我们很轻松的就可以写出如下代码来完成此功能:

public int addInt(int x, int y) {

return x + y;

}

后来有一天业务拓展,需要支持浮点数进行相加并返回计算结果。于是我们可以新增一个新的如下方法来完成新需求:

public float addFloat(float x, float y) {

return x + y;

}

日子一天天的过去,又有了新的需求,需要支持double类型的相加,那么我们依然可以依葫芦画瓢的再增加一个新的方法来完成double类型的数据相加。诶?等等。。。观察上面的两个方法除了参数的类型和返回值不同之外,其他的都相同(方法名是可以相同的,称之为重载,此处为了区分类型,故写作不同名称)。于是为了解决这个问题,java就引入了泛型机制,可以很好的解决重复代码。

我们平常用的最多的数据结构恐怕就是List了。例如:

List list = new ArrayList();

list.add("element0");

list.add("element1");

list.add(100);

这段代码无论在编写阶段还是运行阶段都是不会报错的。

for (int i = 0; i < list.size(); i++) {

String value = (String) list.get(i);

System.out.println("第" + (i + 1) + "个元素的值是: " + value);

}

可是当我们需要用上面的代码遍历输出集合中元素的时候,却会报出ClassCastException。这时因为最后添加的一个元素是 int 类型,强转为 String 类型肯定是会报错的。于是这里就体现出了泛型的第二个好处:安全。于是我们经常如下去创建一个List:

List list = new ArrayList<>();

list.add("element0");

list.add("element1");

list.add(100); // 编译器会提示此行代码报错

并且加了泛型之后在取值的时候也不需要强制类型转换了。

综上所述:1.适用于多种数据类型执行相同的代码。2.在编译期间就发现数据类型安全问题。这也就是我们为什么要使用泛型的原因。

泛型的使用

泛型类

public class GenericClass {

private T data;

public GenericClass(T data) {

this.data = data;

}

public T getData() {

return data;

}

public void setData(T data) {

this.data = data;

}

}

泛型接口

public interface GenericInterface {

T next();

}

关于泛型接口的实现有两种:

public class GenericInterfaceImpl1 implements GenericInterface{

@Override

public String next() {

return null;

}

}

public class GenericInterfaceImpl2 implements GenericInterface{

@Override

public T next() {

return null;

}

}

这两种方式的区别就是一个是在使用的时候才确定具体类型,一个在声明类的时候就确定了类型。

泛型方法

public T genericMethod(T... t) {

return t[t.length / 2];

}

其中是定义泛型方法所必须的,如果没有的话,即使这个方法带有 T 、或者 E 这种常见的泛型定义,那么它依然不是一个泛型方法。例如上面泛型类中的 getData() 或者 setData(T data),前面没有,它们依旧是普通的方法,只不过他们是定义在了泛型类中罢了。

类型变量的限定-用于方法上

public static T min(T a, T b) {

if (a.compareTo(b) > 0) return a;else return b;

}

extends 关键字从面相对象上严格来说是叫派生。那么 T extends Comparable 就可以理解为派生自 Comparable 这个接口的子类。故下面可以使用Comparable 接口中的 compareTo() 方法。假如尖括号中只有一个 T,那么参数a是无法调用compareTo()方法的。这就是所谓的类型变量的限定。

关于限定类型的使用有如下规则:

可以有多个限定类型,中间用 & 符号隔开

可以是类,也可以是接口。如果是类的话需要放在第一个的位置

有且只能有一个类,接口则没有限制。因为java是单继承,多实现。

下面是简单示例:

public static T min(T a, T b) {

if (a.compareTo(b) > 0) return a;else return b;

}

此时传入的参数需要满足是 View 的子类,且同时实现了Comparable 和 Serializable 接口才能使用。否则无法通过编译。

类型变量的限定-用于类上

public class GenericClass {

private T data;

public GenericClass(T data) {

this.data = data;

}

public T getData() {

return data;

}

public void setData(T data) {

this.data = data;

}

public T min(T t) {

if (this.data.compareTo(t) > 0) {

return t;

} else {

return this.data;

}

}

}

这里是把上面泛型类进行了一个改造,这时泛型的具体类型只能是 Comparable 的实现类。

泛型的约束和局限性

不能实例化类型变量

还是以上面的泛型类为例。这时我们给它添加一个无参的构造方法。

public class GenericClass {

private T data;

public GenericClass() {

}

public T getData() {

return data;

}

public void setData(T data) {

this.data = data;

}

}

假如我们想要在构造方法中初始化 T 的实例是不允许的。编译器会直接报错:

0d312430eb60

静态域或静态方法不能引用类型变量

0d312430eb60

编译器会直接报错。这个问题的答案其实很简单:泛型的具体类型是在创建出具体的泛型类的对象的时候才确定的,而静态代码的执行是早于对象的创建的。那么虚拟机根本就不知道静态的 T 是什么。但如果是静态泛型方法则是可以的:

public static void print(E e) {

System.out.println(e.toString());

}

基本数据类型不能作为泛型

GenericClass genericClass1 = new GenericClass<>(); // 报错 基本类型不可以

GenericClass genericClass2 = new GenericClass<>(); // 需要用其包装类

不能使用 instanceof 关键字

通常我们需要判断一个对象是不是某种类型的时候,都会用 instanceof 关键字来判断。可是判断某个对象是不是某个泛型类的类型时却不可以。例如:

GenericClass genericFloat = new GenericClass<>();

if (genericFloat instanceof GenericClass){ // 这一行编译器会报错

}

不能实例化泛型数组

0d312430eb60

可以声明泛型数组,但是却不能实例化。这是 java 语法规定。是不是很奇葩?

泛型类不能继承 Exception 或 Throwable

0d312430eb60

非常简单粗暴,编译器直接提示泛型类不能派生自 Throwable。

不能捕获泛型类对象

0d312430eb60

但是下面这种写法确实允许的:

0d312430eb60

通配符

假设现在有两个如下类,且存在继承关系:

public class Animal {

}

public class Dog extends Animal{

}

Dog 是 Animal 的子类,那么问题来了:

GenericClass animalGeneric = new GenericClass<>();

GenericClass dogGeneric = new GenericClass<>();

请问GenericClass 和 GenericClass 之间存在继承关系吗?答案显然是否定的。但是泛型类可以继承或扩展其他泛型类,例如 List 和 ArrayList 之间的关系:

public class GenericClassChild extends GenericClass{

}

再假设现在有如下一个方法:

public static void set(GenericClass genericClass) {

}

那么下面两种调用可以吗?

set(animalGeneric); // 1

set(dogGeneric); // 2

很显然,第一种是肯定可以的。第二种就不可以了。于是为了解决这种问题就有了通配符的概念。

? extends

现在有如下4个类,且有如下继承关系:

0d312430eb60

假设现在有如下4个对象和对应的打印方法:

GenericClass fruit = new GenericClass<>();

GenericClass apple = new GenericClass<>();

GenericClass redApple = new GenericClass<>();

GenericClass orange = new GenericClass<>();

public static void printExtends(GenericClass extends Apple> genericClass) {

}

下面我们来看一下调用结果:

0d312430eb60

从上图中我们可以得知 ?extends 限定了传入参数的上边界。那这样使用有没有什么限制呢?

0d312430eb60

可以看到当我们尝试着设置数据的时候是不允许的,当我们要取数据的时候却只可以用基类 Fruit 去接收。其实这也很好解释。上面我们说了?extends 限定了上边界,也就是说当我们取数据的时候,不管这个时候这个对象里有什么,但是一定是 Fruit 的子类,根据多态的性质,可以用父类来接收子类。至于设置数据的时候为什么不可以也就很好解释了。因为编译器只知道传入的是 Fruit 的子类,而至于传入的具体是哪一个子类,编译器是不知道的。至此我们可以总结一下:?extends 限定了传入参数类型的上界,用于安全的访问数据

?super

GenericClass fruit = new GenericClass<>();

GenericClass apple = new GenericClass<>();

GenericClass redApple = new GenericClass<>();

GenericClass orange = new GenericClass<>();

public static void printSuper(GenericClass super Apple> genericClass) {

}

下面我们来看一下调用结果:

0d312430eb60

可以看到?super正好是限定了传入参数的下边界。下面我们来看一下有什么限制:

0d312430eb60

不是说 super 是限定了下边界吗?怎么在设置数据的时候父类 Fruit 反而不行了呢?而子类 RedApple 却可以呢?这是因为所有类的父类都是Object,而编译器无法确定传入的是什么类型,但是 Apple 和 Apple 的子类是可以安全的转型为Apple的,可以满足最下边界,所以可以安全传入。这也就是为什么最后获取的时候直接得到的是 Object ,而不是 Fruit 。至此我们可以总结一下:?super 限定了传入参数类型的下界,用于安全的设置数据

虚拟机是如何实现泛型的?

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值