深入浅析Java反射机制
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。当然反射本身并不是一个新概念,它可能会使我们联想到光学中的反射概念,尽管计算机科学赋予了反射概念新的含义,但是,从现象上来说,它们确实有某些相通之处,这些有助于我们的理解。
Java反射机制主要提供下面几种用途:
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个类所具有的成员变量和方法
在运行时调用任意一个对象的方法
首先看一个简单的例子,通过这个例子来理解Java的反射机制是如何工作的。
packagecom.wanggc.reflection; importjava.lang.reflect.Method; /** *Java反射练习。 * *@authorWanggc */ publicclassForNameTest{ /** *入口函数。 * *@paramargs *参数 *@throwsException *错误信息 */ publicstaticvoidmain(String[]args)throwsException{ //获得Class Class<?>cls=Class.forName(args[0]); //通过Class获得所对应对象的方法 Method[]methods=cls.getMethods(); //输出每个方法名 for(Methodmethod:methods){ System.out.println(method); } } }
当传入的参数是java.lang.String时,会输出如下结果
publicbooleanjava.lang.String.equals(java.lang.Object) publicjava.lang.Stringjava.lang.String.toString() publicintjava.lang.String.hashCode() publicintjava.lang.String.compareTo(java.lang.String) publicintjava.lang.String.compareTo(java.lang.Object) publicintjava.lang.String.indexOf(int) publicintjava.lang.String.indexOf(int,int) publicintjava.lang.String.indexOf(java.lang.String) publicintjava.lang.String.indexOf(java.lang.String,int) publicstaticjava.lang.Stringjava.lang.String.valueOf(int) publicstaticjava.lang.Stringjava.lang.String.valueOf(char) publicstaticjava.lang.Stringjava.lang.String.valueOf(boolean) publicstaticjava.lang.Stringjava.lang.String.valueOf(float) publicstaticjava.lang.Stringjava.lang.String.valueOf(char[],int,int) publicstaticjava.lang.Stringjava.lang.String.valueOf(double) publicstaticjava.lang.Stringjava.lang.String.valueOf(char[]) publicstaticjava.lang.Stringjava.lang.String.valueOf(java.lang.Object) publicstaticjava.lang.Stringjava.lang.String.valueOf(long) publiccharjava.lang.String.charAt(int) publicintjava.lang.String.codePointAt(int) publicintjava.lang.String.codePointBefore(int) publicintjava.lang.String.codePointCount(int,int) publicintjava.lang.String.compareToIgnoreCase(java.lang.String) publicjava.lang.Stringjava.lang.String.concat(java.lang.String) publicbooleanjava.lang.String.contains(java.lang.CharSequence) publicbooleanjava.lang.String.contentEquals(java.lang.CharSequence) publicbooleanjava.lang.String.contentEquals(java.lang.StringBuffer) publicstaticjava.lang.Stringjava.lang.String.copyValueOf(char[]) publicstaticjava.lang.Stringjava.lang.String.copyValueOf(char[],int,int) publicbooleanjava.lang.String.endsWith(java.lang.String) publicbooleanjava.lang.String.equalsIgnoreCase(java.lang.String) publicstaticjava.lang.Stringjava.lang.String.format(java.lang.String,java.lang.Object[]) publicstaticjava.lang.Stringjava.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]) publicbyte[]java.lang.String.getBytes(java.lang.String)throwsjava.io.UnsupportedEncodingException publicvoidjava.lang.String.getBytes(int,int,byte[],int) publicbyte[]java.lang.String.getBytes() publicbyte[]java.lang.String.getBytes(java.nio.charset.Charset) publicvoidjava.lang.String.getChars(int,int,char[],int) publicnativejava.lang.Stringjava.lang.String.intern() publicbooleanjava.lang.String.isEmpty() publicintjava.lang.String.lastIndexOf(java.lang.String) publicintjava.lang.String.lastIndexOf(int,int) publicintjava.lang.String.lastIndexOf(int) publicintjava.lang.String.lastIndexOf(java.lang.String,int) publicintjava.lang.String.length() publicbooleanjava.lang.String.matches(java.lang.String) publicintjava.lang.String.offsetByCodePoints(int,int) publicbooleanjava.lang.String.regionMatches(boolean,int,java.lang.String,int,int) publicbooleanjava.lang.String.regionMatches(int,java.lang.String,int,int) publicjava.lang.Stringjava.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence) publicjava.lang.Stringjava.lang.String.replace(char,char) publicjava.lang.Stringjava.lang.String.replaceAll(java.lang.String,java.lang.String) publicjava.lang.Stringjava.lang.String.replaceFirst(java.lang.String,java.lang.String) publicjava.lang.String[]java.lang.String.split(java.lang.String) publicjava.lang.String[]java.lang.String.split(java.lang.String,int) publicbooleanjava.lang.String.startsWith(java.lang.String) publicbooleanjava.lang.String.startsWith(java.lang.String,int) publicjava.lang.CharSequencejava.lang.String.subSequence(int,int) publicjava.lang.Stringjava.lang.String.substring(int) publicjava.lang.Stringjava.lang.String.substring(int,int) publicchar[]java.lang.String.toCharArray() publicjava.lang.Stringjava.lang.String.toLowerCase() publicjava.lang.Stringjava.lang.String.toLowerCase(java.util.Locale) publicjava.lang.Stringjava.lang.String.toUpperCase() publicjava.lang.Stringjava.lang.String.toUpperCase(java.util.Locale) publicjava.lang.Stringjava.lang.String.trim() publicfinalnativevoidjava.lang.Object.wait(long)throwsjava.lang.InterruptedException publicfinalvoidjava.lang.Object.wait()throwsjava.lang.InterruptedException publicfinalvoidjava.lang.Object.wait(long,int)throwsjava.lang.InterruptedException publicfinalnativejava.lang.Classjava.lang.Object.getClass() publicfinalnativevoidjava.lang.Object.notify() publicfinalnativevoidjava.lang.Object.notifyAll()
这样就列出了java.lang.String类的所有方法名、及其限制符、返回类型及抛出的异常。这个程序使用Class类forName方法载入指定的类,然后调用getMethods方法返回指定类的方法列表。java.lang.reflect.Method用来表述某个类中的单一方法。
使用java的反射机制,一般需要遵循三步:
获得你想操作类的Class对象
通过第一步获得的Class对象去取得操作类的方法或是属性名
操作第二步取得的方法或是属性
Java运行的时候,某个类无论生成多少个对象,他们都会对应同一个Class对象,它表示正在运行程序中的类和接口。如何取得操作类的Class对象,常用的有三种方式:
调用Class的静态方法forName,如上例;
使用类的.class语法,如:Class<?>cls=String.class;
调用对象的getClass方法,如:Stringstr="abc";Class<?>cls=str.getClass();
下面将通过实例讲述如何通过前面所诉的三步来执行某对象的某个方法:
packagecom.wanggc.reflection; importjava.lang.reflect.Method; /** *Java反射练习。 * *@authorWanggc */ publicclassReflectionTest{ publicstaticvoidmain(String[]args)throwsException{ DisPlaydisPlay=newDisPlay(); //获得Class Class<?>cls=disPlay.getClass(); //通过Class获得DisPlay类的show方法 Methodmethod=cls.getMethod("show",String.class); //调用show方法 method.invoke(disPlay,"Wanggc"); } } classDisPlay{ publicvoidshow(Stringname){ System.out.println("Hello:"+name); } }
前面说过,Java程序的每个类都会有个Class对象与之对应。Java反射的第一步就是获得这个Class对象,如代码14行。当然,每个类的方法也必有一个Method对象与之对应。要通过反射的方式调用这个方法,就要首先获得这个方法的Method对象,如代码16行,然后用Method对象反过来调用这个方法,如代码18行。注意16行getMethod方法的第一个参数是方法名,第二个是此方法的参数类型,如果是多个参数,接着添加参数就可以了,因为getMethod是可变参数方法。执行18行代码的invoke方法,其实也就是执行show方法,注意invoke的第一个参数,是DisPlay类的一个对象,也就是调用DisPlay类哪个对象的show方法,第二个参数是给show方法传递的参数。类型和个数一定要与16行的getMethod方法一直。
上例讲述了如何通过反射调用某个类的方法,下面将再通过一个实例讲述如何通过反射给某个类的属性赋值:
packagecom.wanggc.reflection; importjava.lang.reflect.Field; /** *Java反射之属性练习。 * *@authorWanggc */ publicclassReflectionTest{ publicstaticvoidmain(String[]args)throwsException{ //建立学生对象 Studentstudent=newStudent(); //为学生对象赋值 student.setStuName("Wanggc"); student.setStuAge(); //建立拷贝目标对象 StudentdestStudent=newStudent(); //拷贝学生对象 copyBean(student,destStudent); //输出拷贝结果 System.out.println(destStudent.getStuName()+":" +destStudent.getStuAge()); } /** *拷贝学生对象信息。 * *@paramfrom *拷贝源对象 *@paramdest *拷贝目标对象 *@throwsException *例外 */ privatestaticvoidcopyBean(Objectfrom,Objectdest)throwsException{ //取得拷贝源对象的Class对象 Class<?>fromClass=from.getClass(); //取得拷贝源对象的属性列表 Field[]fromFields=fromClass.getDeclaredFields(); //取得拷贝目标对象的Class对象 Class<?>destClass=dest.getClass(); FielddestField=null; for(FieldfromField:fromFields){ //取得拷贝源对象的属性名字 Stringname=fromField.getName(); //取得拷贝目标对象的相同名称的属性 destField=destClass.getDeclaredField(name); //设置属性的可访问性 fromField.setAccessible(true); destField.setAccessible(true); //将拷贝源对象的属性的值赋给拷贝目标对象相应的属性 destField.set(dest,fromField.get(from)); } } } /** *学生类。 */ classStudent{ /**姓名*/ privateStringstuName; /**年龄*/ privateintstuAge; /** *获取学生姓名。 * *@return学生姓名 */ publicStringgetStuName(){ returnstuName; } /** *设置学生姓名 * *@paramstuName *学生姓名 */ publicvoidsetStuName(StringstuName){ this.stuName=stuName; } /** *获取学生年龄 * *@return学生年龄 */ publicintgetStuAge(){ returnstuAge; } /** *设置学生年龄 * *@paramstuAge *学生年龄 */ publicvoidsetStuAge(intstuAge){ this.stuAge=stuAge; } }
Java的发射机制中类有Class对应,类的方法有Method对应,当然属性也有Field与之对应。代码中注释已经做了详细的注释,在此不再赘述。但要注意,Field提供了get和set方法获取和设置属性的值,但是由于属性是私有类型,所以需要设置属性的可访问性为true,如代码50~51行。也可以在为整个fields设置可访问性,在40行下面使用AccessibleObject的静态方法setAccessible,如:AccessibleObject.setAccessible(fromFields,true);
前面讲述了如何用Java反射机制操作一个类的方法和属性,下面再通过一个实例讲述如何在运行时创建类的一个对象:
packagecom.wanggc.reflection; importjava.lang.reflect.Field; /** *Java反射之属性练习。 * *@authorWanggc */ publicclassReflectionTest{ publicstaticvoidmain(String[]args)throwsException{ //建立学生对象 Studentstudent=newStudent(); //为学生对象赋值 student.setStuName("Wanggc"); student.setStuAge(); //建立拷贝目标对象 StudentdestStudent=(Student)copyBean(student); //输出拷贝结果 System.out.println(destStudent.getStuName()+":" +destStudent.getStuAge()); } /** *拷贝学生对象信息。 * *@paramfrom *拷贝源对象 *@paramdest *拷贝目标对象 *@throwsException *例外 */ privatestaticObjectcopyBean(Objectfrom)throwsException{ //取得拷贝源对象的Class对象 Class<?>fromClass=from.getClass(); //取得拷贝源对象的属性列表 Field[]fromFields=fromClass.getDeclaredFields(); //取得拷贝目标对象的Class对象 Objectints=fromClass.newInstance(); for(FieldfromField:fromFields){ //设置属性的可访问性 fromField.setAccessible(true); //将拷贝源对象的属性的值赋给拷贝目标对象相应的属性 fromField.set(ints,fromField.get(from)); } returnints; } } /** *学生类。 */ classStudent{ /**姓名*/ privateStringstuName; /**年龄*/ privateintstuAge; /** *获取学生姓名。 * *@return学生姓名 */ publicStringgetStuName(){ returnstuName; } /** *设置学生姓名 * *@paramstuName *学生姓名 */ publicvoidsetStuName(StringstuName){ this.stuName=stuName; } /** *获取学生年龄 * *@return学生年龄 */ publicintgetStuAge(){ returnstuAge; } /** *设置学生年龄 * *@paramstuAge *学生年龄 */ publicvoidsetStuAge(intstuAge){ this.stuAge=stuAge; } }
此例和上例运行的结果是相同的。但是copyBean方法返回的对象不再是外面传入的,而是由方法内部产生的,如第40行代码所示。注意:Class的newInstance方法,只能创建只包含无参数的构造函数的类,如果某类只有带参数的构造函数,那么就要使用另外一种方式:fromClass.getDeclaredConstructor(int.class,String.class).newInstance(24,"wanggc");
至此,Java反射机制的常用机能(运行时调用对象的方法、类属性的使用、创类类的对象)已经介绍完了。
补充:在获得类的方法、属性、构造函数时,会有getXXX和getgetDeclaredXXX两种对应的方法。之间的区别在于前者返回的是访问权限为public的方法和属性,包括父类中的;但后者返回的是所有访问权限的方法和属性,不包括父类的。
以上内容就是给大家介绍的java发射机制,希望大家喜欢。