JAVA中反射是动态获取信息以及动态调用对象方法的一种反射机制。
Java反射就是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;并且能改变它的属性。而这也是Java被视为动态语言的一个关键性质。Java反射的功能是在运行时判断任意一个对象所属的类,在运行时构造任意一个类的对象,在运行时判断任意一个类所具有的成员变量和方法,在运行时调用任意一个对象的方法,生成动态代理。反射是Java中最为重要的特性,几乎所有的开发框架以及应用技术都是基于反射技术的应用。
在一个程序中如果要通过对象取得这个对象所在类的信息,可以通过Object类中的getClass()方法(public final Class>getClass())实现。
例:反射初步操作
package Project.Study.Reflect;
import java.util.Date;
public class Test1 {
public static void main(String[]args){
Date date=new Date(); //产生实例化对象
System.out.println(date.getClass());//直接反射输出对应类的信息
}
}
//结果:
//class java.util.Date
当我们使用getClass()方法时,返回的类型为java.lang.Class,这是反射操作的源头类,即所有的反射操作都需要从此类开始,而最关键的是这个类有以下3种实例化方式。
该方法往往出现在简单Java类与提交参数的自动赋值操作中,像Struts、Spring MVC都会提供表单参数与简单Java类的自动转换。
例:
package Project.Study.Reflect;
import java.util.Date;
public class Test2 {
public static void main(String[]args){
Date date=new Date(); //产生实例化对象
Class<?>cls=date.getClass(); //通过实例化对象取得Class对象
//Class类中定义有“public String getName()”方法可以取得类的完整名称
System.out.println(cls.getName()); //直接对象所在类的名称
}
}
//结果:
//java.util.Date
该方式往往是将反射操作的类型设置交由用户使用,像Hibernate中进行数据保存以及根据ID查询中会使用到此类操作。
例:
package Project.Study.Reflect;
public class Test3 {
public static void main(String[]args){
Class<?>cls=java.util.Date.class; //通过类名称取得Class类对象
System.out.println(cls.getName()); //直接对象所在类的名称
}
}
//结果:
//java.util.Date
该方法可以实现配置文件以及Annotation配置的反射操作,几乎所有的开发框架都是依靠此方式实现的。
例:
package Project.Study.Reflect;
public class Test4 {
public static void main(String[]args) throws Exception {
//此处直接传递了一个要进行反射操作类的完整名称,是用字符串定义的
Class<?>cls=Class.forName("java.util.Date");
System.out.println(cls.getName()); //直接对象所在类的名称
}
}
//结果:
//java.util.Date
掌握了Class类对象实例化的三种操作方式,我们现在就可以利用Class类来进行类的反射控制了。
Class类的常用方法
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public static Class>forName(String className) throws ClassNotFoundException | 普通 | 通过字符串设置的类名称实例化Class类对象 |
2 | public Class>[] getInterfaces() | 普通 | 取得类实现的所有接口 |
3 | public String getName() | 普通 | 取得反射操作类的全名 |
4 | public String getSimpleName() | 普通 | 取得反射操作类名,不包括包名称 |
5 | public Package getPackage() | 普通 | 取得反射操作操作类所在的包 |
6 | public Class super T>getSuperclass() | 普通 | 取得反射操作类的父类 |
7 | public boolean isEnum() | 普通 | 反射操作的类是否是枚举 |
8 | public boolean isInterface() | 普通 | 反射操作的类是否是接口 |
9 | public boolean isArray() | 普通 | 反射操作的类是否是数组 |
10 | public T newInstance() throws InstantiationException,IllegalAccessException | 普通 | 反射实例化对象 |
在Class类中最重要的一个方法就是newInstance()方法,通过此方法可以利用反射实现Class类包装类型的对象实例化操作,也就是说即使不使用关键字new也可以进行对象的实例化操作。
注意:如果利用newInstance()方法反射实例化对象,则类中一定要提供无参构造方法,否则会出现语法错误。
例:利用反射实例化对象
package Project.Study.Reflect;
class Book{
public Book(){
System.out.println("该类的无参方法");
}
@Override
public String toString(){
return "Hello World";
}
}
public class Test5 {
public static void main(String[]args) throws Exception {
Class<?>cls=Class.forName("Project.Study.Reflect.Book");//设置要操作对象的类名称
//反射实例化后的对象返回的结果都是Object类型
Object object=cls.newInstance(); //相当于使用new调用无参结构
Book book=(Book)object; //向下转型
System.out.println(book);
}
}
//结果:
//该类的无参方法
//Hello World
上程序中的newInstance()方法返回的类型为Object,如果有需要则可以利用对象的向下转型操作将其强制变为子类实例进行操作。
可能会有人问使用newInstance()方法进行对象的实例化有什么好处,直接用new它不香吗?
其实因为关键字new实例化对象需要明确地指定类的构造方法,故new是造成耦合的最大元凶,所以使用newInstance()方法实例化对象可以实现更好的解耦合操作。
使用Class类中的newInstance()方法虽然可以实现反射实例化对象操作,但是这样的操作本身有一个限制,就是操作对象的类中必须提供无参的构造方法。
例:错误示例
package Project.Study.Reflect;
class Book2{
private String title;
private double price;
public Book2(String title,double price){
this.price=price;
this.title=title;
}
@Override
public String toString(){
return "图书名称:"+this.title+",价格:"+this.price;
}
}
public class Test6 {
public static void main(String[]args) throws Exception {
Class<?>cls=Class.forName("Project.Study.Reflect.Book2");
Object object=cls.newInstance();
System.out.println(object);
}
}
//结果:
//Exception in thread "main" java.lang.InstantiationException: Project.Study.Reflect.Book2
// at java.base/java.lang.Class.newInstance(Class.java:585)
// at Project.Study.Reflect.Test6.main(Test6.java:18)
//Caused by: java.lang.NoSuchMethodException: Project.Study.Reflect.Book2.()
// at java.base/java.lang.Class.getConstructor0(Class.java:3350)
// at java.base/java.lang.Class.newInstance(Class.java:572)
// ... 1 more
所以,当类中没有提供无参构造方法的时候,就必须通过java.lang.reflect.Constructor类来实现对象的反射实例化操作。(java.lang.reflect是所有反射操作类的程序包)
取得类中的构造方法
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Constructor>[] getConstructors() throws SecurityException | 普通 | 取得全部的构造方法 |
2 | public Constructor< T >[] getConstructors(Class>…parameterTypes) throws NoSuchMethodException, SecurityException | 普通 | 取得指定参数类型的构造方法 |
Constructor类的常用操作方法
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Class>[] getExceptionTypes() | 普通 | 返回构造方法上所有抛出异常的类型 |
2 | public int getModifiers() | 普通 | 取得构造方法的修饰符 |
3 | public String getName() | 普通 | 取得构造方法的名字 |
4 | public int getParameterCount() | 普通 | 取得构造方法中的参数个数 |
5 | public Class >[] getParameterTypes() | 普通 | 取得构造方法中的参数类型 |
6 | public T newInstance(Object…initargs) throws InstantiationException,IllegalAccessException,IllegalArgumentException,InvocationTargetException | 普通 | 调用指定参数的构造实例化对象 |
注意:因为修饰符是用数字描述的,所有的修饰符都是一个数字,所以取得修饰符的方法返回的是int类型。
例:明确调用类中的有参构造
package Project.Study.Reflect;
import java.lang.reflect.Constructor;
class Book3{
private String title;
private double price;
public Book3(String title,double price){
this.title=title;
this.price=price;
}
@Override
public String toString(){
return "图书名称:"+this.title+",价格:"+this.price;
}
}
public class Test6 {
public static void main(String[]args)throws Exception{
Class<?>cls=Class.forName("Project.Study.Reflect.Book3");
//明确地找到Book3类中两个参数的构造,第一个参数类型是String,第二个是double
Constructor<?>constructor=cls.getConstructor(String.class,double.class);
Object object=constructor.newInstance("Java",79.9); //实例化对象
System.out.println(object);
}
}
//结果:
//图书名称:Java,价格:79.9
上程序的思路:
1.首先利用Class类对象取得此构造方法(返回Constructor类对象);
2.然后利用Constructor类中的newInstance()方法传递指定的数据,就可以调用有参构造进行对象的反射实例化。
Class类取得普通方法的操作
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Method[] getMethods() throws SecurityException | 普通 | 取得类中的全部方法 |
2 | public Method[] getMethod(String name,Class>…parameterTypes) throws NoSuchMethodException,SecurityException | 普通 | 取得类中指定方法名称与参数类型的方法 |
注:反射操作中,每一个方法都通过java.lang.reflect.Method类表示。
Method类的常用方法
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public int getModifiers() | 普通 | 取得方法的修饰符 |
2 | public Class> getReturnType() | 普通 | 取得方法的返回值类型 |
3 | public int getParameterCount() | 普通 | 取得方法中定义的参数数量 |
4 | public Class> getParameterTypes() | 普通 | 取得方法中定义的所有参数类型 |
5 | public Object invoke(Object obj,Object…args) throws IllegalAccessException,IllegalArgumentException,InvocationTargetException | 普通 | 反射调用方法并且传递执行方法所需要的参数数据 |
6 | public Class> getExceptionTypes() | 普通 | 取得方法抛出的异常类型 |
例:使用反射操作简单Java类的属性
package Project.Study.Reflect;
import java.lang.reflect.Method;
class Book4{
private String title;
public void setTitle(String title){
this.title=title;
}
public String getTitle() {
return title;
}
}
public class Test7 {
public static void main(String[]args)throws Exception{
String fieldName="Title";
Class<?>cls=Class.forName("Project.Study.Reflect.Book4");
Object object=cls.newInstance();
//取得类中的setTitle()方法
Method setMet=cls.getMethod("set"+fieldName,String.class);
//取得类中的getTitle()方法,本方法不接收参数并且没有返回值类型说明
Method getMet=cls.getMethod("get"+fieldName);
setMet.invoke(object,"Java书"); //等价于Book4类对象.setTitle("Java书");
System.out.println(getMet.invoke(object)); //等价于Book4类对象.getTitle();
}
}
//结果:
//Java书
Class类中取得成员的操作
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Field[] getDeclaredFields() throws SecurityException | 普通 | 取得本类定义的全部成员 |
2 | public Field[] getDeclaredFields(String name) throws NoSuchFieldException,SecurityException | 普通 | 取得本类指定名称的成员 |
3 | public Field[] getFields() throws SecurityException | 普通 | 取得本类继承父类的全部成员 |
4 | public Field[] getField(String name) throws NoSuchFieldException,SecurityException | 普通 | 取得本类继承父类中指定名称的成员 |
Field类的常用方法
No. | 方法 | 类型 | 描述 |
---|---|---|---|
1 | public Class> getType() | 普通 | 取得改成员的类型 |
2 | public Object get(Object obj) throws IllegalAccessException,IllegalArgumentException | 普通 | 取得指定对象中的成员的内容,相当于直接调用成员 |
3 | public void set(Object obj,Object value) throws IllegalAccessException,IllegalArgumentException | 普通 | 设置指定对象中的成员内容,相当于直接利用对象调用成员设置内容 |
注意:不管使用反射调用的是普通方法还是调用类中的成员,都必须存在实例化对象(可以依靠反射取得实例化对象),因为类中的属性必须在类产生实例化对象后才可以使用。
例:利用反射直接操作私有成员
package Project.Study.Reflect;
import java.lang.reflect.Field;
class Book5{
private String title;
}
public class Test8 {
public static void main(String[]args)throws Exception{
Class<?>cls=Class.forName("Project.Study.Reflect.Book5"); //取得反射对象
Object object=cls.newInstance(); //必须给出实例化对象
Field titleField=cls.getDeclaredField("title"); //取得类中的title属性
titleField.setAccessible(true); //取消封装,使title属性可以被外界访问
titleField.set(object,"Java书"); //相当于:Book5类对象.title="数据"
System.out.println(titleField.get(object)); //相当于:Book5类对象.title
}
}
//结果:
//Java书