Java通配符解惑

转载: http://www.linuxidc.com/Linux/2013-10/90928.htm 

为了对通配符的了解更为透切,定义如下几个类:

public class Animal {
    private String name;

	public Animal(String name) {
		this.name = name;
	}
	
	public void eat() {
		System.out.println(getName() + " can eat.");
	}
	
	public String getName(){
		return name;
	}
}

 

public class Cat extends Animal {

	public Cat(String name) {
		super(name);
	}

	public void jump(){
		System.out.println(getName() + " can jump.");
	}
}

 

public class Bird extends Animal {

	public Bird(String name) {
		super(name);
	}

	public void fly(){
		System.out.println(getName() + " can fly.");
	}
}

 

public class Magpie extends Bird {

	public Magpie(String name) {
		super(name);
	}

	public void sing(){
		System.out.println(getName() + 
				" can not only eat,but sing");
	}
}

    首先看无通配符的使用案例,如下:

public class AnimalTrainer {
	public void act(List<Animal> list) {
		for (Animal animal : list) {
			animal.eat();
		}
	}
}
    测试代码如下:
public class TestAnimal {
	public static void main(String[] args) {
		AnimalTrainer animalTrainer = new AnimalTrainer();
		//Test 1
		List<Animal> animalList = new ArrayList<>();
		animalList.add(new Cat("cat1"));
		animalList.add(new Bird("bird1"));
		
		animalTrainer.act(animalList);//可以通过编译
		
		//Test 2
		List<Cat> catList = new ArrayList<>();
		catList.add(new Cat("cat2"));
		catList.add(new Cat("cat3"));
		
		animalTrainer.act(catList);//无法通过编译
	}
}

    如上,Test 1 的执行应该可以理解的,因为cat1和bird1都是Animal对象,自然可以添加List<Animal>里。对于Test2,无法通过编译是因为List<Cat>并不是List<Animal>子类,传入参数有误,也就无法通过编译。现在尝试去修改AnimalTrainer.act()代码,让它变得更为通用的一点,即不仅仅是接受List<Animal>参数,还可以接受List<Bird>等参数。那如何更改呢?

一、通配符的上界

        既然知道List<Cat>并不是List<Anilmal>的子类型,那就需要去寻找替他解决的办法,使AnimalTrianer.act()方法变得更为通用(既可以接受List<Animal>类型,也可以接受List<Cat>等参数)。在java里解决办法就是使用通配符"?",具体到AnimalTrianer,就是将方法改为act(List<? extends Animal> list),当中"?"就是通配符,而"? extends Animal"则表示通配符"?"的上界为Animal,换句话说就是,"? extends Animal"可以代表Animal或其子类,可代表不了Animal的父类(如Object),因为通配符的上界是Animal。

       如下,改进之后的AnimalTrianer:

public class AnimalTrainer {
	public void act(List<? extends Animal> list) {
		for (Animal animal : list) {
			animal.eat();
		}
	}
}
        再次进行测试,发现Test2可以通过编译:

public class TestAnimal {
	public static void main(String[] args) {
		AnimalTrainer animalTrainer = new AnimalTrainer();
		//Test 1
		List<Animal> animalList = new ArrayList<>();
		animalList.add(new Cat("cat1"));
		animalList.add(new Bird("bird1"));
		
		animalTrainer.act(animalList);	//可以通过编译
		
		//Test 2
		List<Cat> catList = new ArrayList<>();
		catList.add(new Cat("cat2"));
		catList.add(new Cat("cat3"));
		
		animalTrainer.act(catList);		//也可以通过编译
	}
}
        经过上述分析,可以知道List<Animal>和List<Cat>都是List<? extends Animal>的子类型,类似有List<Bird>,List<Magpie>也是List<? extends Animal>的子类型。

        现在总结如下:对于通配符的上界,有以下几条规则:(假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))

        1) G<? extends Y> 是 G<? extends X>的子类型(如List<? extends Cat> 是 List<? extends Animal>的子类型)。

        2) G<X> 是 G<? extends X>的子类型(如List<Animal> 是 List<? extends Animal>的子类型)

        3) G<?> 与 G<? extends Object>等同,如List<?> 与List<? extends Object>等同。


        学习到了这里,我们就有了疑惑的地方,先观察以下代码,判断是否可行?

public void testAdd(List<? extends Animal> list){
		//....其他逻辑
		list.add(new Animal("animal"));
		list.add(new Bird("bird"));
		list.add(new Cat("cat"));
	}

List<? extends Animal> list = new ArrayList<>();
list.add(new Animal("animal"));
list.add(new Bird("bird"));
list.add(new Cat("cat"));

       (此处加入我自己的理解,如果想了解详细过程,可以参照原文),所有的list.add()方法都不会编译通过,根据上面规则2),3)的描述,我们不能向List<? extends Animal>中添加任何对象,添加任何对象都会对泛型的最初的原则构成破坏。按道理来说,List<Animal>,List<Bird>都是List<? exntends Animal>的子类型,可以使用list.add()进行添加,但是如果添加了Animal之后,导致Bird和Cat不能添加,Java为了保证类型的一致性,只能使全部的类型不能添加。

二、通配符的下界

        既然有了通配符的上界,自然有着通配符的下界。可以如此定义通配符的下界 List<? super Bird>,其中"Bird"就是通配符的下界。

        注意:不能同时声明泛型通配符申明上界和下界。

        在谈注意细节之前,我们先看一下通配符的使用规则——对于通配符的上界,有以下几条基本规则:(假设给定的泛型类型为G,(如List<E>中的List),两个具体的泛型参数X、Y,当中Y是X的子类(如上的Animal和Cat))

            1) G<? super X> 是 G<? super Y>的子类型(如List<? super Animal> 是 List<? super Bird>的子类型)。

            2) G<X> 是 G<? super X>的子类型(如List<Animal> 是 List<? super Animal>的子类型)。

        再来看如下代码,判断其是否符合逻辑:

