泛型的本质,其实就是将类型参数化,就是对于要操作的数据类型指定为一个参数。泛型,是为了在编译的时候能检测到非法的类型。而使用通配符,则是在此之上做的一个扩展,使泛型的使用更加的灵活。
泛型的好处如果不是用泛型,想要对参数类型的“任意化”,就要做显式的强制类型转换。但这里有个问题。请看一下代码。
public class Test{ public static void main(String[] args) { showTest(); //不指定明确的类型,用Object showTest2(); //明确指定类型 } //不指定明确的类型,用Object public static void showTest(){ List<Object> oblist = new ArrayList<>(); oblist.add("abc"); String str =oblist.get(0);//这里再编译的时候不会出错,但是在运行的时候就会报错 String str2 =(String) oblist.get(0);//这里做了显式的强制类型转换 System.out.println(str); System.out.println(str2); } //明确指定类型 public static void showTest2(){ List<String> oblist = new ArrayList<>(); oblist.add("abc"); String str =oblist.get(0);//因为指定了类型,所以获取到的值是不需要做类型转换的 System.out.println(str); } }从上面的额代码可看出, 省去了强制转换,可以在编译时候检查类型安全。
通配符常用的通配符有: T,E,K,V,?
其实也可以是A、B、C、D、E等的字母代替。使用 T,E,K,V,?只不过是约定俗成而已。
T,E,K,V,? 的约定如下:
T:(type) 表示具体的一个java类型。
E:代表Element。
K、V :分别代表java键值中的Key Value。
? :无界通配符,表示不确定的 java 类型
上边界限定通配符 < ? extends E>上边界:用extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
有时候,为什么要使用通配符,而不是简单的泛型呢?其中有一个很重要的原因,就是使用通配符, 可以让你的方法更具有通用性。
比如,有一个父类Animal,然后该父类有几个子类。比如猫猫、狗等。它们都有名字的属性,然后有一个动物列表。
你可以这样写:
List<Animal> animalList也可以这样写
List<? extends Animal> animalList如果想要获取列表里面的明细属性,则:
//方式一 public static void getNameList(List< Animal > animals) { for (Animal animal : animals) { System.out.println(animal.getName()); } } //方式二 public static void getNameList2(List<? extends Animal > animals) { for (Animal animal : animals) { System.out.println(animal.getName()); } } public static void main(String[] args) { Dog dog = new Dog(); dog.setName("aa"); List< Dog > dogs = new ArrayList<>(); dogs.add(dog); getNameList(dogs);//报错 getNameList2(dogs);//不会报错 }方式二的入参写法,限定了上界,但是不关心具体类型是什么,所以对于传入的类型是Animal、Animal的字类的都能支持,方式一则不行。
也可以使用<? extends Animal> 形式的通配符,实现向上转型。
向上转型:
//Animal为一个父类,Dog为Animal的字类 Dog dog = new Dog(); //dog指向的对象在编译时和运行时都是Dog类型 //下面的就是向上转型 Animal dog = new Dog(); //dog指向的对象在编译时是Animal类型,而运行时时Dog类型使用<? extends Animal> 形式的通配符,实现向上转型。
public class Test{ public static void main(String[] args){ List<? extends Animal> list = new ArrayList<Dog>(); list.add(new Dog()); //不能添加,编译报错 list.add(null); //可以添加,不报错。 Animal animal = list.get(0); // 允许返回。 } }这里有个缺陷,不能对list做添加的操作,只能做读取。
当使用extends通配符时,我们无法想list中添加任何东西(null除外),那又为什么可以取出东西呢?
因为无论取什么出来,我们都可以通过向上转型用Animal指向它,这在Java中是被允许的,但不确定取到的是什么,所以必须用上限接收。
Animal animal = list.get(0);//使用上限Animal接收。正确用法。 Dog animal = list.get(0); //错误 下边界限定通配符 < ? super E>又叫超类型通配符。与extends特性完全相反。
下边界: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object。
private <T> void test(List<? super T> dst, List<T> src){ for (T t : src) { dst.add(t); } } public static void main(String[] args) { List<Dog> dogs = new ArrayList<>(); List<Animal> animals = new ArrayList<>(); new Test3().test(animals,dogs); }dst 类型 “大于等于” src 的类型,这里的“大于等于”是指 dst 表示的范围比 src 要大,因此装得下 dst 的容器也就能装 src 。