美文网首页
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 限定了传入参数类型的下界,用于安全的设置数据
    虚拟机是如何实现泛型的?

    相关文章

      网友评论

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

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