分享

Java中的泛型

 碧海山城 2012-08-19

1          参考

2         简单泛型

2.1   泛型类

public class Holder3<T> {

    private T a;

   

    public Holder3(T a ){

        this.a=a;

    }

   

    public void set(T a){this.a=a;}

   

    public T get(){return a;}

   

    public static void main(String[] args){

        Holder3<Animal> h3=new Holder3<Animal>(new AnimalWrapper());

    } 

}

就象main方法,

当创建Holder3对象时,必须指明向持有什么类型的对象,将其置于尖括号内。

2.2   泛型方法

定义泛型方法,只需要将泛型参数列表置于返回值之前,就像下面:

public class GenericMethods {

 

    public <T> void f(T x){

        System.out.println(x.getClass().getName());

    }

   

    public static void main(String[] agrs){

        GenericMethods gm=new GenericMethods();

        gm.f("");

        gm.f(1);

        gm.f(1.0);

        gm.f(1.0F);

       

        gm.f(gm); 

    }  

}

结果:

java.lang.String

java.lang.Integer

java.lang.Double

java.lang.Float

proxy.generic.GenericMethods

 

如果是泛型类,必须在创建对象的时候指定类型参数的值,而是用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型,这称为类型参数推断。因此,可以想调用普通方法一样调用f(),而且,就好像是f()被无限次重载过。

 3         擦除

 

3.1   初步擦除

Class c1=new ArrayList<String>().getClass();

Class c2=new ArrayList<Integer>().getClass();

   

System.out.println(c1==c2);

 

从代码上看,我们很容易认为是不同的类型。不同的类型在行为上不同,例如尝试将一个Integer放入ArrayList<String>,所得到的行为(失败)与Integer放入ArrayList<Integer>,所得到的行为完全不同,但是程序会打印出来相同。下面是另外一个奇怪的程序:

public class LostInformation {

 

    public static void main(String[] args){

        List<Frob> list=new ArrayList<Frob>();

        Map<Frob,Fnorkie> map=new HashMap<Frob, Fnorkie>();

       

        Quark<Fnorkie> quark=new Quark<Fnorkie>();

        Particle<Long,Double> p=new Particle<Long, Double>();

            System.out.println(Arrays.toString(list.getClass().getTypeParameters()));

        System.out.println(Arrays.toString(map.getClass().getTypeParameters()));

        System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));

        System.out.println(Arrays.toString(p.getClass().getTypeParameters()));

   

    }

}

class Frob{}

class Fnorkie{}

class Quark<Q>{}

class Particle<POSITION,MOMENTUN>{}

 

[E]

[K, V]

[Q]

[POSITION, MOMENTUN]

Class.getTypeParameters()的说明看起来是返回一个TypeVarible对象数组,表示有泛型声明所声明的类型参数,这好像是暗示你可能发现参数类型的信息,但是,正如结果看到的,你能够发现的只是用作参数占位符的标识符,残酷的现实是:

在泛型代码内部,无法获得任何有关泛型参数类型的信息

 

3.2   C++的泛型与Java泛型边界

 

Template<class T> class Manipulator{

T obj;

public:

Void manipulator(T x){obj.f();}

}

C++的实现中,obj上可以调用f()方法,为什么?当实例化这个模板时,C++编译器将进行检查,因此在Manipulator<HasF>被实例化的这一刻,看到HasF拥有一个方法f(),如果不是这样,将会得到一个编译期错误,这样类型安全就得到了保障。

 

Java中不能这么做,除非借助泛型类的边界,以此告诉编译器,只能接受遵循这个边界的类型,这里重用了extends关键字:

Class Mainpulator<T extends Hasf>{

Private T obj;

Public void mainpulate{obj.f();}

}

 

3.3   擦除原因

擦除并不是语言的一个特性,是Java实现泛型的一种折中,因为泛型不是Java语言出现时就有的组成部分,所以这种折中是必须的。

 

如果泛型在Java1.0中就已经是其一部分,那么这个特性就不会使用搽除来实现,它将具体化,使类型参数保持为第一类试题,因此你就能够在泛型参数上执行基于类型的语言操作和反射操作。主要是为了向后兼容性,即现有的代码和类文件仍旧合法,并且继续保持其之前的含义,而且还要支持迁移兼容性,使得类库按照它们自己的步调变为泛型的

 

在基于擦除的实现中,泛型类型被当作第二类类型处理,即不能再某些重要的上下文环境中使用的类型,泛型类型只有在静态类型检查期间出现,在此之后,程序中所有泛型类型都将被擦除,替换为它们的非泛型上界,例如,诸如List<T>被擦除为List,而普通的类型变量在未指定边界的情况下,被擦除为Object

 

3.4   擦除的问题

public class ArrayMaker<T> {

 

    private Class<T> kind;

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

   

   

    T[] create(int size){

//这里必须转型成有意义的类型,因为T并没有包含具体的意思,它被擦除了,会 //有警告

        return (T[])Array.newInstance(kind, size);

    }

   

    public static void main(String[] grs){

        ArrayMaker<String> stringMarker=

new ArrayMaker<String>(String.class);

        String[] stringArray=stringMarker.create(9);

        System.out.println(Arrays.toString(stringArray));

    }

   

}

结果:

[null, null, null, null, null, null, null, null, null]

 

