java 泛型指导手册(下)

title: java 泛型指导手册(下)
date: 2017-07-27 11:38:03

原文:http://tutorials.jenkov.com/java-generics/index.html

index

  1. java的泛型通配符

1. java的泛型通配符

Java Generic’s Wildcards

基本的泛型集合赋值问题

想像一下你有如下的类型继承结构:

public class A { }
public class B extends A { }
public class C extends A { }

类B和C是继承自A的

然后请你看一看这两个List变量:

List<A> listA = new ArrayList<A>();
List<B> listB = new ArrayList<B>();

你能把listA指向listB吗?或者把listB指向listA?或者说明白点,这下面两行可以吗?

listA = listB;

listB = listA;

答案是不可以,两种情况都不可以,原因是:

  1. 在listA中你能插入A的实例,或者其子类的实例(即B,C)。

然后如果你再这样做:

list<B> listB = listA;

然后你就开始冒险了,因为你想想,listA中很有可能包含非B类的实例。当你试着从listB中拿出对象,你冒险了,你有可能拿出一个不是B的对象(即:A或者C)。这就打破了之前的约定:在listB里我声明了,我只包含B类型的对象。

  1. 将listB赋值给listA也会造成问题。

详细地说:

listA = listB;

如果你能做这样的赋值操作,这就意味着你也可以把A和C的实例插入listB中。你这样做的方法是通过listA的reference(引用),记住,listA之前就被声明为List<A>了。

因此,你的动作约等于向类型是list<B>的listB中插入非B的实例。

什么时候我们不得不做这样古怪的赋值?

上述的古怪的乱七八糟的赋值在你想制造一个可复用的方法时能用得到,特别是你在一些特定类型的集合间的操作时。

想象你有一个方法来处理List里的元素,比如打印其中的元素吧。这大概看起来如下:

public void processElements(List<A> elements){
   for(A o : elements){
      System.out.println(o.getValue());
   }
}

这个方法遍历A实例的list,然后调用getValue()(假设A类有这个方法);

正如我们在上述段落里看到的,你不能对listB和listC使用此方法。

泛型通配符

泛型通配符就是为了解决上述问题。其目的有两个:

  1. 读一个泛型集合

  2. 写入一个泛型集合

以下是用泛型通配符定义一个集合的方式:

List<?>           listUknown = new ArrayList<A>();
List<? extends A> listUknown = new ArrayList<A>();
List<? super   A> listUknown = new ArrayList<A>();

下面来解释一下这些奇怪的通配符是啥意思

未知通配符

List<?>意指一个不知道类型的list。可能是List<A>,List<B>,List<C>,或者List<String>等等。

既然你不知道其类型,你就不能贸然写入一个对象进去,你只能从中读。读出来你也要小心地把它当作最基本的Object类来对待。给你一个例子:

public void processElements(List<?> elements){
   for(Object o : elements){
      System.out.println(o);
   }
}

现在processElements(p)能被各种泛型列表被当作参数p传入后,进行调用。比如List<A>,List<B>,List<C>,或者List<String>等。给你一个可行的例子:

List<A> listA = new ArrayList<A>();

processElements(listA);

扩展通配符边界

List<? extends A>意味着这是一个其容纳对象是A的实例,或者其子类实例(即B和C)的列表。

当你知道其类型在A或者其子类范围内,你就可以安全地调用本来属于A的方法,至于对象是子类时,这时候会把子类自动转型为父类。例子如下:

public void processElements(List<? extends A> elements){
   for(A a : elements){
      System.out.println(a.getValue());
   }
}

你现在可以调用processElements(),无论对象是A还是B,还是C,因此,以下所有的例子都是可行的:

List<A> listA = new ArrayList<A>();
processElements(listA);

List<B> listB = new ArrayList<B>();
processElements(listB);

List<C> listC = new ArrayList<C>();
processElements(listC);

但是请记住,现在,(通过使用继承通配符时),你不能调用任何插入方法。因为你不知道他是A类,还是A的子类(即B,C),你不能把一个低完善程度的父类,放入具有更高完善度的子类列表中。低完善程度的父类(A)不会是高完善度子类(B和C)的实例,反过来是对的,即:A和A的子类(B和C)都是A或者A超类的实例

超类通配符边界

List<? super A>意指列表类型可以是A或者A的超类(父类)

当出现这种列表时,你能进行插入操作。不管是插入A类还是A的子类(即B和C)都是可以的:

public static void insertElements(List<? super A> list){
    list.add(new A());
    list.add(new B());
    list.add(new C());
}

记住,A和A的子类都是A或者A超类的实例。所以你能对List<? super A>的集合对象进行插入A或者A子类的操作。

这样一来,你就可以对泛型为A或者A的超类的列表进行插入操作,如下:

List<A>      listA      = new ArrayList<A>();
insertElements(listA);

List<Object> listObject = new ArrayList<Object>();
insertElements(listObject);

这个insertElements()方法不可以进行任何读取操作,除非将读取的对象转型为Object类型。

原因在于,储存在List里的对象是A或者A的超类,但是你就是不知道他超到哪个程度,所以你不可以贸然调用任何读取方法(当你写出对象名字时,你大概会调用其toString(),但是,因为超类对这个方法可能有修改,所以,任何出现对象名字的代码都是像在读取,而读取是有风险的)。但是向上转型是有极限的,所有类都继承自Object。当你转型为Object后,调用是安全的,如下:

Object object = list.get(0);

但是以下是错的:

A object = list.get(0);

猜你喜欢

转载自blog.csdn.net/paulkg12/article/details/76186211