12.1 反射概述
Java的反射(reflection)机制是指在程序的运行状态中,可以构造任意一个类的对象,可以得到任意一个对象所属的类的信息,可以调用任意一个类的成员变量和方法,可以获取任意一个对象的属性和方法。这种动态获取程序信息以及动态调用对象的功能称为Java语言的反射机制。
反射机制的优点是可以实现动态创建对象和编译(即动态编译),特别是在J2EE的开发中,反射的灵活性表现的十分明显。例如,一个大型的软件,不可能一次就把程序设计的很完美,当这个程序编译、发布上线后,如果需要更新某些功能,我们不可能要用户把以前的软件卸载,再重新安装新的版本。这时,如果采用静态编译,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制,程序可以在运行时动态的创建和编译对象,不需要用户重新安装软件,即可实现功能的更新。
12.2 认识Class类
Java程序的运行机制,JVM编译.java文件生成对应的.class文件,然后再将.class文件加载到内存中执行。在执行.class文件的时候可能需要用到其他类(其他.class文件内容),这个时候就需要获取其他类的信息(反射)。JVM在加载.class文件时,会产生一个java.lang.Class对象代表该.class字节码文件,从该Class对象中可以获得类的信息。因此要想完成反射操作,就必须先认识Class类。
Class是JDK定义的类,它提供了很多方法,通过调用Class类的成员方法可以获取Class对象中的信息(.class文件中的类信息)。Class类的常用方法如下表。
方法 | 描述 |
---|---|
public static Class<?> forName(String className) throws ClassNotFoundException | 传入完整的“包.类”名称实例化Class对象 |
public Constructor[] getConstructors() throws SecurityException | 得到一个类中的全部构造方法 |
public Field[] getDeclaredFields() throws SecurityException | 得到本类中单独定义的全部属性 |
public Field[] getFields() throws SecurityException | 取得本类继承而来的全部属性 |
方法 | 描述 |
---|---|
public Method[] getMethods() throws SecurityException | 得到一个类中的全部方法 |
public Method getMethod(String name,Class...parameter Type)throws NoSuchMethodException,SecurityException | 返回一个Method对象,并设置一个方法中的所有参数类型 |
public Class[] getInterfaces() | 得到一个类中所实现的全部接口 |
public String getName() | 得到一个类完整的“包.类”名称 |
public Package getPackage() | 得到一个类的包 |
public Class getSuperclass() | 得到一个类的父类 |
public Object newInstance() throws InstantiationException,IllegalAccessException | 根据Class定义的类实例化对象 |
public Class<?> getComponentType() | 返回表示数组类型的Class |
public boolean isArray() | 判断此Class是否是一个数组 |
由于Class对象代表的是.class文件(类),因此可以说所有的类实际上都是Class类的实例,所有的对象都可以转变为Class类型表示。
实例化Class对象共有以下三种方式:
(1)根据类名获取:类名.class;
(2)根据对象获取:对象.getClass();
(3)根据全限定类名获取:Class.forName(“全限定类名”)。
Class类本身没有定义任何的构造方法,所以如果要使用Class类,必须通过上述三种方式进行实例化。接下来,通过一个案例演示Class类的上述三种实例化方式。
class A{
}
class Example01 {public static void main(String args[]){Class<?> c1 = null;Class<?> c2 = null;Class<?> c3 = null;try{ c1 = Class.forName("cn.itcast.A");}catch(ClassNotFoundException e){e.printStackTrace();}c2 = new A().getClass();c3 = A.class;System.out.println("类名称:"+c1.getName());System.out.println("类名称:"+c2.getName());System.out.println("类名称:"+c3.getName());}
}
上述代码中,第9行代码使用forName()方法实例化Class对象c1,第13行代码使用对象.getClass()的方式实例化Class对象c2,第14行代码使用类名.class的方式实例化Class对象c3。从图12-3的运行结果可以发现,3种实例化Class对象的结果是一样的,但是使用forName()方法实例化Class对象只需要将类的全限定类名以字符串作为参数传入即可,这让程序具备了更大的灵活性,所以使用forName()方法实例化Class对象是较为常用的一种方式,读者应重点掌握。
12.3 Class类的使用
了解了Class类的实例化过程,那么到底该如何去使用Class类呢?实际上Class在开发中最常见的用法就是将Class类对象实例化为自定义类对象,即可以通过一个给定的字符串(类的全限定类名)实例化一个本类的对象。将Class对象实例化为本类对象时,可以通过无参构造完成,也可以通过有参构造完成。
12.3.1 通过无参构造函数实例化对象
如果想通过Class类实例化其他类的对象,则可以使用newInstance()方法,但是必须要保证被实例化的类中存在一个无参构造方法。接下来通过一个案例演示Class类通过无参构造实例化对象。
class Person{private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "姓名:"+this.name+",年龄:"+this.age;}
}
class Example02 {public static void main(String args[]){Class<?> c = null; try{c = Class.forName("cn.itcast.Person");}catch(ClassNotFoundException e){e.printStackTrace();}Person per = null;try{per = (Person)c.newInstance();}catch (Exception e){e.printStackTrace();}per.setName("张三");per.setAge(30);System.out.println(per);}}
上述代码中,第1~19行代码创建了一个Person类,在Person类中定义了name和age属性。第24行代码是通过Class.forName()方法实例化Class对象,第30行代码是使用Class对象c调用newInstance()方法并传入的完整“包.类”名称,对Person对象进行实例化操作。
在使用newInstance()方法实例化类对象时,被实例化对象的类中必须存在无参构造方法,否则无法实例化对象。
接下来演示没有无参构造方法时,通过newInstance()方法实例化对象。
class Person{private String name;private int age;public Person(String name,int age){ //定义有参构造方法this.setName(name);this.setAge(age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "姓名:"+this.name+",年龄:"+this.age;}
}
class Example03 {public static void main(String args[]){Class<?> c = null; try{c = Class.forName("cn.itcast.Person");}catch(ClassNotFoundException e){e.printStackTrace();}Person per = null;try{per = (Person)c.newInstance();}catch (Exception e){e.printStackTrace();}}
}
因为Person类中并没有存在无参构造方法,所以第35行代码对Person对象进行实例化时,无法直接使用newInstance()方法实例化的。由运行结果可知,报错信息提示Person类中没有发现无参构造方法,无法使用newInstance()方法实例化Person对象。因此,在使用Class类实例化对象时一定要在类中编写无参构造方法。
12.3.2 通过有参构造实例化对象
如果类中没有无参构造方法,则可以通过有参构造方法实例化对象。通过有参构造方法实例化对象时,需要明确调用的构造方法,并传递相应的参数。通过有参构造方法实例化对象的操作步骤如下:
(1)通过Class类中的getConstructors()方法获取本类中的全部构造方法。
(2) 向构造方法中传递一个对象数组,对象数组里面包含构造方法中所需的各个参数。
(3)通过Constructor类实例化对象。
上述操作步骤中使用了Constructor类,Constructor类用于存储本类的构造方法。Constructor类的常用方法如下表。
方法 | 描述 |
---|---|
publicint getModifiers() | 得到构造方法的修饰符 |
public String getName() | 得到构造方法的名称 |
public Class<?>[] getParameterTypes() | 得到构造方法中参数的类型 |
public String toString() | 返回此构造方法的信息 |
public T newInstance(Object...initargs) throws InstantistionException, IllegalAccessException,IllegalArgumentException, InvocationTargetException | 向构造方法中传递参数,实例化对象 |
import java.lang.reflect.Constructor;
class Person{private String name;private int age;public Person(String name,int age){this.setName(name);this.setAge(age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "姓名:"+this.name+",年龄:"+this.age;}
}
class Example04 {public static void main(String args[]){Class<?> c = null; try{c = Class.forName("cn.itcast.Person");}catch(ClassNotFoundException e){e.printStackTrace();}Person per = null;Constructor<?> cons[] = null;cons = c.getConstructors();try{per = (Person)cons[0].newInstance("张三",30);}catch (Exception e){e.printStackTrace();}System.out.println(per);}
}
上述代码中,第5~8行代码定义了Person类的有参构造方法。第34~35行代码通过Class类取得了Person类中全部构造方法,并以对象数组的形式返回。第27行代码调用了Person类中的构造方法,而在Person类中只有一个构造方法,所以直接取出对象数组中的第一个元素即可(下表为0就表示调用第一个构造方法)。在声明对象数组时,必须考虑到构造方法中参数的类型顺序,所以第一个参数的类型为String,第二个参数的类型为Integer。
12.4 反射的作用
在实际开发中,通过反射可以得到一个类的完整结构,包括类的构造方法、类的属性、类的方法,这就需要使用到java.lang.reflect包中的以下几个类:
(1)Constructor:表示类中的构造方法。
(2)Field:表示类中的属性。
(3)Method:表示类中的方法。
Constructor、Field、Method都是AccessibleObject类的子类。
12.4.1 获取所实现的全部接口
要取得一个类所实现的全部接口,必须使用Class中的getInterfaces()方法。getInterfaces()方法声明如下:
public Class[] getInterfaces();
getInterfaces()方法返回一个Class类的对象数组,调用Class类中的getName()方法可以取得类的名称。
接下来通过一个案例讲解通过getInterfaces()方法获取一个类所实现的全部接口。
interface China{public static final String NATION = "CHINA";public static final String AUTHOR = "张三";
}
class Person implements China{private String name;private int age;public Person(String name,int age){this.setName(name);this.setAge(age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "姓名:"+this.name+",年龄:"+this.age;}
}
public class Example05 {public static void main(String args[]){Class<?> c = null;
try{c = Class.forName("cn.itcast.Person");
}catch(ClassNotFoundException e){e.printStackTrace();
}
Class<?> cons[] = c.getInterfaces();
for (int i = 0;i < cons.length; i++){System.out.println("实现的接口名称:"+ cons[i].getName());
}
}
}
上述代码中,第1~4行代码定义了一个China接口,第5~27行代码定义了一个Person类并实现了China接口。因为接口是类的特殊形式,而且一个类可以实现多个接口,所以,第36~39行代码以Class数组的形式将全部的接口对象返回,并利用循环的方式将内容依次输出。由图12-6可知,Person类实现了China接口。
12.4.2 获取全部方法
要取得一个类中的全部方法,可以使用Class类中的getMethods()方法,该方法返回一个Method类的对象数组。如果想要进一步取得方法的具体信息,如方法的参数、抛出的异常声明等,就必须依靠Method类。
Method类的常用方法如下表。
方法 | 描述 |
---|---|
public int getModifiers() | 得到本方法的修饰符 |
public String getName() | 得到方法的名称 |
public Class<?>[] getParameterTypes() | 得到方法的全部参数的类型 |
public Class<?> getReturnType() | 得到方法的返回值类型 |
public Class<?>[] getExceptionType() | 得到一个方法的全部抛出异常 |
public T newInstance(Object...initargs) throws InstantistionException, IllegalAccessException,IllegalArgumentException, InvocationTargetException | 通过反射调用类中的方法 |
interface China{public static final String NATION = "CHINA";public static final String AUTHOR = "张三";public void sayChina();
}
class Person{private String name;private int age;public Person(String name,int age){this.setName(name);this.setAge(age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "姓名:"+this.name+",年龄:"+this.age;}
}
class Example06{public static void main(String args[]){Class<?> c = null;
try{c = Class.forName("cn.itcast.Person");
}catch(ClassNotFoundException e){e.printStackTrace();
}
Class<?> cons[] = c.getInterfaces();
for (int i = 0;i < cons.length; i++){System.out.println("实现的接口名称:"+ cons[i].getName());
}
}
}
上述代码中,第1~5行代码首先定义了一个China接口,并在China接口中定义了两个final修饰的String属性和sayChina()方法。第6~28行代码定义了一个Person类。最后在30~41行的main()方法中定义了一个Class的对象,通过Class对象调用forName()方法获取了“cn.itcast.Person”的所有方法。并定义了一个名称为cons[]的Class集合,用于存储Class的所有接口,最后使用for循还打印consp[]集合。
从运行结果可以发现,程序不仅将Person类的方法输出,也把从Object类中继承而来的方法同样进行了输出。
12.4.3 获取全部属性
在反射操作中也可以获取一个类中的全部属性,但是类中的属性包括两部分,从父类继承的属性,本类定义的属性。因此,在获取类的属性时也有两种不同的方式,分别如下:
(1)获取实现的接口或父类中的公共属性:public Field[] getFields throws SecurityException。
(2)获取本类中的全部属性:public Field[] getDeclaredFields throws SecurityException。
上述两种方法返回的都是Field数组,每一个Field对象表示类中的一个属性。如果要获取属性的详细信息,就需要调用Field类的方法。Field类的常用方法如下表。
方法 | 描述 |
---|---|
public int getModifiers() | 得到本方法的修饰符 |
public String getName() | 得到方法的名称 |
public boolean isAccessible() | 判断此属性是否可被外部访问 |
public void setAccessible(Boolean flag)throws SecurityException | 设置一个属性是否可被外部访问 |
public String toString() | 返回此Filed类的信息 |
public Object get(Object obj)throws IllegalArgument Exception,IllegalAccessException | 得到一个对象中属性的具体内容 |
public void set(Object obj, Object value)throws IllegalArgument Exception,IllegalAccessException | 设置指定对象中属性的具体内容 |
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
class Person{private String name;private int age;public Person(String name,int age){this.setName(name);this.setAge(age);}public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;}public String toString() {return "姓名:"+this.name+",年龄:"+this.age;}
}
public class Example07{public static void main(String[] args){Class<?> c1 = null;try{c1 = Class.forName("cn.itcast.Person");}catch (ClassNotFoundException e){e.printStackTrace();}{Field f[] = c1.getDeclaredFields(); //取得本类属性for (int i = 0;i<f.length;i++){ //循环输出Class<?> r = f[i].getType(); //取得属性的类型int mo = f[i].getModifiers(); //得到修饰符数字String priv = Modifier.toString(mo); //取得属性的修饰符System.out.print("本类属性:");System.out.print(priv+" "); //输出修饰符System.out.print(r.getName()+" "); //输出属性类型System.out.print(f[i].getName()); //输出属性名称System.out.println(" ;");}}}
}
上述代码中,第3~25行代码定义了一个Person类,并在Person类中定义了name和age属性。第30行代码实例化了一个Class对象c1;第35行代码通过调用Class类的getDeclaredFields()方法获取Person类的所有属性,并存入Filed数组中。第36~45行代码通过for循环输出Filed数组中Person类的属性。
【案例12-1】 重写toString()方法
为了方便输出对象,Object类提供了toString()方法。但是该方法的默认值是由类名和哈希码组成的,实用性并不强。通常需要重写该方法以提供更多的对象信息。
本案例要求使用反射重写类的toString()方法,并通过反射输出类的包名、类名、类的公共构造方法、类的公共域和类的公共方法。
(1)通过任务的描述可知,此程序需要利用反射重写toString()方法,因此,需要先创建一个类,并在该类中定义两个方法,一个是toString()方法,用于输出类的包、类的名字、类的公共构造方法、类的公共域和类的公共方法等信息;另一个是main()方法,用来进行测试。
(2)由于是重写Object类的toString()方法,因此需要给toString()方法传递一个Object对象。
(3)由于需要利用反射输出类的包、类的名字、类的公共构造方法、类的公共域和类的公共方法,故需要先通过Object对象.getClass()获得代表该类的Class对象,再通过类的Class对象. getPackage()获得类所在的包,通过类的Class对象.getSimpleName()获得类的简单名称,通过类的Class对象.getDeclaredConstructors()获得所有代表构造方法的Constructor数组,遍历数组,判断如果是访问控制符为“public”即为公共构造方法。通过类的Class对象.getDeclaredFields()获得代表所有域的Field数组,遍历数组,判断如果是访问控制符为“public”即为公共域。通过类的Class对象.getDeclaredMethods()获得代表所有方法的Method[]数组,遍历数组,判断如果是访问控制符为“public”即为公方法。
1 import java.lang.reflect.Constructor;2 import java.lang.reflect.Field;3 import java.lang.reflect.Method;4 import java.lang.reflect.Modifier;5 public class StringUtils {6 @SuppressWarnings("unchecked")7 public String toString(Object object) {8 // 获得代表该类的Class对象9 Class clazz = object.getClass();10 // 利用StringBuilder来保存字符串11 StringBuilder sb = new StringBuilder();12 // 获得类所在的包13 Package packageName = clazz.getPackage(); 14 // 输出类所在的包15 sb.append("包名:" + packageName.getName() + "\t"); 16 String className = clazz.getSimpleName(); // 获得类的简单名称17 sb.append("类名:" + className + "\n"); // 输出类的简单名称18 sb.append("公共构造方法:\n");19 // 获得所有代表构造方法的Constructor数组20 Constructor[] constructors = clazz.getDeclaredConstructors();21 for (Constructor constructor : constructors) {22 String modifier = 23 Modifier.toString(constructor.getModifiers());// 获得方法修饰符24 if (modifier.contains("public")) {// 查看修饰符是否含“public”25 sb.append(constructor.toGenericString() + "\n");26 }27 }28 sb.append("公共域:\n");29 // 获得代表所有域的Field数组30 Field[] fields = clazz.getDeclaredFields();31 for (Field field : fields) {32 String modifier = Modifier.toString(field.getModifiers());33 if (modifier.contains("public")) {// 查看修饰符是否含“public”34 sb.append(field.toGenericString() + "\n");35 }36 }37 sb.append("公共方法:\n");38 // 获得代表所有方法的Method[]数组39 Method[] methods = clazz.getDeclaredMethods();40 for (Method method : methods) {41 String modifier = Modifier.toString(method.getModifiers());42 // 查看修饰符是否含有“public”43 if (modifier.contains("public")) {44 sb.append(method.toGenericString() + "\n");45 }46 }47 return sb.toString();48 }49 public static void main(String[] args) {50 System.out.println(new StringUtils().toString(new Object()));51 }52 }
【案例12-2】 速度计算
本案例要求使用反射技术编写一个速度计算程序,计算某种交通工具的行驶速度。现有两种工具:Bike和 Plane,其中Bike的速度运算公式为:A*B/C,Plane的速度运算公式为:A+B+C。
用户可通过输入交通工具名称选择自己想要使用的交通工具,选择交通工具之后,自动计算出该交通工具的行驶速度。此外,在未来如果增加第3种交通工具的时候,不必修改以前的任何程序,只需要编写新的交通工具的程序即可。
(1)通过任务描述可知,有两种交通工具Plane和Bike:Plane类、Bike类。
(2)由于任务要求在未来如果增加第3种交通工具的时候,不必修改以前的任何程序,只需要编写新的交通工具的程序,故还需要编写一个接口Common,且Plane类和Bike类都继承Common接口。
(3)最后编写一个测试类CaculateSpeed,在main()方法中,编写程序,提示用户输入自己想要使用的交通工具,并利用反射来计算交通工具的速度。
1 public interface Common {2 double getSpeed(double a,double b,double c);3 }
4 public class Bike implements Common {5 @Override6 public double getSpeed(double a, double b, double c) {7 return a*b/c;8 }9 }
1 public class Plane implements Common {2 @Override3 public double getSpeed(double a, double b, double c) {4 return a+b+c;5 }6 }
1 import java.util.Scanner;2 public class CaculateSpeed {3 public static void main(String[] args){4 Scanner in = new Scanner(System.in);5 System.out.println("请输入您要使用的交通工具名称:");6 String choice =in.nextLine();7 String transport = "fanshe."+choice; //8 double a = 23, b = 24, c = 25;9 try {10 Common newtransport = (Common)11 Class.forName(transport).newInstance();12 System.out.println(choice+" speed is : 13 "+newtransport.getSpeed(a,b,c));14 } catch (InstantiationException e) {15 e.printStackTrace();16 } catch (IllegalAccessException e) {17 e.printStackTrace();18 } catch (ClassNotFoundException e) {19 e.printStackTrace();20 }21 }22 }
【案例12-3】:利用反射实现通过读取配置文件对类进行实例化
现在有一个项目,项目中创建了一个Person类,在Person类中定义了一个sleep()方法。在工程中还定义了一个Student类继承Person类,在Student类中重写了Person类的sleep()方法。项目有一个配置文件,名称为test.properties,在配置文件中配置了一个className属性和一个methodName属性,className属性值是类的全限定类名,methodName属性值是方法名。
本案例要求通过读取配置文件对类进行实例化,具体如下:
(1)获取test.properties配置文件中的className属性值(类的全限定类名),利用反射对该类进行实例化。
(2)获取test.properties配置文件中的methodName属性值(方法名),利用反射获取对象方法,并执行该方法。
(1)通过任务描述可知,需要先在工程的根目录下创建一个test.properties文件,在配置文件中配置一个className属性和一个methodName属性,className属性值是类的全限定类名,methodName属性值是方法名。
(2)然后创建两个类:Person类和Student类且Student类继承Person类。在Person类中编写一个sleep()方法,在Student类中重写Person类的sleep()方法;
(3)最后编写一个测试类ReflexTest,在main()方法中,编写程序,具体步骤描述如下:
1.利用反射加载配置文件
2.获取配置文件中的数据,获取类的全路径名及方法名
3.根据获取的类的全路径名,利用反射将该类加载进内存
4.创建该类对象
5.根据在配置文件中获取的方法名获取对象方法
6.执行方法
test.properties
1 className = fanshe.Person2 methodName = sleep
1 public class Person {2 public void sleep() {3 System.out.println("sleep......");4 }5 }
1 public class Student extends Person{2 @Override3 public void sleep() {4 super.sleep();5 System.out.println("呼噜呼噜~~~");6 }7 public void s1() {8 super.sleep();9 System.out.println("hello");10 }11 }
1 import java.io.IOException;2 import java.lang.reflect.InvocationTargetException;3 import java.lang.reflect.Method;4 import java.util.Properties;5 public class ReflexTest {6 public static void main(String[] args) throws IOException, 7 ClassNotFoundException, InstantiationException, IllegalAccessException,8 NoSuchMethodException, SecurityException, IllegalArgumentException, 9 InvocationTargetException {10 /*1、加载配置文件11 * 用类名.class.getResourceAsStream("/xx")或者12 * 类名.class.getClassLoader().getResourceAsStream("xx");13 * 区别在于前者是需要反斜杠,后者不需要14 * */15 Properties properties = new Properties(); 16 properties.load(RelectTestMain.class.getResourceAsStream("/test.prope17 rties"));18 //2、获取配置文件中定义的数据19 String className = properties.getProperty("className");20 String methodName = properties.getProperty("methodName");21 //3、加载该类进内存22 Class cls = Class.forName(className);23 //4、创建类对象24 Object obj = cls.newInstance();25 //5、获取对象方法26 Method method = cls.getMethod(methodName);27 //6、执行方法28 method.invoke(obj);29 }30 }
只需要修改test.properties配置文件即可。
test.properties
1 sclassName = fanshe.Student2 methodName = s1
12.4 本章小结
本章主要介绍了Java的反射机制。首先简单介绍了反射机制;然后介绍了Class类和Class类的应用;最后介绍了反射的应用,包括获取接口、获取方法、获取属性。通过本章的学习,读者对Java的反射会有一定的了解,掌握好这些知识,对以后的实际开发大有裨益。