黑马程序员-JAVA-泛型粗解

来源:互联网 发布:淘宝助理5.5在哪下载 编辑:程序博客网 时间:2024/06/06 05:28

——Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ——-


泛型简介

泛型类似于c++中的模板,实现了一次编码多重类型复用,大大减少了编程负担且加强了类型检查的强度。
很多以前需要类型强制转换的操作在引入泛型后变得不需要了,而以前很多类型转换时疏忽出现的问题在限制好泛型之后能够在编译时就检查出来。

Java中的泛型

Java中的泛型自1.5引入,采用的是类型擦除(Type erasure),不同于c++模板的多重编译和.net的类型参数。
Java中的泛型基本上都是在编译器这个层次来实现的。在生成的Java字节代码中是不包含泛型中的类型信息的。使用泛型的时候加上的类型参数,会被编译器在编译的时候去掉。这个过程就称为类型擦除。
而.net泛型是通过运行时类型替换完成的,通过反射可以获得其运行时实际类型和类型参数。c++则是通过类型推断,在编译期间推断所有泛型类型,并指定为实际类型,对于未使用的泛型则不编译,使用的泛型无法推断其实际类型则会报错。
Java的泛型实现实际上完全依赖于编译器,在生成的.class文件中并不包含泛型信息。编译器会智能检测所有泛型使用,对于可识别的危险操作,则报错以禁止这样的行为,比如

List<?> li = new ArrayList<String>();li.add(123);//error

这里试图给一个实际泛型类型为String的List添加Integer类型的元素,这样的操作无疑是很危险的,所以编译器不会让它通过,实际上,对于声明为?的泛型,只能操作Object类型。而对于无法推断是否有类型问题的情况,编译器会给出警告。
类型擦除的基本过程也比较简单,首先是找到用来替换类型参数的具体类。这个具体类一般是Object。如果指定了类型参数的上界的话,则使用这个上界。把代码中的类型参数都替换成具体的类。同时去掉出现的类型声明,即去掉<>的内容。比如T get()方法声明就变成了Object get();List<String>就变成了List。接下来就可能需要生成一些桥接方法(bridge method)。这是由于擦除了类型之后的类可能缺少某些必须的方法。比如

class MyString implements Comparable<String> {    public int compareTo(String str) {                return 0;        }} 

在泛型擦除之后,Comparable<String> 变成了 Comparable 相应的,需要覆盖的compareTo(String str) 变成了 compareTo(Object o),这时编译器就会自动生成转接(bridge)方法以实现功能不变。

泛型限定

有时候为了方便的使用,我们需要泛型有一定的伸缩性,而又不失类型安全。仅仅使用Object显然达不到类型安全的需要,而指定泛型是不能协变的,这样有类型安全问题,那么是否有一个方法可以解决这个问题呢?
答案就是泛型限定,Java中的泛型限定可以指定声明泛型的继承上界和下界,语法如下:

<? extends T> //ceil limit<? super T> //floor limit

需要注意的是:泛型限定只能用于引用的声明,不能用于构造新对象。在使用中,被赋值目标应使用下界限定,被调用引用应使用上界限定。比如在一个方法中,其返回类型只能使用上界限定,其用于接收参数的局部变量应该使用下界限定。

泛型与原始类型、数组的使用

作为类型擦除实现的泛型,Java泛型中不能使用原始类型,这里一般应该使用其包装类。
同样的原因,不能声明一个泛型类型的数组,除非泛型指定为<?>,因为数组的类型检查不能检查实际的泛型类型,这样一来,比如 new List<T>[] 便出现了类型风险,为了避免这个问题,Java禁止了此类结构,指定为<?> 实际上限定了泛型使用只在Object范围内, 这样存入的泛型类型也只能是 Object或者<?>,所以是可行的。
Java同时于1.5引入了可变参数,实际上是参数数组的编译包装,,因此,同样有可能造成类型风险,但是编译器并没有阻止这样使用,而只是提供一个警告。这里必须自行防止ClassCastException。
异常的例子:

static <T>void m(List<String>... l){        Object[] objectArray = l;     // Valid        objectArray[0] = Arrays.asList(42);        String s = l[0].get(0);    //ClassCastException    } 

在调用时,编译器会自动进行类型推断比如:

    static <T>void m(T... args){        sp("ComponentType:"+args.getClass().getComponentType());        for (T t : args) {            sp(t.getClass());        }    }        m(ls,"",Integer.valueOf(53253));        m("","");        m(new ArrayList<String>(),new HashSet<StringBuilder>());

其结果为:

ComponentType:class java.lang.Objectclass java.util.ArrayListclass java.lang.Stringclass java.lang.IntegerComponentType:class java.lang.Stringclass java.lang.Stringclass java.lang.StringComponentType:class java.util.AbstractCollectionclass java.util.ArrayListclass java.util.HashSet

泛型的简单例子

//Generic interfaceinterface Converter <S,T>{    T convertTo(S s);}//Generic classclass  Gen<T,S extends Number> implements Comparable<T>,Converter <S,T> {    //Generic vals    T v1,v2;    T[] vs;    //Generic staic method !!note: T here is not associated with the T of class    public static <T extends Comparable<? super T> & Serializable> Comparator<T>     cmpr(Class<? extends T> cls,T t){        return (T o1, T o2) -> o1.compareTo(o2);    }    //Generic method    @Override    public int compareTo(T o) {        // TODO Auto-generated method stub        return 0;    }    @Override    public T convertTo(S s) {        // TODO Auto-generated method stub        return null;    }}

参见:
“Java 理论和实践: 了解泛型”

0 0
原创粉丝点击