JDK5.0 泛型不泛

来源:互联网 发布:报名软件app 编辑:程序博客网 时间:2024/04/30 17:36
如果你追求新的JDK,那么当你习惯了JDK1.4 的编码后,使用JDK1.5一定出现很多问题。其中,使用泛型的警告一定是布满整个程序吧(    可用@SuppressWarnings("unchecked")消除警告),于是便打算看看泛型的知识。结果不看不知道,一看吓一跳。
先说说泛型的语法吧。
首先,一个类,接口都可泛型。语法是: public class classname<T> , public interface interfacename<T>
其次,泛型可用于函数(方法)。语法为: public <T> void f(T x) 或静态方法 public <A,B> Map<A,B> f()
最后,泛型可以定界。例如 public class ClassRoom<T extends Room<E>> 。 ClassRoom现在接受一个Room类型的对象。

在说说泛型的问题:
首先,由于擦除,泛型的类型信息都在编译后失去了。
例如:
 
public class EraserCheck {
    
public static void main(String args[]){
        Class c1 
= new ArrayList<String>().getclass();
        Class c2 
= new ArrayList<Integer>().getclass();
        System.out.println(c1 
== c2);
    }

}
你认为输出的是true还是false?
事实上,ArrayList<String>与ArrayList<Integer>是截然不同的ArrayList<Integer>中是不能放入String的。
这是为什么呢?
我们再来看看下面一个例子。
public class LostInformation{
    
public static void main(String[] args){
        List
<String> list = new ArrayList<String>();
        Map
<Integer, String> map = new HashMap<Integer, String>Map();
        System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
    }

}
根据JKD文档描述,Class.getTypeParameters 将返回一个TypeVariable对象的数组,表示有泛型声明所声明的
参数类型。事实上呢,输出了什么?
你自己试试,并对比一下ArrayList的源代码和HashMap的源代码。实际上输出的只是占位符号。

来看看以下的C++代码,摘自《Think  in java 4》,稍有改动。
#include<iostream>
using namespace std:

template
<Class T> class Manipulator{
T obj;
public:
    Manipulator(T x) 
{obj = x; }
    Manipulator() 
{obj = new T();}
    
void manipulator() { obj.f(); }
}
;

class HasF {
     
public:
         
void f() { count << "HashF::f() " << endl;
}
;

int main(){
    HasF hf;
    Manipulator
<HasF> manipulator(hf);
    manipulator.mainpulate();
}


如果你没有学过C++模板,你一定会惊奇,为什么Manipulator的obj可以调用f(),因为他根本不知道obj会有这个函数阿。原因是编译期间编译器能知道Manipulator的模板(泛型)为HasF类,于是在HasF中自动寻找f()这个函数,如果没有,则出错。
而java却不能这样。问题就出在当编译器去寻找Manipulator的具体类的时候,具体类的信息丢失了,于是不知道从何寻找f()这个函数。
幸好我们还可以使用协助泛型类,看下面的java实现代码:
public class Manipulator<extends HasF> {
    T obj;
    
public Manipulator(T obj){this.obj = obj; }
    
public void manipulate(){obj.f();}

    
public static void main(String[] args){
        HasF hf 
= new HasF();
        Manipulator
<HasF> manipulator = new Manipulator<HasF>(hf);
        manipulator.manipulate();
    }

}
   HasF省略。
由此已可看到擦除的问题了,但是为什么要用擦除呢?
这个就得从兼容性说起了。为了兼容jdk1.4的版本,当我们使用那些用1.4编写的类库或者其他资源时,我们可以
不必再使用jdk5.0来重写,或许这就是牺牲泛型,使用擦除的原因吧。当然,也提供了擦除的补偿。大家可以实现自己的补偿,使用各种方法。这里提一个函数isInstance(obj), 由于擦除,instancof已经不起作用了,所以
我们用isInstance来补偿。看下面的java代码(来自《Think in java4》:
public class Building{}
public class House{}

public class ClassTypeCapture<T> {
    Class
<T> kind;
    
public ClassTypeCapture(Class<T> kind){
        
this.kind = kind;
    }

    
public boolean f(Object obj){
        
return kind.isInstance(obj);
    }

    
public static void main(String[] args){
        ClassTypeCapture
<Building> cttb = new ClassTypeCapture<Building>();
        System.out.println(cttb.f(
new Building()));
            System.out.println(cttb.f(
new House()));
        ClassTypeCapture
<House> cttbh = new ClassTypeCapture<House>();
        System.out.println(ctth.f(
new Building()));
        System.out.println(ctth.f(
new Housse()));
    }

}

让我们来看看结果:
true
true
false
true
isInstance是一个动态信息,在执行的时候才检测。

还有一个问题,不知道大家发现没有,就是在定义一个泛型时如下:
 public <K,V> Map<K,V> f(){
    ...
}
使用的时候,我们不得不在赋值语句两边都写类型参数,这样似乎很不合理。
Map<Integer,String> map = new HashMap<Integer, String>();
这样,Integer,String写了两次,显得编译器很笨。这也是擦除的原因。
大家自己看看有什么办法可以不用这样写呢?
java泛型的问题还很多,现在就到这里,我们下次在说。