Java 泛型

134 阅读3分钟

泛型

泛型:是JDK5中引入的特性,可以在编译阶段约束操作的数据类型,并进行检查

泛型的格式:<数据类型>

注意:泛型只能支持引用数据类型

  • 在没有泛型的时候,集合如何存储数据?
    • 如果我们没有给集合指定类型,默认认为所有的数据包类型都是Object类型,此时可以往集合添加任意的数据类型
    • 坏处:我们在获取数据的时候,无法使用它的特有行为
  • 泛型的好处
    • 统一数据类型
    • 把运行时期的问题提前到了编译期间,避免了强制类型转换可能出现的异常,因为在编译阶段类型就能确定下来

Java中的泛型是伪泛型

  • 当数据添加到集合中的时候,只是检查了一下数据是否为该限定类型
  • 当数据被添加到集合中后,这些数据又被强转为了Object类型
  • 当我们再从集合中获取数据时,这些数据才会被强转回原数据类型

在代码中的体现

泛型的擦除

  • Java文件中
ArrayList<String> list = new ArrayList<>();
  • java文件编译成class字节码文件后
ArrayList list = new ArrayList();

泛型的细节

  • 泛型中不能写基本数据类型
    • 因为存入集合中后,所有数据都会被强转为Object类型,而只有引用数据类型能够被强转为Object类型
  • 指定泛型的具体类型后,传递数据时,可以传入该类类型或者其子类类型
  • 如果不写泛型,类型默认是Object

泛型类

使用场景:当一个类中,每个变量的数据类型不确定时,就可以定义带有泛型的类

格式

修饰符 class 类名<类型> {
    
}

举例

public class ArrayList<E> {
    
}

此处E可以理解为变量,但是不是用来记录当数据的,而是记录数据的类型,可以写成:T、E、K、V等

public class MyArrayList<E> {
    Object[] obj = new Object[10];
    int size;
    
    public boolean add(E e) {
        obj[size] = e;
        size++;
        return true;
    }
    
    public E get(inr index) {
        return (E)obj[index];
    }
    
    @Override
    public String toString() {}
    return Arrays.toString(obj);
}

泛型方法

  • 方法中形参类型不确定时
    • 方案1:可以使用类名后面定义的泛型
      • 所有方法都能用
    • 方案2:在方法申明上定义自己的泛型
      • 只有本方法能用

格式

修饰符<类型> 返回值类型 方法名(类型 变量名) {
    
}

举例

public<T> void show(T t) {
    
}

此处T可以理解为变量,但是不是用来记录当数据的,而是记录数据的类型,可以写成:T、E、K、V等

public class ListUtil {
    private ListUtil() {}
    
    public static<E> void addAll(ArrayList<E> list, E e1, E e2, E e3) {
        list.add(e1);
        list.add(e2);
        list.add(e3);
    }
    
    //可变参数
    public static<E> void addAll(ArrayList<E> list, E...e) {
        for (E element : e) {
            list.add(element);
        }
    }
}

泛型接口

格式

修饰符 interface 接口名<类型> {
    
}

举例

public interface List<E> {
    
}
  • 如何使用一个带泛型的接口

    • 实现类给出具体类型
    public class MyArrayList implements List<String> {}
    
    • 实现类延续泛型,创建对象时再确定
    public class MyArrayList<E> implements List<E> {}
    

泛型的继承和通配符

  • 泛型不具备继承性,但是数据具备继承性
class Ye{}
class Fu extends Ye{}
class Zi extends Fu{}

public static void method(ArrayList<Ye> list) {
    
}


ArrayList<Ye> list1 = new ArrayList<>();
ArrayList<Fu> list2 = new ArrayList<>();
ArrayList<Zi> list3 = new ArrayList<>();

//泛型不具备继承性
method(list1);
method(list2);  //报错
method(list3);  //报错
//此时,泛型里面写的是什么类型的数据,那么只能传递什么类型的数据

//数据具备继承性
list1.add(new Ye());
list1.add(new Fu());
list1.add(new Zi());
  • 解决方案1

    • 使用泛型方法
    public static<E> void method(ArrayList<E> list) {
        
    }
    
    method(list1);
    method(list2);  
    method(list3);
    
    • 弊端:此时可以传递任意的数据类型
  • 解决方案2

    • 需求:虽然不确定数据类型,但希望只能传递Ye、Fu、Zi
    • 使用泛型的通配符:可以限定类型的范围
      • ?也表示不确定的类型,但可以进行类型的限定
      • ? extends E:表示可以传递E或者E所有的子类类型
      • ? super E:表示可以传递E或者E所有的父类类型
    public static void method(ArrayList<? extends Ye> list) {
        
    }
    
    public static void method(ArrayList<? super Zi> list) {
        
    }
    
    method(list1);
    method(list2);  
    method(list3);
    
    • 应用场景

    1. 如果我们再定义类、方法、接口的时候,如果类型确定,就可以定义泛型类、泛型方法、泛型接口
    2. 如果类型不确定,但是能知道以后只能传递某个继承体系中的,就可以使用泛型的通配符