java_泛型解析

版权声明:Firewine 的博客,想要转载请标明出处 https://blog.csdn.net/xyjworkgame/article/details/89713640

泛型

在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的任意子类型的容器对象,他们可以用类型参数代替,通配符更加方便

猜你喜欢

转载自blog.csdn.net/xyjworkgame/article/details/89713640