下面通过一个简短有说明价值的小例子,来记录一下JAVA泛型中super、extends的用法和区别。
项目地址: https://github.com/owenliang/java-somewhat/tree/master/src/cc/yuerblog/template 。
package cc.yuerblog.template; import java.util.ArrayList; /** * 叫做Vector的数组容器 * @author liangdong * * @param <T> */ public class Vector<T> extends ArrayList<T> { void walk(Walker<? super T> walker) { for (int i = 0; i < this.size(); ++i) { walker.onValue(this.get(i)); } } }
为了保持简洁,Vector类直接继承自ArrayList,也就拥有了和ArrayList同样的方法,同时Vector也是一个泛型类。
walk方法的本意是要求用户传入一个实现了Walker接口的对象,以便可以得到vector的元素遍历回调。
但是walk方法在声明函数参数的时候会用到更加复杂的泛型语法:<? super T>,它的意思是要求Walker接口必须能够接受T类型或者T类型的父类型作为回调参数,什么意思呢?
如果是Vector<Integer>对象,那么它不仅可以接受Walker<Integer>作为参数,甚至可以将Walker<Number>作为参数,这是因为Integer可以向上转型为Number,所以Walker<Number>同样可以兼容。
我们急需看一下Walker接口的定义。
package cc.yuerblog.template; /** * 用于遍历Vector的回调处理类 * @author liangdong * * @param <T> */ public interface Walker<T> { /** * 遍历回调函数 * @param value */ void onValue(T value); }
该接口也是泛型的,从而可以支持任何元素类型。
onValue方法没有什么魔法,就是<T>决定的。
现在我们在创建Vector<Integer>对象,实现Walker<Number>接口,然后结合<? super T>看一下整体是如何工作的:
package cc.yuerblog.template; public class Main { public static void main(String[] args) { Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); // 基于callback遍历:遍历vector, 要求回调函数能兼容Integer向上转型即可 vector.walk(new Walker<Number>() { @Override public void onValue(Number value) { System.out.println(value.intValue()); } }); // 直接遍历:要求Vector中元素是Number的子类 Main.walk(vector); }
创建vector时,JAVA可以根据左侧变量类型自动推断,所以右侧只需要一个空的<>即可。
接下来传入了一个Walker<Number>接口的实现,覆写的是onValue(Number value)方法。
这时候我们再去看Vector<Integer>::walk方法的实现:
void walk(Walker<? super T> walker) { for (int i = 0; i < this.size(); ++i) { walker.onValue(this.get(i)); } }
此时walk方法的参数是:Walker<? super Integer> walker,表示可以接纳Walker<Integer>或者父类的Walker<Number>。
onValue(Number value)回调函数Integer类型的元素,是可以完成自动向上转换的,这也是为什么Vector<T>的walk方法应该用super的原因:即只要回调函数能经过向上转型兼容传入的参数即可,Vector没必要要求Walker一定要用T类型,父类也是可以的。
extends的出发点则不同,我们先看代码:
public class Main { public static void main(String[] args) { Vector<Integer> vector = new Vector<>(); vector.add(1); vector.add(2); // 直接遍历:要求Vector中元素是Number的子类 Main.walk(vector); } public static void walk(Vector<? extends Number> vector) { for (int i = 0; i < vector.size(); ++i) { Number num = vector.get(i); System.out.println(num.intValue()); } } }
简单说,我定义了一个Main.walk方法,希望支持任意继承自Number的元素类型进行遍历。
所以这里函数参数的声明是Vector<? extends Number>,即:vector里的元素只要是Number的子类即可,我都可以接受,并且我在处理的时候都可以安全的向上转型为Number处理。
JAVA定义泛型类只需要简单<T>,而声明函数参数的时候就出现了extends和super的两种视角,理解它们的意图应该是最重要的,上面的例子给了一个很好的说明。
如果文章帮到了你,请您乐于扫码捐赠1元钱,以便支持服务器运转。