扒一拔:Java 中的泛型(一) (3)

我们可以看到, 泛型方法 addBox 中定义了一个类型参数 U, 在泛型方法的调用时, Java 编译器可以推断出该类型参数。 因此, 很多时候, 我们不需要指定他们。

如上面的例子, 我们可以显示的指出

BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);

也可以省略, 这样, Java 编译器可以从方法参数中推断出

BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);

由于方法参数是 Integer, 因此, 可以推断出类型参数就是 Integer。

5.2 泛型类的类型推断和实例化

这是我们最常用到的类型推断了: 将构造函数中的类型参数替换成<>(该符号被称为“菱形(The diamond)”), 编译器可以从上下文中推断出该类型参数。

比如说, 正常情况先, 我们是这样子声明的

Map<String, List<String>> myMap = new HashMap<String, List<String>>();

但是, 实际上, 构造函数的类型参数是可以推断出来的。 因此, 这样子写即可

Map<String, List<String>> myMap = new HashMap<>();

但是, 不能将 <> 去掉, 否则编译器会报警告。

Map<String, List<String>> myMap = new HashMap(); // 警告

5.3 类的类型推断和构造函数

在泛型类和非泛型类中, 构造函数都是可以声明自己的类型参数的。

class MyClass<X> { <T> MyClass(T t) { // ... } public static void main(String[] args) { MyClass<Integer> myObject = new MyClass<>(""); } }

在以上代码 main 函数中, X 对应的类型是 Integer, 而 T 对应的类型是 String。

那么, 菱形 <> 对应的是 X 还是 T 呢?

在 Java SE 7 之前, 其对应的是构造函数的类型参数。 而在 Java SE 7及以后, 其对应的是类的类型参数。

也就是说, 如果类不是泛型, 则代码是这样子写的

class MyClass{ <T> MyClass(T t) { // ... } public static void main(String[] args) { MyClass myObject = new MyClass(""); } }

T 的实际类型, 编译器根据方法的参数推断出来。

5.4 类型推断和目标类型

Java 编译器利用目标类型来推断泛型方法调用的类型参数。 表达式的目标类型就是 Java 编译器所期望的数据类型, 根据该数据类型, 我们可以推断出泛型方法的类型。

以 Collections 中的方法为例

static <T> List<T> emptyList();

我们在赋值时, 是这样子

List<String> listOne = Collections.emptyList();

该表达式想要得到 List 的实例, 那么, 该数据类型就是目标类型。 由于 emptyList 的返回值是 List, 因此, 编译器就推断, T对应的实际类型就是 String。

当然, 我们也可以显示的指定该类型参数

List<String> listOne = Collections.<String>emptyList(); 6 通配符

在泛型中, 使用 ? 作为通配符, 其代表的是未知的类型。

6.1 设定通配符的下限

有时候, 我们想写一个方法, 它可以传递 List, List和List。 此时, 可以使用通配符来帮助我们了。

设定通配符的上限

使用?, 其后跟随着 extends, 再后面是 BundingType(即上边界)

<? extends BundingType>

示例

class MyClass{ public static void process(List<? extends Number> list) { for (Number elem : list) { System.out.println(elem.getClass().getName()); } } public static void main(String[] args) { List<Integer> integers = new LinkedList<>(Arrays.asList(1)); List<Double> doubles = new LinkedList<>(Arrays.asList(1.0)); List<Number> numbers = new LinkedList<>(Arrays.asList(1)); process(integers); process(doubles); process(numbers); } }

输出

java.lang.Integer java.lang.Double java.lang.Integer

也就是说, 我们通过通配符, 可以将List, List和List作为参数传递到同一个函数中。

6.2 设定通配符的下限

上限通配符是限定了参数的类型是指定的类型或者是其子类, 使用 extends 来进行。

而下限通配符, 使用的是 super 关键字, 限定了未知的类型是指定的类型或者其父类。

设定通配符的下限

<? super bundingType>

在 ? 后跟着 super, 在跟上对应的边界类型。

示例

public static void addNumbers(List<? super Integer> list) { for (int i = 1; i <= 10; i++) { list.add(i); } }

对于该方法, 由于我们是要将整型添加到列表中, 因此, 需要传入的列表必须是整型或者其父类。

6.3 未限定的通配符

当然, 我们也可以使用未限定的通配符。 如List<?>, 表示未知类型的列表。

使用通配符的情景

所写的方法需要使用 Object 类所提供的功能

所写的方法, 不依赖于具体的类型参数。 比较常见的是反射中, 用Class<?>而非Class, 因为绝大部分方法都不依赖于具体的类型。

那么, 为什么不使用 List

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxxdj.html