public void testAdd(List<? super Bird> list){
	list.add(new Bird("bird"));
	list.add(new Magpie("magpie"));
}

List<? super Bird> list = new ArrayList<>();
list.add(new Bird("bird"));
list.add(new Magpie("magpie"));
list.add(new Animal("animal"));

       看第一段代码,分析如下,因为"? super Bird"代表了Bird或其父类,而Magpie是Bird的子类,所以上诉代码不可通过编译。而事实上是" ",为什么呢?

       在解疑之前,再来强调一个知识点,子类可以指向父类,即Bird也是Animal对象。现在考虑传入到testAdd()的所有可能的参数,可以是List<Bird>,List<Animal>或者List<Object>等等,发现这些参数的类型是Bird或其父类,那我们可以这样看,把bird、magpie看成Bird对象,也可以将bird、magpie看成Animal对象,类似的可看成Object对象,最后发现这些添加到List<? supe Bird> list里的对象都是同一类对象(如本文刚开篇提到的Test 1),因此testAdd方法是符合逻辑,可以通过编译的。

       现在再来看一下第二段代码,第二、三行代码的解释和上文一样,至于最后一行list.add( newAnimal("animal"))是无法通过编译的,为什么呢?为了保护类型的一致性,因为"? super Bird"可以是Animal,也可以是 Object 或其他 Bird 的父类,因无法确定其类型,也就不能往List<? super Bird>添加Bird的任意父类对象。

      既然无法确定其父类对象,那该如何遍历List<? super Bird> ? 因为Object是所有类的根类,所以可以用Object来遍历。如下,不过貌似其意义不大。

for (Object object : list) {/**/}

      那"? super BoundingType"可以应用在什么地方呢?"? super BoundingType"应用相对广泛,只不过是混合着用。

      下面举个简单的例子,先假设有以下两个Student和CollegeStudent,当中CollegeStudent继承Student,如下:

public class Student implements Comparable<Student>{
	private int id;

	public Student(int id) {
		this.id = id;
	}

	@Override
	public int compareTo(Student o) {
		return (id > o.id) ? 1 : ((id < o.id) ? -1 : 0);
	}
}

 

public class CollegeStudent extends Student{
	public CollegeStudent(int id) {
		super(id);
	}
}

       先需要根据他们的id对他们进行排序(注意此处是对数组对象进行排序),设计方法如下,(n指数组元素的个数):

public static <T extends Comparable<? super T>> void selectionSort(T[] a,int n)
       先理解此方法含义,首先<T extends Comparable<T>>规定了数组中对象必须实现Comparable接口,Comparable<? Super T>表示如果父类实现Comparable接口,其自身可不实现,如CollegeStudent。先假设有一个CollegeStudent的数组,如下:

CollegeStudent[] stu = new CollegeStudent[]{
   new CollegeStudent(3),new CollegeStudent(2),
   new CollegeStudent(5),new CollegeStudent(4)};
        执行方法 selectionSort(stu,4)是完全可以通过的。可如果定义的selectionSort方法如下:

public static <T extends Comparable<T>> 
			void selectionSort(T[] a,int n)

        则方法selectionSort(stu,4)不能执行,因为CollegeStudent没有实现Comparable<CollegeStudent>接口。换句话就是"? super T"使selectionSort方法变得更为通用了。selectionSort完整代码的实现可参考本文的末尾。

三、无界通配符

        知道了通配符的上界和下界,其实也等同于知道了无界通配符,不加任何修饰即可,单独一个"?" ,如List<?>,"?"可以表示任意类型,即未知类型

        无界通配符通常会用在下面两种情况:

        1、当方法是使用原始的Object类型作为参数时,如下:

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + "");
    System.out.println();
}
         可以选择改为如下实现:

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + "");
    System.out.println();
}
         这样就可以兼容更多的输出,而不单纯是List<Object>,如下:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
         2、在定义的方法体的业务逻辑与泛型类型无关,如List.size(),List.clear()。实际上,最常用的就是Class<?>,因为Class<T>并没有依赖于T。


附录:selectionSort的代码实现:(如果需要实现比较好的输出,最好重写Student的toString方法)

public class SortArray {
	
	//对一组数组对象运用插入排序,n指数组元素的个数
	public static <T extends Comparable<? super T>> 
					void selectionSort(T[] a,int n) {
		for (int index = 0; index < n-1; index++) {
			int indexOfSmallest = getIndexOfSmallest(a,index,n-1);
			swap(a,index,indexOfSmallest);
		}
	}
	
	public static <T extends Comparable<? super T>> int getIndexOfSmallest
              (T[] a, int first, int last) {
		T minValue = a[first]; // 假设第一个为minValue
		int indexOfMin = first; // 取得minValue的下标
		for (int index = first + 1; index <= last; index++) {
			if (a[index].compareTo(minValue) < 0) {
				minValue = a[index];
				indexOfMin = index;
			}
		}

		return indexOfMin;
	}
	
	public static void swap(Object[] a,int first,int second) {
		Object temp = a[first];
		a[first] = a[second];
		a[second] = temp;
	}
	
	public static void main(String[] args) {
		CollegeStudent[] stu = new CollegeStudent[]{
				new CollegeStudent(3),
				new CollegeStudent(2),
				new CollegeStudent(5),
				new CollegeStudent(4)};
		selectionSort(stu, 4);
		for (Student student : stu) {
			System.out.println(student);
		}
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值