前言
前段时间刚看完java编程思想,然后在撸代码的过程中,因为前期做了很好的抽象,所以想使用泛型来简化代码。但是过程中遇到一个问题让我思考了很长时间。现在记录一下。
描述
废话不多说,先贴上代码:
class Father{
}
class Son extends Father{
}
public class GerericTest {
public static void main(String[] args) {
Son a=new Son();
Son b=new Son();
List<Son> sons=new ArrayList<Son>();
sons.add(a);
sons.add(b);
Map<String, Son> map=new HashMap<String, Son>();
map.put("a",a);
map.put("b",b);
funMethod(sons,map);
}
private static void funMethod(List<? extends Father> sons, Map<String, ? extends Father> map) {
if (sons.size()>0) {
map.put("c", sons.get(0));
}
}
}
这段代码其实没有什么业务含义,纯粹是为了暴漏问题。编译器拒绝编译,报错:
The method put(String, capture#2-of ? extends Father) in the type Map<String,capture#2-of ? extends Father> is not applicable for the arguments (String, capture#3-of ? extends Father)
我来简陋的解释一下:在Map<String,capture#2-of ? extends father>的方法 put (String,capture#2-of ? extends father)和与参数(String,capture#3-of?extends father) 不匹配。
分析
编译器报错:类型不匹配。表面上看map的value的类型 和 sons集合中元素的类型是一样:? extends Father,即父类都是Father的类型。但是编译器的报错信息里 ?号之前还有一个capture的东东,capture后面跟的东西不一样。难道类型的确是不一样的吗?答案是:对,不一样。
通配符 ? extends Father 代表的意思是 任何父类是Father的类型,但是无法确定具体是哪个类。如果定义这样的泛型参数,你只能做查看操作,不能做修改操作:当操作的对象类型不确定时,编译器拒绝做 增删改 操作,这样的操作是不安全的。
解决办法
那就归回原始,使用明确的类型参数T来代表确切的类型。即:
class Father{
}
class Son extends Father{
}
public class GerericTest {
public static void main(String[] args) {
Son a=new Son();
Son b=new Son();
List<Son> sons=new ArrayList<Son>();
sons.add(a);
sons.add(b);
Map<String, Son> map=new HashMap<String, Son>();
map.put("a",a);
map.put("b",b);
funMethod(sons,map);
}
private static <T extends Father>void funMethod(List<T> sons, Map<String,T> map) {
if (sons.size()>0) {
map.put("c", sons.get(0));
}
}
}
完美解决问题。这样在方法FunMethond中就可以确定T代表的是父类是Father的一个确定类型,虽然编译期间不知道,但是运行期间会确定这个类型。
问题延申
java编程思想 和 深入理解jvm虚拟机 中都说过java泛型是编译器通过欺骗实现的。即jvm并不支持这样的语法,但是编译器支持这样的语法,并在编译的时候将类型擦除。为了验证,我尝试着将上面的代码在编译器编译成字节码后,进行反编译,结果如下:
public class GerericTest
{
public static void main(String[] args)
{
Son a = new Son();
Son b = new Son();
List sons = new ArrayList();
sons.add(a);
sons.add(b);
Map map = new HashMap();
map.put("a", a);
map.put("b", b);
funMethod(sons, map); }
private static <T extends Father> void funMethod(List<T> sons, Map<String, T> map) {
if (sons.size() > 0)
map.put("c", (Father)sons.get(0));
}
}
大家看到虽然在funMethod中的泛型定义方式是没有变化的,
但是 map.put("c", sons.get(0)) 这行代码经过编译器编译后,通过强制类型转换将类型擦除掉,变成: map.put("c", (Father)sons.get(0))
后记
泛型编程是java中的比较高级的概念,虽然不是像c++原生支持,但是依然给编程人员带来很大的方便:编写更通用的代码。以后继续夯实基础,一步一个脚印走下去。