标签:
本文是《Java核心技术
卷1》中第五章继承中关于反射的阅读总结。
Java中的反射库(reflection library)中提供了一个非常丰富且精心设计的工具集,以便编写能够动态操纵Java代码的程序。
能够分析类能力的程序称为反射(reflective)。反射的功能很强大,下面是反射的用途:
Java是面向对象的语言,在Java中,所有的东西都是类(除了静态方法和基本类型)。那么,类是不是一个对象呢?
在Java程序运行期间,Java运行时系统时钟为所有的对象维护一个被称为运行时的类型标志。这个信息跟踪着每个对象的所属类。虚拟机利用运行时类型信息选择相应的方法执行。
Class类保存了Java类的这些信息。官网中,对这个类型称为类的类类型,也就是说一个类的对象。比如,有一个类Student,可以使用下面的代码创建一个实例:
Stuent stu=new Studnet();即stu是类Student的一个实例。那既然类也是对象,那么Student是什么的实例呢?Student是类Class的一个实例。
可以通过如下三种方法获得一个类的类类型。
(1)getClass方法
如果有一个类的实例,那么可以通过这个实例的getClass方法获得这个类的类类型:
Class cl=stu.getClass();(2)静态方法forName
forName是Class的一个静态方法,如果没有一个类的实例,但是知道这个类的名字,可以使用这个方法获得这个类的类类型:
Class cl=Class.forName("Student");如果类名保存在字符串中,并可在运行中改变,就可以使用这个方法。当然,这个方法只有在参数是类名或接口名的时候才能够执行,否则forName方法将抛出一个异常。因此,使用这个方法时,应该处理这个异常。
(3).class
第三个获得类类型 的方法很简单。如果T是任意的Java类型,T.class将代表匹配的类对象。比如:
Class cl1=Student.class; Class cl2=int.class Class cl3=Double[].class
虚拟机为每个类型管理一个Class对象。因此可以使用==运算符实现两个类对象的比较:
if(stu.getClass()==Student.class)...
Class中最常用的一个方法就是getName方法,这个方法将返回类的名字。
还可以通过类类型创建一个类的实例。比如:
Class cl=Student.class; Student stu=(Student)cl.newInstance();
使用forName和newInstance方法可以根据存储在字符串中的类名创建一个实例:
String s="java.util.Date"; Object date=Class.forName(s).newInstance();
在第一节中,Class类的forName方法可能会抛出一个异常,可以使用下面的代码捕获并处理异常:
try{ String name=...;//get class name Class cl=Class.forName(name);//might throw exception do something with cl }catch(Exception e){ e.printStackTrace(); }
下面简要的介绍一下反射机制最重要的内容:检查类的结构。
在java.lang.reflect包中有三个类Field、Method和Constructor分别描述类的域、方法和构造器。这三个类都有一个叫getName的方法,用来返回项目的名称。Field类有一个getType方法,用来返回描述域所属的Class对象。Method和Constructor类有能够报告参数类型的方法,Method还有一个可以报告返回类型的方法。这三个类有一个getModifiers的方法,它将返回一个整数值,用不同的位开关描述public和static这样的修饰符的使用状况,还可以利用Modifier.toString方法将修饰符打印出来。
Class类的getFields、getMethods和getCostructors方法可以获得类提供的public域、方法和构造器数组,其中包括超类的共有成员。Class类的getDeclatedFields、getDeclatedMethods和getDeclaredConstructors方法可以获得类中声明的全部域、方法和构造器,其中包括私有和受保护的成员,但不包括超类的成员。
Constructor类:
Field类:
Method类:
下面的代码编写了一个可以打印一个类的所有构造器、方法和域的相关信息的类:
package reflection; import java.lang.reflect.*; public class PrintClassInfo { private PrintClassInfo(){} public static void printClassInfo(String name){ try{ Class cl=Class.forName(name); Class supercl=cl.getSuperclass(); String modifiers=Modifier.toString(cl.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.print("class "+name); if(supercl!=null&&supercl!=Object.class)System.out.print(" extends "+supercl.getName()); System.out.print("\n{\nConstructors:\n"); printConstructors(cl); System.out.println("Methods:"); printMethods(cl); System.out.println("Fields:"); printFields(cl); System.out.println("}"); }catch(ClassNotFoundException e){ e.printStackTrace(); } System.exit(0); } public static void printConstructors(Class cl){ Constructor[] cons=cl.getConstructors(); for(Constructor c:cons){ String name=c.getName(); System.out.print(" "); String modifiers=Modifier.toString(c.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.print(name+"("); Class[] paramTypes=c.getParameterTypes(); for(int i=0;i<paramTypes.length;i++) { if(i>0)System.out.print(", "); System.out.print(paramTypes[i].getName()); } System.out.println(");"); } } public static void printMethods(Class cl){ Method[] methods=cl.getDeclaredMethods(); for(Method m:methods){ Class retType=m.getReturnType(); String name=m.getName(); System.out.print(" "); String modifiers=Modifier.toString(m.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.print(retType.getName()+" "+name+"("); Class[] paramTypes=m.getParameterTypes(); for(int i=0;i<paramTypes.length;i++){ if(i>0)System.out.print(", "); System.out.print(paramTypes[i].getName()); } System.out.println(");"); } } public static void printFields(Class cl){ Field[] fields=cl.getDeclaredFields(); for(Field f:fields){ Class type=f.getType(); String name=f.getName(); System.out.print(" "); String modifiers=Modifier.toString(f.getModifiers()); if(modifiers.length()>0)System.out.print(modifiers+" "); System.out.println(type.getName()+" "+name+";"); } } }
String name; System.out.println("Enter the class name:"); Scanner scanner=new Scanner(System.in); name=scanner.next(); PrintClassInfo.printClassInfo(name);
public final class java.lang.String
{
Constructors:
public java.lang.String([B, int, int);
public java.lang.String([B, java.nio.charset.Charset);
public java.lang.String([B, java.lang.String);
public java.lang.String([B, int, int, java.nio.charset.Charset);
public java.lang.String([B, int, int, java.lang.String);
public java.lang.String(java.lang.StringBuilder);
public java.lang.String(java.lang.StringBuffer);
public java.lang.String([B);
public java.lang.String([I, int, int);
public java.lang.String();
public java.lang.String([C);
public java.lang.String(java.lang.String);
public java.lang.String([C, int, int);
public java.lang.String([B, int);
public java.lang.String([B, int, int, int);
Methods:
public boolean equals(java.lang.Object);
public java.lang.String toString();
public int hashCode();
public int compareTo(java.lang.String);
public volatile int compareTo(java.lang.Object);
public int indexOf(java.lang.String, int);
public int indexOf(java.lang.String);
public int indexOf(int, int);
public int indexOf(int);
static int indexOf([C, int, int, [C, int, int, int);
static int indexOf([C, int, int, java.lang.String, int);
public static java.lang.String valueOf(int);
public static java.lang.String valueOf(long);
public static java.lang.String valueOf(float);
public static java.lang.String valueOf(boolean);
public static java.lang.String valueOf([C);
public static java.lang.String valueOf([C, int, int);
public static java.lang.String valueOf(java.lang.Object);
public static java.lang.String valueOf(char);
public static java.lang.String valueOf(double);
public char charAt(int);
private static void checkBounds([B, int, int);
public int codePointAt(int);
public int codePointBefore(int);
public int codePointCount(int, int);
public int compareToIgnoreCase(java.lang.String);
public java.lang.String concat(java.lang.String);
public boolean contains(java.lang.CharSequence);
public boolean contentEquals(java.lang.CharSequence);
public boolean contentEquals(java.lang.StringBuffer);
public static java.lang.String copyValueOf([C);
public static java.lang.String copyValueOf([C, int, int);
public boolean endsWith(java.lang.String);
public boolean equalsIgnoreCase(java.lang.String);
public static transient java.lang.String format(java.util.Locale, java.lang.String, [Ljava.lang.Object;);
public static transient java.lang.String format(java.lang.String, [Ljava.lang.Object;);
public void getBytes(int, int, [B, int);
public [B getBytes(java.nio.charset.Charset);
public [B getBytes(java.lang.String);
public [B getBytes();
public void getChars(int, int, [C, int);
void getChars([C, int);
private int indexOfSupplementary(int, int);
public native java.lang.String intern();
public boolean isEmpty();
public static transient java.lang.String join(java.lang.CharSequence, [Ljava.lang.CharSequence;);
public static java.lang.String join(java.lang.CharSequence, java.lang.Iterable);
public int lastIndexOf(int);
public int lastIndexOf(java.lang.String);
static int lastIndexOf([C, int, int, java.lang.String, int);
public int lastIndexOf(java.lang.String, int);
public int lastIndexOf(int, int);
static int lastIndexOf([C, int, int, [C, int, int, int);
private int lastIndexOfSupplementary(int, int);
public int length();
public boolean matches(java.lang.String);
private boolean nonSyncContentEquals(java.lang.AbstractStringBuilder);
public int offsetByCodePoints(int, int);
public boolean regionMatches(int, java.lang.String, int, int);
public boolean regionMatches(boolean, int, java.lang.String, int, int);
public java.lang.String replace(char, char);
public java.lang.String replace(java.lang.CharSequence, java.lang.CharSequence);
public java.lang.String replaceAll(java.lang.String, java.lang.String);
public java.lang.String replaceFirst(java.lang.String, java.lang.String);
public [Ljava.lang.String; split(java.lang.String);
public [Ljava.lang.String; split(java.lang.String, int);
public boolean startsWith(java.lang.String, int);
public boolean startsWith(java.lang.String);
public java.lang.CharSequence subSequence(int, int);
public java.lang.String substring(int);
public java.lang.String substring(int, int);
public [C toCharArray();
public java.lang.String toLowerCase(java.util.Locale);
public java.lang.String toLowerCase();
public java.lang.String toUpperCase();
public java.lang.String toUpperCase(java.util.Locale);
public java.lang.String trim();
Fields:
private final [C value;
private int hash;
private static final long serialVersionUID;
private static final [Ljava.io.ObjectStreamField; serialPersistentFields;
public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
}
值得注意的是,这个程序可以分析Java解释器能够加载的任何类,而不仅仅是编译程序时可以使用的类。
类的加载有两种方式,一个是在编译时加载的静态加载类,另一个是在运行时加载的动态加载类。当我们使用new来创建一个对象时,使用的是静态加载类,这个时候必须保证类已经实现。而Class的forName方法不但可以获得一个类的类类型,还是一个动态加载类的方法,可以越过编译,在运行时加载一个类。
动态加载有什么好处么?考虑一个场景,比如自己要实现一个办公软件Office,里面有各种组件Word和Excel等。有一个Office工具可以启动各种组件,由于现阶段只有两个组件Word和Excel,那么Office可以这样编写:
class Office { public static void main(String[] args) { if("Word".equals(args[0])) { Word w=new Word(); w.start(); } if("Excel".equals(args[0])) { Excel e=new Excel(); e.start(); } } }
这里使用的就是静态加载,因为使用new创建的一个对象。不过,如果开发Word的组工作较快,已经完成了开发,代码假如如下:
class Word { public void start() { System.out.println("Word running..."); } }
说找不到Excel类。这样,整个的Office都不能使用了。
这时,就可以使用动态加载了。使用Class的forName方法动态加载一个类:
Word w=(Word)Class.forName(args[0]);
interface OfficeAble { void start(); }
class Word implements OfficeAble { public void start() { System.out.println("Word running..."); } }这样,在OfficeBetter类中就可以使用接口了:
class OfficeBetter { public static void main(String[] args) { try { Class cl=Class.forName(args[0]); OfficeAble oa=(OfficeAble)cl.newInstance(); oa.start(); } catch(Exception e) { e.printStackTrace(); } } }注意,forName会抛出异常,要进行捕获并处理。
这时,编译Word和OfficeBetter:
编译没有错误了,运行也正确。
当启动Excel时就会报错:
说没有找到Excel类。
当Excel实现完后,只需要编译Excel,不需要编译OfficeBetter就可以运行:
结果正确。这样,使用动态加载类,就可以随时添加功能而不需要重新编译。
现在已经知道了如何查看任意对象的数据域名称和类型:
(1)获得对应的类类型;
(2)通过类类型调用getDeclaredFields方法;
利用反射机制可以查看在编译时还不清楚的对象域。
查看对象域的关键方法是Field类中的get方法。如果f是一个Field类型的对象,obj是某个包含f域的类的对象,f.get(obj)将返回一个对象,其值为obj与的当前值。
既然能够得到域的值,那么也就能设置域的值。可以使用Field类中的set方法设置域的值,用法为f.set(obj,value),obj是包含f域的对象,value是要设置的值。
还要注意,如果f是一个私有域,那么直接使用get方法会抛出一个IllegalAccessException异常。除非拥有访问权限,否则Java安全机制只允许查看任意对象有哪些域而不允许得去域的值。
反射机制的默认行为受限于Java的访问控制。然而,如果一个Java程序没有受到安全管理器的控制,就可以覆盖访问控制。可以调用Field、Method或Constructor类中的setAccessible方法达到这个目的:
f.setAccessible(true);
setAccessible方法是AccessibleObject类中的一个方法,它是Field、Method和Constructor类的超类。
下面的代码演示了使用get和set方法获得和设置域的值:
Student stu=new Student("Bai",20,99); Class cl=stu.getClass(); Field f=cl.getDeclaredField("name"); f.setAccessible(true); String stuname=(String)f.get(stu); System.out.println("The name is:"+stuname); f.set(stu, "Liu"); System.out.println("The name is:"+(String)f.get(stu));其中Student类包含String类型的name、int类型的age和double类型的score。
运行结果如下:
The name is:Bai
The name is:Liu
即成功获得和设置域的值。
不过get还有一个问题,就是name是一个String,因此把它当做Object返回没有问题。但是如果要返回double的score,double不是对象,这时可以使用Field类中的getDouble方法。此时,反射机制会自动将这个域值打包到相应的对象包装器中。
Field fs=cl.getDeclaredField("score"); fs.setAccessible(true); System.out.println("The score is:"+fs.getDouble(stu));
The score is:99.0
在C和C++中,可以从函数指针执行任意函数。虽然Java没有提供函数指针,但反射机制允许调用任意方法。
和Field中的get方法类似,Method类中有个invoke方法,它允许调用包装在当前Method对象中的方法。invoke方法的签名是:
Object invoke(Object obj,Object... args);
在一个类中,必须知道哪些信息才能唯一确定一个方法呢?
由于Java中可以有同名的方法,因此可以使用参数来进行区分。不过要注意,返回类型不能作为区分两个函数的依据。因此,在使用Method类中的getMethod和getDeclaredMethod方法时,要给出方法名和方法参数:
Method getMethod(String name,Class... parameterTypes)
import java.lang.reflect.*; public class InvokeTest { public static void main(String[] args) { A a=new A(); Class cl=a.getClass(); try { Method m1=cl.getMethod("add", new Class[]{String.class,String.class}); m1.invoke(a, new Object[]{"hello","world"}); Method m2=cl.getMethod("add", int.class,int.class); m2.invoke(a, 1,2); Method m3=cl.getMethod("add"); m3.invoke(a); Method m4=cl.getMethod("add", String.class,int.class); m4.invoke(null, "Liu",100); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class A{ public void add(){ System.out.println("do nothing..."); } public void add(int a,int b){ System.out.println(a+b); } public void add(String a,String b){ System.out.println(a.toUpperCase()+b.toUpperCase()); } public static void add(String name,int age){ System.out.println(name+" is "+age+" years old."); } }
第一种,构造一个Class数组:
Method m1=cl.getMethod("add", new Class[]{String.class,String.class});
Method m2=cl.getMethod("add", int.class,int.class);
这里要注意的是,对于静态方法,没有包含它的对象,这时第一个参数置为null即可。
运行结果如下:
HELLOWORLD
3
do nothing...
Liu is 100 years old.
我们知道,集合中的泛型使得一个集合中只能保存一种类型数据。比如ArrayList<String>只能保存String类型,如果代码中有add(20)就会报错,因为20是int类型,不能转换成String。那么泛型是在哪个阶段起作用呢?
泛型的使用在编写代码中起到了一种类型检查的作用。我们知道,泛型只是编译器的功能,Java虚拟机并没有泛型,也就是说,是不是如果绕过编译,就能在泛型中添加别的类型的数据呢?比如在ArrayList<String>中添加int数据。
下面的代码演示了这种情况:
import java.lang.reflect.*; import java.util.ArrayList; public class MethodDemo { public static void main(String[] args) { ArrayList list=new ArrayList(); ArrayList<String> list1=new ArrayList<>(); System.out.println(list.getClass()==list1.getClass()); } }
true
说明在编译阶段是去泛型化的。
接下来修改代码如下:
import java.lang.reflect.*; import java.util.ArrayList; public class MethodDemo { public static void main(String[] args) { ArrayList list=new ArrayList(); ArrayList<String> list1=new ArrayList<>(); list.add(10); list.add("hello"); list1.add("world"); //list1.add(20);ERROR:can't add int to ArrayList<String> try { Method m=list1.getClass().getDeclaredMethod("add", Object.class); m.invoke(list1, 20); System.out.println(list1.size()); System.out.println(list1); for(String s:list1){ System.out.print(s+" "); } } catch (Exception e) { // TODO: handle exception e.printStackTrace(); } } }
长度为2,内容也正确,说明我们跳过了编译阶段的类型检查,在运行时成功添加了int类型数据。不过,使用for循环遍历是报错了,因为20不是String类型数据。
通过这个例子,可以知道,泛型会在编译时去泛型化。同时可以通过反射在运行阶段添加数据。
java.lang.reflect包中的Array类可以动态创建数组。比如,可以将这个特性应用到Array类的copyOf方法实现中。
如果要给一个Student[]数组复制,可以先将Student[]转换为Object[]数组,比如这样:
public static Object[] badCopyOf(Object[] a,int newLength){ Object[] newArray=new Object[newLength]; System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength)); return newArray; }不过,这样的话,返回的类型是Object[],而Object[]不能转化为Student[]类型。
为了编写通用的方法,需要创建一个与原数组类型相同的新数组。为此,需要java.lang.reflect包中的Array类中的一些方法。
Array类中有一个newInstance方法,能够创建新数组。这个方法要两个参数,一个是数组的元素类型,另一个是数组的长度:
Object newArray=Array.newInstance(componentType,newLength);这样,就需要获得数组元素的类型和数组的长度。使用Array类中的getComponentType方法可以获得元素的类型,使用Array类中的getLength方法可以获得数组的长度。
下面是完整的代码:
import java.lang.reflect.*; import java.util.Arrays; public class CopyOfTest { public static void main(String[] args) { int[] a={1,2,3}; a=(int[])goodCopyOf(a,10); System.out.println(Arrays.toString(a)); String[] b={"Tom","Dick","Harry"}; b=(String[])goodCopyOf(b,10); System.out.println(Arrays.toString(b)); b=(String[])badCopyOf(b,10); } public static Object[] badCopyOf(Object[] a,int newLength){ Object[] newArray=new Object[newLength]; System.arraycopy(a, 0, newArray, 0, Math.min(a.length, newLength)); return newArray; } public static Object goodCopyOf(Object a,int newLength){ Class cl=a.getClass(); if(!cl.isArray())return null; Class componentType=cl.getComponentType(); int length=Array.getLength(a); Object newArray=Array.newInstance(componentType, newLength); System.arraycopy(a, 0, newArray, 0, Math.min(length, newLength)); return newArray; } }
结果如下:
注意,应该将goodCopyOf的参数声明为Object类型,而不要声明成Object[]。因为正数数组类型int[]可以转换成Object,但不能转换成对象数组。
标签:
原文地址:http://blog.csdn.net/u012877472/article/details/51084965