1. 基本概念

什么是泛型?泛型用来做什么?
在有泛型类之前,程序员必须使用Object编写适用于多种类型的代码,这不仅繁琐,且很不安全(无类型推断来做编译前检测)。

泛型程序设计意味着编写的代码可以对多种不同类型的对象重用。例如,你不希望为收集一组String和File对象分别写两个类。实际上我们使用的ArrayList类就可完成这个工作。它就是一个泛型类。


2. 泛型类

泛型类(generic class)就是有一个或多个类型变量的类。泛型类相当于普通类的工厂。
简单示例:

public class Pair<T> {
    private T first;
    private T second;

    public Pair(T first, T second) {
        this.first = first;
        this.second = second;
    }

    SetterAndGetter.....
}

3. 泛型方法

简单例子

注意:类型变量T,放在修饰符(public static等)后面,返回类型前面,并用<>包裹。

实例1:

public class ArrayAlg {
    /**
     * @param a 为 varargs 是一个 T[] 数组
     */
    public static <T> T getMiddle(T... a){
        return a[a.length / 2];
    }

    public static void main(String[] args) {
        String middle = ArrayAlg.getMiddle("join", "Q.", "Public");
        System.out.println(middle);
    }

    /*
     * 输出  Q.
     */
}

泛型变量限定

实例2:
类型变量的限定。对类型变量加以约束。

public class ArrayAlg {

    /**
     * 返回一对值,一个最大值,一个最小值
     *
     * @param a 参数
     */
    public static <T extends Comparable> Pair<T> minMax(T[] a) {
        if (a == null || a.length == 0) {
            return null;
        }

        T min = a[0];
        T max = a[0];
        for (T t : a) {
            if (min.compareTo(t) > 0) {
                min = t;
            }
            if (max.compareTo(t) < 0) {
                max = t;
            }
        }
        return new Pair<>(min, max);
    }

    public static void main(String[] args) {

        LocalDate[] birthDays = {
                LocalDate.of(2906, 12, 9),
                LocalDate.of(1815, 12, 9),
                LocalDate.of(1903, 12, 9),
                LocalDate.of(1966, 12, 9),
                LocalDate.of(1906, 12, 9),
                LocalDate.of(1222, 12, 9)
        };

        Pair<LocalDate> mm = ArrayAlg.minMax(birthDays);
        System.out.println("min = " + mm.getFirst());
        System.out.println("max = " + mm.getSecond());
    }
}

输出:

min = 1222-12-09
max = 2906-12-09

4. 泛型代码及虚拟机

类型擦除

无论何时定义一个泛型类型,都会自动提供一个相应的原始类型(raw type)。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,并将其替换其限定类型(例如:Pair< T extends xxx > )。特别的,对于无限定的变量(例如:Pair)则替换为Object。

类型擦除后的结果后,结果就是一个普通的类,就好像Java语言中引入泛型之前实现的类一样。

转换泛型表达式

调用一个泛型方法调用时,如果擦除了返回类型(如将 T 擦除为 Object),编译器会插入强制类型转换。

Pair<Employee> buddies = .......;
Employee buddy = buddies.getFirst();
  • 对于原始方法Pair.getFirst进行调用
  • 将类型擦除后返回的Object强制转换为Employee类型

小总结

对于Java泛型的转换,需要记住以下几个事实:

  • 虚拟机中没有泛型,只有普通的类和方法。
  • 所有的类型参数都会替换为他们的限定类型。
  • 为保持类型安全性,必要时会插入强制类型转换。

5. 泛型类型的继承规则(重要)

泛型类型的继承规则.png
简而言之,不管类型S和类型T有什么关系,List<S>List<T> 不会有任何关系


6. 通配符类型(重要)

如同上一节介绍的一样,严格的泛型类型系统的使用是很复杂的,由此Java设计者引入了一个新的概念,通配符。在通配符类型中,允许类型参数发生变化。

注意:?T的使用位置不完全相同!用途更是不同!T用于定义泛型。用于方便泛型使用。而且仅用于方法参数处。

通配符的子类限定

例如:
通配符类型 List< ? extends Person> ,表示任何泛型List类型,他的类型类型参数是Person的子类。如List<Student>List<Teacher>。但不能是List<String>
通配符子类限定.png

通配符的超类型限定

这是通配符附加的能力,也是相较于普通类型变量限定(T extends xxx)强大的地方。
例如:
List<? super Student>List< ? super T>
通配符超类限定.png

无限定通配符

无限定的通配符:List<?>看起来和原始的不加限定的List<T>没什么区别。
List<?>是一个脆弱的类型。它对于很多简单的操作非常有用。如果方法中并没有用到实际的类型,那么使用?在一定程度可以简化代码。

最后修改:2020 年 12 月 10 日
如果觉得我的文章对你有用,请随意赞赏