即使kind被存储为Class<T>,擦除也意味着它实际被存储为Class,没有任何参数。因此,当你在使用它时,例如创建数组时,Array.newInstance()实际上并未拥有kind所蕴含的类型信息,因此不会产生具体的结果。在Java中推荐使用工厂(直接使用class.newInstance()或者显示的工程方法)方法或者模板方法来解决这类创建问题

 

3.5      擦除的边界

 

虽然在运行的时候,擦除在方法体重移除了类型信息,但是在边界(看了下面的例子再解释边界)的时候,还是会进行类型转换(检查)。

public class FilledListMaker<T> {

    List<T> create(T t,int n){

        List<T> result=new ArrayList<T>();

        for(int i=0;i<n;i++){

            result.add(t);

        }

result.add((T)new b());

        return result;

    }

   

    public static void main(String[] agrs){

        FilledListMaker<demo> stringMarker=

new FilledListMaker<demo>();

       

        List<demo> list=stringMarker.create(demo.init("44"), 4);

        System.out.println(list.get(4));

        //这里发生了异常

System.out.println(list.get(4).getaa());

        System.out.println(list);  

    }

}

上面的泛型接受demo类型,但是我们悄悄放入了一个b类型。

 

javap -c FilledListMaker反编译类,会得到下面的内容:

 
可以看到,main方法在get(4)的时候,会需要调用它的toString方法,它是Object的方法,并不需要转型,没有任何类型检查,所以没有发生异常,但是下次调用get(4).getaa()的时候,这需要类型转换,所以这里进行类型检查,抛出了异常。

 

所以可以记住,边界就是发生在需要转型的地方

 

3.6   New泛型

创建一个new T()的尝试将无法实现,部分原因是因为擦除,另一部分原因是因为编译器不能验证具有默认的无参构造函数,但是在C++中,这种操作很自然,很直观,并且很安全。

 

Java中的解决方案是传递一个工厂对象,并使用它来创建新的实例,最便利的工厂就是Class对象,因此,如果使用类型标签,就可以使用newInstance来创建这个类型的新对象:

public class ClassAsFactory<T> {

    T x;

    public ClassAsFactory(Class<T> kind){

        try {

            x=kind.newInstance();

        } catch (Exception e){

            throw new RuntimeException(e);

        }

    }

   

    public static void main(String[] args){

        ClassAsFactory<Employee> fe=new

            ClassAsFactory<Employee>(Employee.class);

       

        //这里会抛出异常,因为Integer没有默认的构造函数

        ClassAsFactory<Integer> f1=new

            ClassAsFactory<Integer>(Integer.class);

    }

}

 

class Employee{}

 

可以看到,这种方式,在某些情况下会有异常,因为Integer没有任何默认的构造函数,所以sun并不是很推荐使用这种方式,建议使用显示的工厂,并将限制其类型,使得智能接受实现了这个工厂的类:

 

3.7   ExtendsSuper?

 

1.上面提到了边界,因此擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那些可以用Object调用的方法,但是,如果能够将这个参数限制为某个类型子集,那么就可以用这些类型子集来调用方法。为了执行这种限制,Java泛型重用了extends关键字,

 

interface face1 {}

interface face2 {}

class class1 {}

class Bound<T extends class1 & face1 & face2> {}

 

在子类还能加入更多的限定

interface face3 {}

class BoundDerived<T extends class1 & face1 & face2 & face3> extends Bound<T> {}

 

2.Super关键字限定了下界,但是没有限定上界,所以

ArrayList<? super Derived1> alsb = new ArrayList<Base1>();

alsb.add(new Derived1()); //success

// alsb.add(new Base1()); // error: The method add(capture#4-of ? super Derived1) in the type ArrayList<capture#4-of ? super Derived1> is not applicable for the arguments (Base1)

Object d = alsb.get(0); // return an Object

 

可以看到在接受参数时限制放宽了,因为编译器知道范型的下界,只要是Derived类或它的子类都是合法的。但是在返回时,它只能返回Object类型,因为它不能确定它的上界。

 

 

3.无界通配符,即<?>,与原生类型(非范型类)大体相似

 

 

个人感觉这部分是在太绕了,看java编程思想里面那么多的解释,还是大概知道咋用就好了,贴一个我之前系统的设计图吧

 

4    关于取得泛型的类型

 

4.1   Type体系

 

java5之后,java加入了type体系,这部分太麻烦了,以后有机会用到再写吧,shit

 

4.2   获得泛型类型

参看Java获得泛型类型

Java泛型有这么一种规律:

位于声明一侧的,源码里写了什么到运行时就能看到什么;

位于使用一侧的,源码里写什么到运行时都没了。

声明一侧,即在Class类内的信息;使用一侧,即在一些方法体内部

 

public class GenericClassTest<A,B extends Number> {

 

   private List<String> list;   

   public static void main(String[] args) throws NoSuchFieldException, SecurityException{

        GenericClassTest<String,Integer> gc=new GenericClassTest<String,Integer>();

        for(TypeVariable ty:gc.getClass().getTypeParameters()){

            System.out.println(ty.getName());

        }

        out(((ParameterizedType)gc.getClass().getDeclaredField("list")

                        .getGenericType()).getOwnerType());

        out(((ParameterizedType)gc.getClass().getDeclaredField("list")

                        .getGenericType()).getRawType());

        out(((ParameterizedType)gc.getClass().getDeclaredField("list").

                        getGenericType()).getActualTypeArguments()[0]);

    }}

输出:

A

B

null

interface java.util.List

class java.lang.String

可以看到,声明的list可以取到String,但是方法内的对象缺只能取到AB

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多