• java_泛型解析


    泛型

    在java中,泛型是无处不在,当然,泛型的基本思维和概念是简单,可是在他后面有容器类,是非常重要的

    1、 基本概念和原理

    1. 泛型 : 就是广泛的类型。类,接口,和方法代码 可以应用于非常广泛的类型,代码与它们能够操作的数据类型不再绑定在一起,同一套代码可以用于多种数据类型

    泛型类

    1. 代码展现

    public class Pair<T>{
        T first;
        T second;
        public Pair(T first , T second){
            this.first = first;
            this.second = second;
        }
        public T getFirst(){
            return first;
        }
        public T getScond(){
            return second;
        }
    }
    

    2. 代码的类型就是T ,

    3. T 表示的就是**类型参数 ** ,泛型就是类型参数化,处理的数据类型不是固定的,而是可以作为参数传入

    public static void main(String[] args){
        Pair<Integer> minmax = new Pair<Integer>(1,100);
        Integer min = minmax.getFirst();
        Integet max = minmax.getSecond();
    }
    

    4. 在<> 中就是要传入的参数,

    5. 在泛型类中,类型参数可以多个,只需要用逗号隔开。

    public class Pair<U,V>{
        U first;
        V second;
        public Pair(U first , V second){
            this.first = first;
            this.second = second;
        }
        public U getFirst(){
            return first;
        }
        public V getScond(){
            return second;
        }
    }
    

    6. 使用多参数的泛型类

    Pair<Integer,String> minmax = new Pair<>(1,"小我")

    7. 为什么 不是用Object 而是使用泛型呢,两者有什么区别

    • 在java中 有 java编译器和java虚拟机 ,编译器将java 源代码 转换为 .class文件,虚拟机加载并运行.class文件。
    • 对于泛型类,java编译器会将泛型代码转换为普通的非泛型代码,就像上面的普通Pair类代码及其使用代码一样,将类型参数T擦除,替换为Object,插入必要的强制类型转换。
    • java虚拟机实际执行的时候,它 是不知道泛型这回事的,只知道普通的类和代码

    8. 泛型的好处

    1. 更好的安全性
    2. 更好的可读性
    3. 如果使用Object ,代码写错,不会有错误提示,但是在运行的时候,会报错

    容器类

    1. 什么是容器类

    1. 简单说: 就是容纳并管理多项数据的类,数组就是用来管理多项数据的,但数组有很多限制,比如长度,插入,删除,效率比较低

    2. 就比如,前面实现的动态数组,就是一个容器,,

      动态数组博客地址

    3. 作为一个容器类,他容纳的数据类型是作为参数传递过来的

    泛型方法

    1. 栗子

    public static <T> int indexOf(T[] arr , T elm){
        for(int i=0; i< arr.length;i++){
            if(arr[i].equals(elm)){
                return i;
            }
        }
        return -1;
    }
    
    1. 在调用这个寻找的方法,不需要指定类型

    泛型接口

    1. 栗子

    public interface Comparable<T>{
        public int compareTo(T o);
    }
    public interface Comparator<T>{
        intt compare(T o1,T o2);
        boolean equals(Object obj);
    }
    

    2. 实际使用的注意

    1. 实现接口,应该指定具体的类型,。

    类型参数的限定

    在java中,参数必须为给定的上界类型或其子类型,这个限定是通过extends 关键字来表示的,

    1. 上界为某个具体类

    • 比如 上面的Pair类,可以定义一个子类NuymberPair ,限定两个类型参数必须为Number,

    • 代码:

      public class NumberPair<U extends Number,V extends Number> extends Pair<U,V>{
          public NumberPair(U first , V second){
              super(first,second);
          }
      }
      
    • 如果限制了,,就只能使用这个一个类型。

    • 指定 边界后,类型擦除时,就不会转换为object 了,而是转换为他的边界类型

    2. 上界为某个接口

    • 比如算法中使用的Comparable 接口

      public static <T extends Comoparable> T max(T[] arr)}{
          T max = arr[0];
          for(int i=1;i< arr.length; i++){
              if(arr[i].compareTo(max) > 0){
                  max = arr[i];
              }
          }
          return max;
      }
      
    • max方法 实现类型参数 设置了一个边界 Comparable ,T 必须实现Comparable 接口 ,

    • 但是 上述 ----- 会显示 警告信息: 因为Comparable 是一个泛型接口,他也需要一个类型接口

      public static <T extends Comparable<T>> T max(T[] arr){}
      
    • 对于 上面的 令人费解的语法形式, 叫做 递归类型限制 可以解读 -———— T 表示 一种数据类型,必须实现Comparable接口,且必须可以与相同类型的元素进行比较。

    3. 上界为其他类型参数

    上面的限定都是指定一个明确的类或接口,java支持一个类型参数以另一个类型参数作为上界。

    • 根据上面的类,添加一个方法

      public void addAll(DynamicArray<E> c){
          for(int i=0; i< c.size ; i++){
              add(c.get(i))
          }
      }
      
    • 如果 像上面写的,发现会有一些局限性,,,

      DynamicArray<Number> numbers = new DynamicArray<>();
      DynamicArray<Integer> ints = new DynamicArray<>();
      ints.add(100);
      numbers.addAll(ints);//会显示报错
      
      • 将子类的容器,加入父类的容器,会报错, ,破坏了类型安全的保证

      • 原因是 : DynamicArray 并不是 DynamicArray 的 子类,虽然Integer 是Number的子类,但不是相关的,所以不能辅助,,这点很重要

      • 改变代码

        public <T extends E> void addAll(DynamicArray<E> c){
            for(int i=0; i< c.size ; i++){
                add(c.get(i))
            }
        }
        
      • E 是 DynamicArray 的类型参数,T是 addAll 的类型参数,T 的上界限定为 E ,这样就可以了

    总结

    1. 泛型 是计算机 程序中一种重要的思维方式,他将数据结构 和算法 与 数据类型 相分离,让同一套数据结构和算法 能够应用于各种数据类型,而且保证类型安全,提供可读性。

    2、解析通配符

    1. 更简洁的参数类型限定

    1. 将上面的add 方法替换

      public void addAll(DynamicArray<? extends E> c){
          for(int i=0; i< c.size ; i++){
              add(c.get(i))
          }
      }
      
    2. 这个方法 没有定义类型参数,c 的类型 是DynamicArray<? extends E> ,? 表示通配符,<? extends E> 表示限定通配符

    3. 为什么 两种不同的写法,,但是效果是一样的

      1. 用于定义类型参数,它声明一个类型参数T ,可放在泛型类定义中类名后面,泛型方法返回值前面
      2. <? extends E> 用于==实例化类型==参数,它用于实例化泛型变量中的类型参数,只是这个具体类型参数是未知的,只知道他是E或 E的某个子类型

    2. 理解通配符

    1. 无限定通配符 : DynamicArray<?>

    2. 上面的indexOf 方法就可以这样使用

      public static int indexOF(DynamicArray<?> arr, object elm);
      or
      public static <T> indexOF(DynamicArray<T> arr, object elm);
      
    3. 但是上面的 两种通配符 都有一个重要的限制 : 只能读,不能写

    DynamicArray<Integer> ints = new DynamicArray<>();
    DynamicArray<? extends Number> numbers = ints;
    Integer a = 200;
    numbers.add(a);//错误
    numbers.add((Number)a);//错误
    numbers.add((Object)a);//错误
    
    • 因为无法判断类型 ,所以直接禁止掉了
    1. 但是也不是 什么时候,都可以使用无限定通配符

      1. 如果参数类型之间有依赖关系,也只能使用类型参数
      2. 如果 返回值 依赖于 类型参数,也不能用通配符

    总结

    • 通配符形式都可以用类型参数的形式替代,通配符能做的,类型参数也能做
    • 通配符可以减少类型参数,形式更加的简单,所以能用就用
    • 如果类型参数之间有依赖关系,或者返回值依赖类型参数,或者需要写操作,则不能使用通配符
    • 通配符和类型参数往往配合使用

    3. 超类型通配符

    1. 写法为<? super E> 称为超类型通配符,表示 E的某个父类型,

    2. 例子代码

      public void copyTo(DynamicArray<E> dest){
          for(int i=0;i<size;i++){
              dest.add(get(i));
          }
      }
      
    3. 但是 子 将Integer对象加入 Number容器 会编译错误, 但是可以通过 超类型通配符解决

    public void copyTo(DynamicArray<? super E> dest){
        for(int i=0;i<size;i++){
            dest.add(get(i));
        }
    }
    
    1. 还有另一个场合就是 — Comparable/Comparator 接口

      1. 如果当 base 类 实现 Comparable接口 , child没有 实现,,那么当他们根据 实例变量 进行比较,,使用前面所说的max方法比较,会报错,,

      2. 因为, child 实现的不是Comparable 而是 Comparable ,

      3. 所以修改max方法

        public static <T extends Comoparable<? super T>> T max(T[] arr)}
        
    2. 通配符比较

      1. <? super E> 灵活,或比较,让对象可以写入父类型的容器,使父类型容器的比较方法 应用于子类对象,它不能 被类型 参数形式替代
      2. <?> and <? extends E> 用于灵活读取,使方法可以读取E 或E的任意子类型的容器对象,他们可以用类型参数代替,通配符更加方便
  • 相关阅读:
    2017 ACM-ICPC 亚洲区(南宁赛区)网络赛 (B,F,L,M)
    Coderfroces 862 C. Mahmoud and Ehab and the xor
    [CQOI 2015] 任务查询系统
    [POI 2014] Couriers
    [POJ 2104] K-th Number
    [模板] 可持久化数组
    [AHOI 2006] 上学路线
    [SCOI2009] 生日礼物
    [BZOJ 3436] 小K的农场
    [USACO2007 Demo] Cow Acrobats
  • 原文地址:https://www.cnblogs.com/YJBlog/p/11815500.html
Copyright © 2020-2023  润新知