泛型从字面上理解,是指一个类、接口或方法支持多种类型,使之广泛化、一般化和更加通用。Java中使用Object类来定义类型也 能实现泛型,但缺点是造成原类型信息的丢失,在使用中容易造成ClassCastException。
//定义泛型类,接口的定义和类一样 class G1<T> { T content; } // 定义泛型方法,方法的头部使用<T>声明,注意结构 class GMethod1 { // 一般泛型方法定义 public static <T> void method1(T params) { } // 返回值也为泛型 public static <E> E method2(E params) { E content = params; return content; } } // extends 的使用,限定泛型的范围,等于或者是extends的子类;只有extends,没有super,通配符才有extends和super class G2<T extends Number> { T content; }
Java中的泛型通配符分为以下三种:
1. 通配符的使用场景
通配符?只有在修饰一个变量或参数的时候会用到,在定义泛型类或泛型方法的时候是不能使用通配符的。
为了更好说明,我们用代码来解释。
首先我们创建一个类 A,是一个泛型类,里面保存一个变量 value。
public class A<T> { private T value; // 省略 get 和 set 方法 // ...... }
我们再创建两个类 Father 和 Son,Son 是 Father 的子类
// Father.java public class Father { } // Son.java public class Son extends Father{ }
思考下面的代码:
public static void main(String[] args){ A<Father> a1 = new A<Father>(); A<Son> a2 = new A<Son>(); test(a1); test(a2); // 编译错误 } public void test(A<Father> a){...}
Son 是 Father 的子类,但 test(A<Father> a) 方法却不能接收参数 a2,也就是说 A 不是 A 的子类。这个时候就可以使用通配符来解决,我们修改 test 方法
public static void main(String[] args){ A<Father> a1 = new A<Father>(); A<Son> a2 = new A<Son>(); test(a1); test(a2); } public void test(A<? extends Father> a){...}
这样可以正常调用,说明类型 A<Son> 是 A<? extends Father> 的子类型。
2. <? extends T> 子类型限定通配符
我们上面的例子使用了子类型限定通配符,使用通配符很方便,但也带来了一些问题,我们接着看代码:
public void test(A<? extends Father> a){ a.setValue(new Father()); //编译错误 a.setValue(new Son()); //编译错误 Father father = a.getValue(); }
你会发现我们根本不能调用 setValue 方法,假想一下 A<? extends Father> 类,里面的方法似乎是这样的
// 这不是真正的Java方法,只是为了说明 ? extends Father getValue(); void setValue(? extends Father);
当我们调用 setValue 方法的时候,编译器只知道需要某个 Father 及其子类型,但不知道具体是什么类型,所以它拒绝传递任何的特定类型。
使用 getValue 就不会有问题,因为返回值肯定是 Father 及其子类型,所以我们把返回值赋给一个 Father 的引用完全合法。
3. <? super T> 超类型限定通配符
超类型限定通配符的行为与上面说的子类型限定通配符相反,可以为方法提供参数,但不能使用返回值。我们看下面的例子:
public void test2(A<? super Father> a){ a.setValue(new Father()); Father father = a.getValue(); // 编译错误 Object object = a.getValue(); }
假想一下 A<? super Father> 类,里面的方法似乎是这样的
// 这不是真正的Java方法,只是为了说明 void setValue(? super Father) ? super Father getValue()
setValue 方法不知道参数的具体类型,但是可以确定的是参数肯定是 Father 及其父类型,所以我们传递 Father 及其子类型是合法的。
调用 getValue 方法不能保证返回类型的对象,所以只能赋给一个 Object。
4. <?> 无限定通配符
无限定通配符 <?> 这种方式不能为方法提供参数,调用方法返回值也只能赋给 Object。
public void test3(A<?> a){ Object object = a.getValue(); a.setValue(new Object()); //编译错误 }
getValue 的返回值只能赋给一个 Object,setValue 方法不能被调用(**注意:**可以调用 setValue(null))。
所以感觉无限定通配符是集合了上面说的两种通配符的缺点,那我们为什么还要使用它呢?其实在某些简单的场景还是有用的,例如下面这种情况:
// 判断A类中的值是否为空,并不关心A类中值具体是什么类型 public Boolean isNull(A<?> a){ return a.getValue == null; }
5. 总结
<? extends T> 子类型限定通配符
无法向其中设置值,但是可以进行正常的取出
<? super T> 父类型限定通配符
可以设置 T 类型及其子类型的对象,但是取出的时候只能赋值给 Object
<?> 无限定通配符
无法向其中设置值,取值的时候也只能赋值给 Object
从上面的总结可以看出,<? extends T> 通配符偏向于内容的获取,而 <? super T> 通配符更偏向于内容的存入。
PECS 原则(Producer Extends Consumer Super) 很好的解释了这两种通配符的使用场景。当然,如果你既想设置值又想取出值,那么就不适合使用通配符了。
原文:https://www.cnblogs.com/myitnews/p/12316483.html