Java中的泛型之泛型协变的迷惑

你觉得泛型协变很简单?很容易明白?那么我们来看看下面的代码片段。在继续本文之前,请务必读一下这篇文章:Java中的泛型

首先,我们来看看这个。

示例一:

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<?> list1, List<?> list2) {}

	public static void main(String[] args) {
		List<String> list1 = new ArrayList<String>();
		List<Integer> list2 = new ArrayList<Integer>();
		doNothing(list1, list2);
	}

}
这个编译通过,没有问题。

那么看看这个。

示例二:

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<?> list1, List<?> list2) {
		list1.add(list2); 
                // Error: The method add(capture#1-of ?) in the type List<capture#1-of ?> 
                // is not applicable for the arguments (List<capture#2-of ?>)
	}

	public static void main(String[] args) {
		List<String> list1 = new ArrayList<String>();
		List<Integer> list2 = new ArrayList<Integer>();
		doNothing(list1, list2);
	}

}
这里就编译不通过了,为啥?不是有通配符吗?通配符不是可以代表一切类型吗? 抓狂

好吧,我这么改下。把前面的那个List<?>改成List<List<?>>不就可以了。里面的那个嵌套不就正好是后面的那个格式吗?

示例三:

import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<?>> list1, List<?> list2) {
		list1.add(list2);
	}

}
果然,这次编译通过了。 大笑list1的嵌套元素类型和list2的类型都未确定的前提下,其形式表达上是一致的,这么写可以编译通过。

那我再变化一下。

示例四:

import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<? extends Number>> list1, List<?> list2) {
		list1.add(list2); 
                // Error: The method add(List<? extends Number>) in the type List<List<? extends Number>> 
                // is not applicable for the arguments (List<capture#1-of ?>)
        }

}
这里又出问题了。前面的list1可以接受一个元素类型限定在Number子类的List类型,但是后面的list2明明是有通配符的啊!通配符不是可以适应任何类型吗? 委屈

难道是list1和list2的类型不匹配?

那再这么改。

示例五:

import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<? extends Number>> list1, List<? extends Number> list2) {
		list1.add(list2);
	}

}
这次编译通过了。 微笑list1的嵌套元素类型和list2的类型,在形式上都是一致的。这么写可以编译通过。

那我把示例三的main函数加上看看。

示例六:

import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<?>> list1, List<?> list2) {
		list1.add(list2);
	}
	
	public static void main(String[] args) {
		List<List<String>> list1 = null;
		List<String> list2 = null;
		doNothing(list1, list2); 
                // Error: The method doNothing(List<List<?>>, List<?>) 
                // in the type Demo1 is not applicable for the arguments (List<List<String>>, List<String>)
	}

}
这次又编译不通过了。 抓狂为啥?没加main方法的时候不是编译通过的吗?list1的类型确定后是List<List<String>>,而list2的类型确定后是List<String>,list2的类型不正好是list1的嵌套类型吗?为啥编译不通过?

其实,你把上面的代码片段都加上main方法,没有一个能编译通过。你木有看错,它们全是错的!怎样?头晕了吧!

警世恒言:别被JVM骗了!某些时候你明明声明的泛型方法没有问题,但是当你用它们的时候,却发现用不了,而且很难发现错在哪里!有木有!


import java.util.ArrayList;
import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<? extends Number>> list1, List<? extends Number> list2) {
		list1.add(list2);
	}
	
	public static void doSomething() {
		
	}
	
	public static void main(String[] args) {
		List<List<? extends Number>> list1 = new ArrayList<List<? extends Number>>();
		List<? extends Number> list2 = new ArrayList<Number>();
		doNothing(list1, list2);
	}

}
将示例三,进行类型限定之后,这个代码是可以编译通过的。关于多通配符的泛型方法声明,本篇只讨论了其中一种形式。这里面槽点太多,大家慢慢回味吧。 微笑


把上面的示例作如下改动:

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<? extends Number>> list1, List<? extends Number> list2) {
		list1.add(list2);
		System.out.println("output: " + list1.toString());
	}
	
	public static void doSomething() {
		
	}
	
	public static void main(String[] args) {
		List<List<? extends Number>> list1 = new ArrayList<List<? extends Number>>();
		List<Number> list2 = new ArrayList<Number>();
		list2.add(new Integer(1));
		list2.add(new Double(2.0));
		list2.add(new Float(3.0f));
		doNothing(list1, list2);
	}

}

输出:

output: [[1, 2.0, 3.0]]

再把上面的示例作一次变化,这次我们不用List.add()方法,换成List.contains()方法。如下:

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<? extends Number>> list1, List<? extends Number> list2) {
		list1.add(list2);
		System.out.println("output: " + list1.toString());
	}
	
	public static void doSomething(List<? extends List<? extends Number>> list1, List<? extends Number> list2) {
		boolean b = list1.contains(list2);
		System.out.println("contains: " + b);
	}
	
	public static void main(String[] args) {
		List<List<? extends Number>> list1 = new ArrayList<List<? extends Number>>();
		List<Number> list2 = new ArrayList<Number>();
		list2.add(new Integer(1));
		list2.add(new Double(2.0));
		list2.add(new Float(3.0f));
		doNothing(list1, list2);
		doSomething(list1, list2);
	}

}

输出:

output: [[1, 2.0, 3.0]]
contains: true
好吧,因为用List.add()会报错。所以,我换成了List.contains()方法。原因嘛,你懂的。 偷笑

关键在于,要理解

? extends List<? extends Number>

到底是个啥类型。


再次改装上面的示例,这次我们创建一个另类的泛型化的类Pair<T extends List<? extends Number>, D extends List<? extends String>>代码:

import java.util.List;

public class Pair<T extends List<? extends Number>, D extends List<? extends String>> {

	private T t;
	private D d;
	
	public Pair() {}
	
	public void setT(T t) {
		this.t = t;
	}
	
	public T getT() {
		return t;
	}
	
	public void setD(D d) {
		this.d = d;
	}
	
	public D getD() {
		return d;
	}
	
	public String toString() {
		return t.toString() + "; " + d.toString();
	}
}

上面的示例被改为:

import java.util.ArrayList;
import java.util.List;

public class Demo1 {
	
	public static void doNothing(List<List<? extends Number>> list1, List<? extends Number> list2) {
		list1.add(list2);
		System.out.println("output: " + list1.toString());
	}
	
	public static void doSomething(List<? extends List<? extends Number>> list1, List<? extends Number> list2) {
		boolean b = list1.contains(list2);
		System.out.println("contains: " + b);
	}
	
	public static void main(String[] args) {
		List<List<? extends Number>> list1 = new ArrayList<List<? extends Number>>();
		List<Number> list2 = new ArrayList<Number>();
		list2.add(new Integer(1));
		list2.add(new Double(2.0));
		list2.add(new Float(3.0f));
		doNothing(list1, list2);
		doSomething(list1, list2);
		
		Pair<List<? extends Number>, List<? extends String>> p = new Pair<List<? extends Number>, List<? extends String>>();
		List<Integer> listA = new ArrayList<Integer>();
		listA.add(1);
		listA.add(2);
		listA.add(3);
		
		List<String> listB = new ArrayList<String>();
		listB.add("one");
		listB.add("two");
		listB.add("three");
		
		p.setT(listA);
		p.setD(listB);
		
		System.out.println("Pair to String: " + p.toString());
	}

}

其中,Pair的创建那一行还可以写成这样:

Pair<List<Integer>, List<String>> p = new Pair<List<Integer>, List<String>>();

大家仔细回味吧。 微笑

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值