Java范型那些事(三)

mac2022-06-30  26

接之前的两篇文章:

Java范型那些事(一) Java范型那些事(二)

之前写了一些Java范型的文章,但是感觉自己还是一知半解,于是又回顾了一下,

官网教程: https://docs.oracle.com/javase/tutorial/java/generics https://docs.oracle.com/javase/tutorial/extra/generics/index.html

关于范型的一些基本知识,大家应该都有所了解,这里再记录一些东西。

1. 无界通配符

无界通配符使用‘?’表示,如List<?>,读作 list of unknown,使用场景如下:

如果您正在编写一个可以使用Object类中提供的功能实现的方法。当代码使用泛型类中不依赖于type参数的方法时。 例如,List.size或List.clear。 实际上,Class <?>之所以经常使用,是因为Class 中的大多数方法都不依赖于T

比如以下方法:

public static void printList(List<Object> list) { for (Object elem : list) System.out.println(elem + " "); System.out.println(); }

这个方法目的是想打印所有类型的集合,但是这个方法只能打印Object类型集合,因为List<Integer>, List<String>, List<Double>不是List<Object>的子类型 ,修改成如下:

public static void printList(List<?> list) { for (Object elem: list) System.out.print(elem + " "); System.out.println(); }

由于对于所有类型A,List<A>都是List<?>的子类型,所以下面的调用没有问题:

List<Integer> li = Arrays.asList(1, 2, 3); List<String> ls = Arrays.asList("one", "two", "three"); printList(li); printList(ls);

⚠️注意: List<Object>和List<?>不是一种类型,你可以在List\中添加任何Object或其子类对象,但是只能往List<?>中添加null,

2. 上界通配符

假设我们想写一个这样的方法,该方法可以处理某个类型和所有其子类型的对象,那么这时候就用到了上界通配符,如:

public static void process(List<? extends Foo> list) { for (Foo elem : list) { // ... } }

这个时候,在循环当中,可以取出list的元素当作Foo类型对象使用,然后可以调用Foo类型中定义的方法,

再举个?:

public static double sumOfList(List<? extends Number> list) { double s = 0.0; for (Number n : list) s += n.doubleValue(); return s; }

这样定义方法后,就可以传入所有Number及其子类对象,像下面这样调用:

List<Integer> li = Arrays.asList(1, 2, 3); System.out.println("sum = " + sumOfList(li)); List<Double> ld = Arrays.asList(1.2, 2.3, 3.5); System.out.println("sum = " + sumOfList(ld));

即只能从中读取数据,不能写入新数据,null除外

3. 下界通配符

假设你想写一个方法:将Integer对象放到一个list中,为了最大化灵活性,你期望这个list可以是List<Integer>, List<Number>, 和 List<Object> ,或者是任何可以写入Integer对象的集合。那么可以这样写:

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

这时候可以这么调用:

addNumbers(new List<Integer>) addNumbers(new List<Number>) addNumbers(new List<Object>)

即这时候只能往里写数据,却不能读取数据

4. Wildcards and Subtyping 通配符和子类型

大家应该都知道,假设B是A的子类,那么List<B>不是List<A>的子类型,以Number和Integer为例,虽然Integer是Number的子类,但是List<Integer>和List<Number> 并没有任何关系,唯一有点关系的就是,它们都是List<?>的子类型。 如果要使得通过List<Integer>的元素来访问到Number对象中的方法,那么可以这样定义:

List<? extends Integer> intList = new ArrayList<>(); List<? extends Number> numList = intList;//编译正常,因为List<? extends Integer>是List<? extends Number>的子类型

下图展示了使用上界和下界通配符的类型直接的关系:

5. 通配符使用准则

什么时候使用上界通配符,什么时候使用下界通配符,可以思考以下两点:

一个“in” 变量 “ in”变量将数据提供给代码。 想象一个具有两个参数的复制方法:copy(src, dest)。 src参数提供要复制的数据,因此它是“ in”参数。

一个“out”变量 “out”变量保存要在其他地方使用的数据。 在复制示例copy(src, dest)中,dest参数接受数据,因此它是“ out”参数。

据此,可以得出通配符准则:

“ in”变量使用上界通配符定义,通过使用extends关键字。“out”变量使用下界通配符定义,通过使用super关键字。如果可以使用Object类中定义的方法访问“ in”变量,请使用无界通配符。如果代码需要同时使用“ in”和“ out”变量来访问变量,则不要使用通配符。

这些准则不适用于方法的返回类型。 应该避免使用通配符作为返回类型,因为它会迫使程序员使用代码来处理通配符。

最新回复(0)