【Java SE】十八、泛型

Java 泛型(generics)是 JDK 5 中引入的一个新特性,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或某个方法的返回值及参数类型,这个泛型类型将在使用时确定。

使用泛型

我们可以在集合中使用泛型,如下:

public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<Integer>();
    list.add(123);
 // list.add("fuck"); 编译不通过
    
    HashMap<String,Integer> map = new HashMap<>(); // 双参泛型,其实后面的泛型可省略,反正和前面一样
    map.put("A", 1);
 // map.put(2, "B"); 编译不通过
    Set<Entry<String,Integer>> entry = map.entrySet(); // 因为返回值也是泛型嵌套结构,所以也要用泛型创建引用
}

注意事项:

  • 集合接口或集合类在 JDK 5.0 时都修改为带泛型结构,所以在实例化集合类时,需指明具体的泛型类型
  • 指明后,在集合类或接口中凡是定义类或接口时,内部结构用到泛型的位置都指定为实例化所传入的类型
  • 泛型类型必须是类,不能是基本数据类型,但可以用包装类代替,如上面的 Integer
  • 若没有指明泛型类型,则默认类型为 Object
  • 泛型不同的引用不能相互赋值,如 ArrayList<Integer>ArrayList<String>

自定义泛型

自定义泛型的过程其实很简单,只需要把不确定的类型用泛型类型代替即可,示例如下:

public class Test<T> { // 第一种用法:自定义泛型类
    T unknowData; // 用泛型类型声明未知变量
    static int[] arr = {1, 2, 3, 4};
    
    public Test(T unknowData) { // 用泛型类型声明形参,这不是泛型方法
        this.unknowData = unknowData;
        List<Integer> list = Test2(arr); // 指明泛型方法的泛型类型
    }
    // 第三种用法:自定义泛型方法
    public static <E> List<E> Test2(E[] arr) {...} // 其泛型类型与类的无关,所以可以是静态的
}
public interface Test1<T>; // 第二种用法:自定义泛型接口

注意事项:

  • 异常类和静态方法中不能使用泛型类型,后者是因为还没实例化传入类的泛型类型,但是泛型方法可以是静态的
  • 不能直接使用泛型类型创建数组,只能 T[] arr = (T[]) new Object[10] 这样来间接创建

泛型的继承

泛型的继承情况有点复杂,这里只讲解两点可能会出现的情况。

泛型的子父类关系

具体分为两种情况:G<A> & G<B>A<G> & B<G>

public void test1() {
    // 虽然类A是类B的父类,但是G<A>和G<B>二者不具备子父类关系,二者是并列关系
    List<Object> list1 = null;
    List<String> list2 = null;
 // list1 = list2; 编译不通过
    // 如果类A是类B的父类,则A<G>就是B<G>的父类,二者是父子关系
    List<String> list3 = null;
    ArrayList<String> list4 = null;
    list3 = list4; // 编译通过
}

注:情况一也对应了注意事项中所说的:泛型不同的引用不能相互赋值

泛型的保留

在父类使用泛型的情况下,子类在继承时可以选择全保留部分保留父类的泛型或是重定义泛型

class Father<T1,T2> {}

// 子类不保留父类的泛型,直接指明父类的泛型类型就可以保证子类继承的是已经确定的类型而不是泛型
class Son1 extends Father {} // 默认Object类型

class Son2 extends Father<Integer,String> {} // 具体类型

// 子类保留父类的泛型,父类的泛型类型是否指明可决定子类是否继承泛型
class Son3<T1,T2> extends Father<T1,T2> {} // 全保留

class Son4<T2> extends Father<Integer,T2> {} // 部分保留

通配符

在上文我们讲到泛型不同的引用不具有父子关系,这导致值传递时无法使用多态性,我们不得不多写几个方法重载,那样十分繁琐。因此 Java 提供了通配符 ? 来作为两者的共同“父类”来实现多态性。

public void test(){
    List<Object> list1 = null;
    List<String> list2 = null; 
    List<?> list = null;
    
    // 作为两者的共同“父类”
    list = list1; // 编译通过
    list = list2; // 编译通过
    
    // 对于List<?>,除了null以外,不能向其内部添加其他数据
 // list.add("shit"); 编译不通过
    
    // 但可以读取数据且只能为Object
    Object o = new list.get(0); // 编译通过
}

Java 还允许我们对通配符加上限制条件,有三种用法:<? extends 类><? super 类><? extends Comparable>

// Person是Student的父类
public void test() {
    List<? extends Person> list1 = null; // (-∞, Person],读取数据可以用Person作引用
    List<? super Person> list2 = null; // [Person, +∞),读取数据只能为Object
    List<Student> list3 = null;
    List<Person> list4 = null;
    List<Object> list5 = null;

    list1 = list3;
	list1 = list4;
 // list1 = list5; 编译不通过
 // list2 = list3; 编译不通过
	list2 = list4;
	list2 = list5;
	// 以下为写入操作的差异
 // list1.add(new Student()); 编译不通过
    list2.add(new Person()); 
	list2.add(new Student());
}

第三种用法只允许泛型为实现 Comparable 接口的实现类的引用调用,这里仅说明就不展示了。