Java泛型限定 extends和super

Java泛型限定(bounded type parameters)是一种特殊的泛型类型参数限制,通过使用限定,可以指定类型参数必须是某种特定类型或其子类型。

在泛型定义中使用 extends 关键字来实现泛型限定,例如:

public class MyClass<T extends Number> {
    // ...
}

在这个示例中,类 MyClass 定义了一个泛型类型参数 T,并通过 extends 关键字限定 T 必须是 Number 类型或 Number 的子类型。

我们接下来详细看一下泛型中extends和super的使用。

Java 泛型通配符是用来表示不确定的泛型类型,使用 ? 代替具体的类型参数。通配符有两种形式,分别是上界通配符和下界通配符。

1、上界通配符
上界通配符使用 extends 关键字,用于限制通配符的类型参数必须是指定类型或其子类。例如:

public static void printList(List<? extends Number> list) {
    for (Number n : list) {
        System.out.print(n + " ");
    }
}

在这个示例中,通配符 ? 被限定为 Number 或其子类,表示可以接受任何类型的 Number 类型,如 Integer、Double、Float 等。
2、下界通配符
下界通配符使用 super 关键字,用于限制通配符的类型参数必须是指定类型或其父类。例如:

public static void addNumbers(List<? super Integer> list) {
    list.add(10);
}

在这个示例中,通配符 ? 被限定为 Integer 或其父类,表示可以接受任何类型的 Integer 类型,如 Number、Object 等。

下面是一个使用泛型通配符的示例代码:

public static void printList(List<? extends Number> list) {
    for (Number n : list) {
        System.out.print(n + " ");
    }
}

public static void addNumbers(List<? super Integer> list) {
    list.add(10);
}

public static void main(String[] args) {
    List<Integer> integerList = new ArrayList<>();
    integerList.add(1);
    integerList.add(2);
    integerList.add(3);
    printList(integerList);

    List<Number> numberList = new ArrayList<>();
    numberList.add(1.23);
    numberList.add(4.56);
    numberList.add(7.89);
    printList(numberList);

    List<Object> objectList = new ArrayList<>();
    objectList.add("hello");
    objectList.add("world");
    printList(objectList); // 编译错误

    List<Integer> intList = new ArrayList<>();
    addNumbers(intList);

    List<Number> numList = new ArrayList<>();
    addNumbers(numList);
}

在这个示例中,首先定义了一个 printList() 方法,使用 extends 通配符限定参数类型为 Number 或其子类型。然后,定义了一个 addNumbers() 方法,使用 super 通配符限定参数类型为 Integer 或其父类型。

在 main() 方法中,首先创建一个 Integer 类型的列表 integerList,添加三个整数。然后,调用 printList() 方法,输出整数列表的内容。接着,创建一个 Number 类型的列表 numberList,添加三个浮点数,再次调用 printList() 方法,输出浮点数列表的内容。注意,由于 Object 不是 Number 的子类,因此无法将 Object 列表作为参数传递给 printList() 方法。接下来,创建一个整数列表 intList,调用 addNumbers() 方法,添加一个整数。最后,创建一个 Number 类型的列表 numList,调用 addNumbers() 方法,添加一个整数。

可以看到,通配符的上界和下界可以很好地限制泛型类型的范围,提高了代码的类型安全性。但是,需要注意的是,使用通配符会导致泛型类型丢失,无法对列表进行添加元素的操作,只能对列表进行读取元素的操作。因此,在编写代码时需要根据具体的需求来选择是否使用泛型通配符。

下面再举一个使用泛型通配符的示例,来说明上界通配符和下界通配符的使用:

import java.util.ArrayList;
import java.util.List;

public class Test {
    public static void printList(List<? extends Number> list) {
        for (Number n : list) {
            System.out.print(n + " ");
        }
        System.out.println();
    }

    public static void addNumbers(List<? super Integer> list) {
        list.add(10);
        list.add(20);
        list.add(30);
        System.out.println(list);
    }

    public static void main(String[] args) {
        List<Integer> integerList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        printList(integerList);

        List<Double> doubleList = new ArrayList<>();
        doubleList.add(1.23);
        doubleList.add(4.56);
        doubleList.add(7.89);
        printList(doubleList);

        List<Number> numberList = new ArrayList<>();
        numberList.add(1.23);
        numberList.add(4.56);
        numberList.add(7.89);
        printList(numberList);

        List<Object> objectList = new ArrayList<>();
        objectList.add("hello");
        objectList.add("world");
        // 编译错误,String 不是 Number 的子类
        //printList(objectList);

        List<Integer> intList = new ArrayList<>();
        addNumbers(intList);

        List<Number> numList = new ArrayList<>();
        addNumbers(numList);
    }
}

在这个示例中,printList() 方法和 addNumbers() 方法的作用和上面的示例一样。在 main() 方法中,首先创建一个 Integer 类型的列表 integerList,添加三个整数。然后,调用 printList() 方法,输出整数列表的内容。接着,创建一个 Double 类型的列表 doubleList,添加三个浮点数,再次调用 printList() 方法,输出浮点数列表的内容。注意,由于 Double 不是 Number 的子类,因此无法将 Double 列表作为参数传递给 printList() 方法。然后,创建一个 Number 类型的列表 numberList,添加三个浮点数,再次调用 printList() 方法,输出浮点数列表的内容。接下来,创建一个 Object 类型的列表 objectList,添加两个字符串,这里会出现编译错误,因为 String 不是 Number 的子类,无法将 Object 列表作为参数传递给 printList() 方法。最后,创建一个整数列表 intList,调用 addNumbers() 方法,添加三个整数。接着,创建一个 Number 类型的列表 numList,调用 addNumbers() 方法,添加三个整数。

需要注意的是,对于泛型通配符的使用,可以总结如下几点:
1、使用上界通配符可以实现读取操作,使用下界通配符可以实现添加操作。
2、上界通配符和下界通配符都可以和泛型类型参数一起使用,例如 List 和 List。
3、通配符不支持类型参数的方法调用,因为通配符只是一个占位符,无法确定具体的类型参数。
4、在使用泛型通配符时,需要根据实际需求选择上界通配符还是下界通配符,以及是否需要使用通配符。
总之,泛型通配符是 Java 泛型中重要的概念,可以很好地限制泛型类型的范围,提高代码的类型安全性。在编写代码时,需要根据具体的需求选择使用泛型通配符还是具体的类型参数,以及使用上界通配符还是下界通配符。

最后,要强调的是:“使用通配符会导致泛型类型丢失,无法对列表进行添加元素的操作”,怎么理解呢?

使用通配符会使得泛型类型变得不确定,编译器无法确定具体的泛型类型,从而导致无法进行添加元素的操作。

例如,假设我们有一个方法:

printList(List<Object> list)

这个方法接受一个 List 类型的参数,并打印列表中的所有元素。如果我们调用这个方法时传入一个 List 类型的参数,编译器会报错,因为 List 类型和 List。

为了解决这个问题,我们可以使用通配符 ? 来定义一个不确定的类型,即 printList(List list)。这样做可以接受任何 List 类型的参数,因为 ? extends Object 表示可以是任何继承自 Object 类型的子类。但是,使用通配符也会导致泛型类型的丢失,编译器无法确定具体的类型参数,从而无法进行添加元素的操作。

例如,如果我们尝试在 printList 方法中添加一个元素,比如 list.add(new Object()),编译器会报错,因为编译器无法确定列表中的元素类型是什么,不能保证添加的元素是合法的。因此,使用通配符会使得泛型类型变得不确定,无法进行添加元素的操作。