美文网首页
Android基础-Java泛型

Android基础-Java泛型

作者: echoSuny | 来源:发表于2021-03-03 15:20 被阅读0次
泛型的定义

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

为什么需要泛型?

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

    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<String> list = new ArrayList<>();
        list.add("element0");
        list.add("element1");
        list.add(100); // 编译器会提示此行代码报错

并且加了泛型之后在取值的时候也不需要强制类型转换了。
综上所述:1.适用于多种数据类型执行相同的代码。2.在编译期间就发现数据类型安全问题。这也就是我们为什么要使用泛型的原因。

泛型的使用
  • 泛型类
public class GenericClass<T> {
    
    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> {

    T next();
}

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

public class GenericInterfaceImpl1 implements GenericInterface<String>{
    @Override
    public String next() {
        return null;
    }
}
public class GenericInterfaceImpl2<T> implements GenericInterface<T>{

    @Override
    public T next() {
        return null;
    }
}

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

  • 泛型方法
    public <T> T genericMethod(T... t) {
        return t[t.length / 2];
    }

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

类型变量的限定-用于方法上
    public static <T extends Comparable> 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 extends View & Comparable & Serializable> T min(T a, T b) {
        if (a.compareTo(b) > 0) return a;else return b;
    }

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

类型变量的限定-用于类上
public class GenericClass<T extends Comparable> {

    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<T> {

    private T data;

    public GenericClass() {

    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

}

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


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



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

    public static <E> void print(E e) {
        System.out.println(e.toString());
    }
  • 基本数据类型不能作为泛型
        GenericClass<double> genericClass1 = new GenericClass<>(); // 报错 基本类型不可以
        
        GenericClass<Double> genericClass2 = new GenericClass<>(); // 需要用其包装类
  • 不能使用 instanceof 关键字
    通常我们需要判断一个对象是不是某种类型的时候,都会用 instanceof 关键字来判断。可是判断某个对象是不是某个泛型类的类型时却不可以。例如:
        GenericClass<Float> genericFloat = new GenericClass<>();

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

        }
  • 不能实例化泛型数组



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

  • 泛型类不能继承 Exception 或 Throwable



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

  • 不能捕获泛型类对象



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


通配符

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

public class Animal {
    
}

public class Dog extends Animal{
        
}

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

        GenericClass<Animal> animalGeneric = new GenericClass<>();
        GenericClass<Dog> dogGeneric = new GenericClass<>();

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

public class GenericClassChild<T> extends GenericClass<T>{
    
}

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

    public static <T> void set(GenericClass<Animal> genericClass) {

    }

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

        set(animalGeneric); // 1
        set(dogGeneric); // 2

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

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



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

        GenericClass<Fruit> fruit = new GenericClass<>();
        GenericClass<Apple> apple = new GenericClass<>();
        GenericClass<RedApple> redApple = new GenericClass<>();
        GenericClass<Orange> orange = new GenericClass<>();

        public static <T> void printExtends(GenericClass<? extends Apple> genericClass) {
      
        }

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


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

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

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

        }

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


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


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

相关文章

  • Java 泛型

    导读 移动开发知识体系总章(Java基础、Android、Flutter) 为什么会有泛型? 泛型类 泛型方法 泛...

  • Android中泛型的应用(二)

    背景感谢并参考[怪盗kidou]的《搞定Gson泛型封装》 。上一篇《 Java基础--Android中泛型的应用...

  • Android-基本的MVP结构的模板工程(泛型,Rx通用请求,

    关键实践链接: Android-Java-泛型-总得有个开始的篇 Android-Java-泛型-官方文档走一走 ...

  • Android基础-Java泛型

    泛型的定义 泛型:参数化的类型。很简单的一句话,那么什么叫做“参数化的类型”呢?。。。。。 为什么需要泛型? 假设...

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

  • Java基础之泛型

    Java基础之泛型 泛型基本介绍 简单介绍在jdk1.6之前,还没有使用到泛型,Java类型分为原始类型、复杂类型...

  • spring 泛型处理

    java 泛型基础 泛型类型:泛型类型是在类型上参数化的泛型类或接口 泛型使用场景编译时前类型检查。定义为 Col...

  • Java泛型基础

    Java泛型基础 1. 认识泛型 泛型是在JDK1.5之后增加的新功能. 泛型可以解决数据的安全性问题, 主要的原...

  • 一文带你认识Java泛型基础

    Java泛型基础 1. 认识泛型 泛型是在JDK1.5之后增加的新功能. 泛型可以解决数据的安全性问题, 主要的原...

  • 第二十八课:泛型

    泛型出现之前 泛型出现之后 Java深度历险(五)——Java泛型

网友评论

      本文标题:Android基础-Java泛型

      本文链接:https://www.haomeiwen.com/subject/xlhzfltx.html