资源描述
,单击此处编辑母版文本样式,第二级,第三级,第四级,第五级,#,单击此处编辑母版标题样式,JAVA,泛型和反射,泛型,是对,Java,语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类。可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。,例如,,Map,类允许您向一个,Map,添加任意类的对象,即使最常见的情况是在给定映射(,map,)中保存某个特定类型(比如,String,)的对象。,因为,Map.get(),被定义为返回,Object,,所以一般必须将,Map.get(),的结果强制类型转换为期望的类型,如下面的代码所示:,Map m = new HashMap();m.put(key, blarg);String s = (String) m.get(key);,要让程序通过编译,必须将,get(),的结果强制类型转换为,String,,并且希望结果真的是一个,String,。但是有可能某人已经在该映射中保存了不是,String,的东西,这样的话,上面的代码将会抛出,ClassCastException,。,理想情况下,您可能会得出这样一个观点,即,m,是一个,Map,,它将,String,键映射到,String,值。这可以让您消除代码中的强制类型转换,同时获得一个附加的类型检查层,该检查层可以防止有人将错误类型的键或值保存在集合中。这就是,泛型,所做的工作。,泛型的好处,Java,语言中引入,泛型,是一个较大的功能增强。不仅语言、类型系统和编译器有了较大的变化,以支持,泛型,,而且类库也进行了大翻修,所以许多重要的类,比如集合框架,都已经成为,泛型,化的了。这带来了很多好处:,类型安全。,泛型,的主要目标是提高,Java,程序的类型安全。通过知道使用,泛型,定义的变量的类型限制,编译器可以在一个高得多的程度上验证类型假设。没有,泛型,,这些假设就只存在于程序员的头脑中(或者如果幸运的话,还存在于代码注释中)。,消除强制类型转换。,泛型,的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。,潜在的性能收益。,泛型,为较大的优化带来可能。更多类型信息可用于编译器这一事实,为,JVM,的优化带来可能。,使用泛型与不使用泛型的比较。,/,不使用泛型,ArrayList al1=new ArrayList();/1,al1.add(new Integer(10);/2,这里可加入任何对象。,Integer i1=(Integer)al1.get(0);/3,这里必须做强制类型转换。如果前面程序不小心往,al1,中放入了非,Integer,对象,那么程序运行到此将会报一个类型转换异常,(java.lang.ClassCastException),。,/,使用泛型,ArrayList al2=new ArrayList();,al2.add(new Integer(10);/,这里只能加入,Integer,对象。,Integer i2=al2.get(0);/,这里不必做强制类型转换。不用再担心程序会报一个类型转换异常,(java.lang.ClassCastException),。,泛型基础,在定义,泛型,类或声明,泛型,类的变量时,使用尖括号来指定形式类型参数。形式类型参数与实际类型参数之间的关系类似于形式方法参数与实际方法参数之间的关系,只是类型参数表示类型,而不是表示值。,当声明或者实例化一个,泛型,的对象时,必须指定类型参数的值:,Map map = new HashMap();,自定义简单泛型,public,class,Gclass,private,T a;,public,T getA(),return,a;,public,void,setA(T a),this,.a = a;,泛型 与继承,泛型中有继承的概念吗,/,使用泛型,ArrayList al2=new ArrayList();/1,al2.add(new Integer(10);/2,Integer i2=al2.get(0);/3,ArrayList alO=al2;/,这样可以吗?,如果说,ArrayList,是一个,ArrayList,就可以这么转换的话将出大问题:,alO.add(new Object();,将是合法的,i2=al2.get(1);/,这样就会导致类型转换异常。,所以说,: ArrayList,不是一个,ArrayList.,这带来另一个后果是,不能实例化泛型类型的数组(,new List3,是不合法的),如果允许声明泛型类型数组,List lsa = new List10;,Object oa = lsa;,List li = new ArrayList();,li.add(new Integer(3);,oa0 = li;,String s = lsa0.get(0);,最后一行将抛出,ClassCastException,,因为这样将把,List,填入本应是,List,的位置。因为数组继承会破坏泛型的类型安全,所以不允许实例化泛型类型的数组(除非类型参数是未绑定的通配符,比如,List,)。,类型通配符,public void showCollection2(Collection c),System.out.println(c);,上面代码中的问号是一个类型通配符。,List,是任何,泛型,List,的父类型,所以完全可以将,List,、,List,或其他传递给,printList(),。,类型通配符的作用,下面的代码工作得很好:,List li = new ArrayList();li.add(new Integer(42);List lu = li;System.out.println(lu.get(0);,另一方面,下面的代码不能工作:,List li = new ArrayList();li.add(new Integer(42);List lu = li;lu.add(new Integer(43); / error,对于,lu,,编译器不能对,List,的类型参数作出足够严密的推理,以确定将,Integer,传递给,List.add(),是类型安全的。所以编译器将不允许您这么做。下面的代码将能工作,因为它不依赖于编译器必须知道关于,lu,的类型参数的任何信息:,List li = new ArrayList();li.add(new Integer(42);List lu = li;lu.clear();,有所限制的类型抽象通配符,public class Matrix . ,编译器允许创建,Matrix,或,Matrix,类型的变量,但是如果试图定义,Matrix,类型的变量,则会出现错误。类型参数,V,被判断为由,Number,限制 。在没有类型限制时,假设类型参数由,Object,限制。这就是为什么前一屏,泛型,方法 中的例子,允许,List.get(),在,List,上调用时返回,Object,,即使编译器不知道类型参数,V,的类型。,有所限制的类型抽象通配符,public void showCollection3(Collection c),for(Number n:c),System.out.println(n.intValue();,泛型方法,通过在类的定义中添加一个形式类型参数列表,可以将类,泛型,化。方法也可以被,泛型,化,不管它们定义在其中的类是不是,泛型,化的。,public T ifThenElse(boolean b, T first, T second),return b ? first : second;,ifThenElse(),不显式地告诉编译器,想要,T,的什么值。编译器不必显式地被告知,T,将具有什么值;它只知道这些值都必须相同。,Integer i = ifThenElse(b, new Integer(1), new Integer(2);,是合法的,而,String s = ifThenElse(b, “pi”, new Float(3.14);,是非法的,因为有类型不满足所需的类型约束:,方法中泛型的调用,/,错误的写法,public void addToCollection3(Number a,Collection c),for(Number n:a),c.add(n);/,编译错误!,/,正确的写法,public void addToCollection3(T a,Collection c),for(T n:a),c.add(n);,JAVA,反射,反射,是,Java,程序开发语言的特征之一,它允许运行中的,Java,程序对自身进行检查,或者说,自审,,并能直接操作程序的内部属性。例如,使用它能获得,Java,类中各成员的名称并显示出来。,Java,语言的反射机制,在,Java,运行时,环境中,对于任意一个类,能否知道这个类有哪些属性和方法?对于任意一个对象,能否调用它的任意一个方法?答案是,肯定,的。这种动态获取类的信息以及动态调用对象的方法的功能来自于,Java,语言的反射(,Reflection,)机制。,Java,反射机制主要提供了以下功能,Java,语言的反射机制,在运行时判断任意一个对象所属的类。,在运行时构造任意一个类的对象。,在运行时判断任意一个类所具有的成员变量和方法。,在运行时调用任意一个对象的方法,Java Reflection API 简介,在,JDK,中,主要由以下类来实现,Java,反射机制,这些类都位于,java.lang.reflect,包中,Class,类:代表一个类。,Field,类:代表类的成员变量(成员变量也称为类的属性)。,Method,类:代表类的方法。,Constructor,类:代表类的构造方法。,Array,类:提供了动态创建数组,以及访问数组的元素的静态方法,考虑下面这个简单的例子,让我们看看 反射是如何工作的。,import java.lang.reflect.*;public class DumpMethods public static void main(String args) try Class c = Class.forName(args0);Method m = c.getDeclaredMethods();for (int i = 0; i m.length; i+)System.out.println(mi.toString(); catch (Throwable e) System.err.println(e);,按如下语句执行:,java DumpMethods java.util.Stack,这样就列出了,java.util.Stack,类的各方法名以及它们的限制符和返回类型。,这个程序使用,Class.forName,载入指定的类,然后调用,getDeclaredMethods,来获取这个类中定义了的方法列表。,java.lang.reflect.Methods,是用来描述某个类中单个方法的一个类。,使用反射的时候必须要遵循三个步骤:,第一步是获得你想操作的类的,java.lang.Class,对象。在运行中的,Java,程序中,用,java.lang.Class,类来描述类和接口等。,下面就是获得一个,Class,对象的方法之一:,Class c = Class.forName(java.lang.String);,还有另一种方法,如下面的语句:,Class c = int.class;,在,java.lang.Object,类中定义了,getClass(),方法,因此对于任意一个,Java,对象,都可以通过此方法获得对象的类型。,Class,类是,Reflection API,中的核心类,它有以下方法,getName(),:获得类的完整名字。,getFields(),:获得类的,public,类型的属性。,getDeclaredFields(),:获得类的所有属性。,getMethods(),:获得类的,public,类型的方法。,getDeclaredMethods(),:获得类的所有方法。,getMethod(String name, Class parameterTypes),:获得类的特定方法,,name,参数指定方法的名字,,parameterTypes,参数指定方法的参数类型。,getConstructors(),:获得类的,public,类型的构造方法。,getConstructor(Class parameterTypes),:获得类的特定构造方法,,parameterTypes,参数指定构造方法的参数类型。,newInstance(),:通过类的,不带参数,的构造方法创建这个类的一个对象。,第二步是调用诸如,getDeclaredMethods,的方法,以取得该类中定义的所有方法的列表。,一旦取得这个信息,就可以进行第三步了,使用,reflection API,来操作这些信息,如下面这段代码:,Class c = Class.forName(java.lang.String);Method m = c.getDeclaredMethods();System.out.println(m0.toString();,它将以文本方式打印出,String,中定义的第一个方法的原型。,得到类信息之后,通常下一个步骤就是解决关于,Class,对象的一些基本的问题。例如,,Class.isInstance,方法可以用于模拟,instanceof,操作符:,public class instance1 public static void main(String args) try Class cls = Class.forName(A);boolean b1 = cls.isInstance(new Integer(37);System.out.println(b1);boolean b2 = cls.isInstance(new A();System.out.println(b2); catch (Throwable e) System.err.println(e);,在这个例子中创建了一个,A,类的,Class,对象,然后检查一些对象是否是,A,的实例。,找出一个类中定义了些什么方法,这是一个非常有价值也非常基础的,reflection,用法。下面的代码就实现了这一用法:,Class cls = Class.forName(“className);Method methlist = cls.getDeclaredMethods();for (int i = 0; i methlist.length; i+) Method m = methlisti;System.out.println(name = + m.getName();System.out.println(decl class = + m.getDeclaringClass();Class pvec = m.getParameterTypes();for (int j = 0; j pvec.length; j+)System.out.println(param # + j + + pvecj);Class evec = m.getExceptionTypes();for (int j = 0; j evec.length; j+)System.out.println(exc # + j + + evecj);System.out.println(return type = + m.getReturnType();System.out.println(-);,这个程序首先取得,className,类的描述,然后调用,getDeclaredMethods,来获取一系列的,Method,对象,它们分别描述了定义在类中的每一个方法,包括,public,方法、,protected,方法、,package,方法和,private,方法等。如果你在程序中使用,getMethods,来代替,getDeclaredMethods,,你还能获得继承来的各个方法的信息。取得了,Method,对象列表之后,要显示这些方法的参数类型、异常类型和返回值类型等就不难了。这些类型是基本类型还是类类型,都可以由描述类的对象按顺序给出。,获取构造器信息,获取类构造器的用法与上述获取方法的用法类似,如:,Class cls = Class.forName(className);Constructor ctorlist = cls.getDeclaredConstructors();for (int i = 0; i ctorlist.length; i+) Constructor ct = ctorlisti;System.out.println(name = + ct.getName();System.out.println(decl class = + ct.getDeclaringClass();Class pvec = ct.getParameterTypes();for (int j = 0; j pvec.length; j+)System.out.println(param # + j + + pvecj);Class evec = ct.getExceptionTypes();for (int j = 0; j evec.length; j+)System.out.println(exc # + j + + evecj);System.out.println(-);,这个例子中没能获得返回类型的相关信息,那是因为构造器没有返回类型。,获取类的字段,(,域,),找出一个类中定义了哪些数据字段也是可能的,下面的代码就在干这个事情:,Class cls = Class.forName(field1);Field fieldlist = cls.getDeclaredFields();for (int i = 0; i fieldlist.length; i+) Field fld = fieldlisti;System.out.println(name = + fld.getName();System.out.println(decl class = + fld.getDeclaringClass();System.out.println(type = + fld.getType();int mod = fld.getModifiers();System.out.println(modifiers = + Modifier.toString(mod);System.out.println(-);,这个例子和前面那个例子非常相似。例中使用了一个,Modifier,,它也是一个,reflection,类,用来描述字段成员的修饰语,如“,private int”,。这些修饰语自身由整数描述,而且使用,Modifier.toString,来返回以“官方”顺序排列的字符串描述,(,如“,static”,在“,final”,之前,),。:和获取方法的情况一下,获取字段的时候也可以只取得在当前类中申明了的字段信息,(getDeclaredFields),,或者也可以取得父类中定义的字段,(getFields),。,根据方法的名称来执行方法,到这里,所举的例子无一例外都与如何获取类的信息有关。我们也可以用,reflection,来做一些其它的事情,比如执行一个指定了名称的方法。,Class cls = Class.forName(method2);Class partypes = new Class2;partypes0 = Integer.TYPE;partypes1 = Integer.TYPE;Method meth = cls.getMethod(add, partypes);method2 methobj = new method2();Object arglist = new Object2;arglist0 = new Integer(37);arglist1 = new Integer(47);Object retobj = meth.invoke(methobj, arglist);Integer retval = (Integer) retobj;System.out.println(retval.intValue();,假如一个程序在执行的某处的时候才知道需要执行某个方法,这个方法的名称是在程序的运行过程中指定的,那么上面的程序演示了如何做到。,上例中,,getMethod,用于查找一个具有两个整型参数且名为,add,的方法。找到该方法并创建了相应的,Method,对象之后,在正确的对象实例中执行它。执行该方法的时候,需要提供一个参数列表,这在上例中是分别包装了整数,37,和,47,的两个,Integer,对象。执行方法的返回的同样是一个,Integer,对象,它封装了返回值,84,。,创建新的对象,对于构造器,则不能像执行方法那样进行,因为执行一个构造器就意味着创建了一个新的对象,(,准确的说,创建一个对象的过程包括分配内存和构造对象,),。所以,与上例最相似的例子如下:,Class cls = Class.forName(constructor2);Class partypes = new Class2;partypes0 = Integer.TYPE;partypes1 = Integer.TYPE;Constructor ct = cls.getConstructor(partypes);Object arglist = new Object2;arglist0 = new Integer(37);arglist1 = new Integer(47);Object retobj = ct.newInstance(arglist); catch (Throwable e) System.err.println(e);,根据指定的参数类型找到相应的构造函数并执行它,以创建一个新的对象实例。使用这种方法可以在程序运行时动态地创建对象,而不是在编译的时候创建对象,这一点非常有价值。,改变字段,(,域,),的值,reflection,的还有一个用处就是改变对象数据字段的值。,reflection,可以从正在运行的程序中根据名称找到对象的字段并改变它,Class cls = Class.forName(field2);Field fld = cls.getField(d);field2 f2obj = new field2();System.out.println(d = + f2obj.d);fld.setDouble(f2obj, 12.34);System.out.println(d = + f2obj.d);,这个例子中,字段,d,的值被变为了,12.34,。,
展开阅读全文