接之前的两篇文章:
Java范型那些事(一) Java范型那些事(二)
之前写了一些Java范型的文章,但是感觉自己还是一知半解,于是又回顾了一下,
官网教程: https://docs.oracle.com/javase/tutorial/java/generics https://docs.oracle.com/javase/tutorial/extra/generics/index.html
关于范型的一些基本知识,大家应该都有所了解,这里再记录一些东西。
无界通配符使用‘?’表示,如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,
假设我们想写一个这样的方法,该方法可以处理某个类型和所有其子类型的对象,那么这时候就用到了上界通配符,如:
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除外
假设你想写一个方法:将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>)即这时候只能往里写数据,却不能读取数据
大家应该都知道,假设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>的子类型下图展示了使用上界和下界通配符的类型直接的关系:
什么时候使用上界通配符,什么时候使用下界通配符,可以思考以下两点:
一个“in” 变量 “ in”变量将数据提供给代码。 想象一个具有两个参数的复制方法:copy(src, dest)。 src参数提供要复制的数据,因此它是“ in”参数。
一个“out”变量 “out”变量保存要在其他地方使用的数据。 在复制示例copy(src, dest)中,dest参数接受数据,因此它是“ out”参数。
据此,可以得出通配符准则:
“ in”变量使用上界通配符定义,通过使用extends关键字。“out”变量使用下界通配符定义,通过使用super关键字。如果可以使用Object类中定义的方法访问“ in”变量,请使用无界通配符。如果代码需要同时使用“ in”和“ out”变量来访问变量,则不要使用通配符。这些准则不适用于方法的返回类型。 应该避免使用通配符作为返回类型,因为它会迫使程序员使用代码来处理通配符。