什么是反射 反射怎么用

Java中有许多对象在运行时都会出现两种类型:编译时类型和运行时类型。例如下面的语句:

Parent p = new Child();

变量p在编译时的类型为Parent,运行时类型为Child,体现了类的多态。另外,某些情况下程序又需要调用该对象运行时类的方法。为了解决类似的问题,程序需要在运行时发现对象和类的真实结构信息

在程序运行时获取对象的真实性息有以下两种做法:

  • 在知道对象的具体类型的情况下,可以先使用instanceof运算符进行判断,再利用类型强制转换将其转换成运行时的类型变量即可
  • 在无法预知该对象属于哪些类的情况下,必须通过反射来发现该对象和类的真实信息

反射(Reflection)机制允许程序在运行时借助Reflection API获取任何类的内部信息,并能直接操作对象的内部属性及方法。反射被视为动态语言的关键。

Java反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时获取任意一个类的所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
  • 生成动态代理

反射机制是实现很多流行框架的基础,如Spring,Hibernate等框架都采用反射机制。Reflection API提供了Constructor、Filed和Method类,这三个类定义在java.lang.reflect包中,分别用于描述类的构造方法、属性和方法。下面示例使用反射机制获取类对象的所有方法。

【示例】使用反射机制获取类对象的所有方法

Class c = Class.forName("java.lang.String");
//获取当前类对象的所有方法
Method[] mtds = c.getDeclaredMethods();
for (Method m : mtds)
    System.out.println(m);
}

上述代码首先使用Class.forName()方法来获取String类的Class对象;再调用getDeclaredMethods()方法获取String类的所有方法,该方法的返回类型是Method[]方法数组。

Executable抽象类

Executable抽象类代表可执行的类成员。Executable抽象类派生了Constructor和Method两个子类。Executable抽象类提供大量方法来获取参数、修饰符或注解等信息,常用方法如下

Executable类的方法
方法功能描述
Parameter[] getParameters()获取所有形参,返回一个Parameter[]数组
int getParameterCount()获取形参个数
abstract int getModifiers()获取修饰符,返回的整数是修饰符public、protected、private、final、static、abstract等关键字所对应的常量
boolean isVarArgs()判断是否包含数量可变的形参

Constructor类

Constructor类用于表示类的构造方法,通过调用Class对象的getConstructors()方法可以获取当前类的构造方法的集合。Constructor常用方法及使用说明如下

Constructor类的方法
方法功能描述
String getName()返回构造方法的名称
Class []getParameterTypes()返回当前构造方法的参数类型
intgetModifiers()返回修饰符的整型标识,返回的整数是修饰符public、protected、private、final、static、abstract等关键字所对应的常量,需使用Modifier工具类的方法解码后才能获得真实的修饰符

下述代码调用getConstructors()方法获取指定类的构造方法信息

ConstructorReflectionaDemo.java

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class ConstructorReflectionDemo {
    public static void main(String[] args) {
        try {
            //获取String类对象
            Class clazz = Class.forName("java.lang.String");
            //返回所有构造方法
            Constructor[] ctors = clazz.getDeclaredConstructors();
            //遍历构造方法
            for(Constructor c : ctors){
                //获取构造方法的修饰符
                int mod = c.getModifiers();
                //使用Modifier工具类的方法获得真实的修饰符,并输出
                System.out.print(Modifier.toString(mod));
                //获取构造方法的名称,并输出
                System.out.print(" " + c.getName() + "(");
                //获取构造方法的参数类型
                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(");");
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

上述代码通过反射操作,获取了java.lang.String类的所有构造方法及其参数信息。调用getModifiers()方法可以获取构造方法的修饰符,该方法的返回值是整数,即修饰符public、protected、private、final、static、abstract等关键字所对应的常量。此时引入Modifier类,通过调用Modifier.toString(int mod)方法,返回修饰符常量所对应的字符串。反之,可以通过下面语句来查看各修饰符的对应值

System.out.println(Modifier.PUBLIC);

查看java.lang.String类的API,与程序的运行结果对照,运行结果如下

public java.lang.String([B);
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);
 java.lang.String([C,int,int,java.lang.Void);
 java.lang.String(java.lang.AbstractStringBuilder,java.lang.Void);
public java.lang.String(java.lang.StringBuilder);
public java.lang.String(java.lang.StringBuffer);
 java.lang.String([B,byte);
public java.lang.String([C,int,int);
public java.lang.String([C);
public java.lang.String(java.lang.String);
public java.lang.String();
public java.lang.String([B,int,int,java.lang.String);
public java.lang.String([B,int);
public java.lang.String([B,int,int,int);
public java.lang.String([I,int,int);

Field类

Field类用于封装属性的信息,调用Class对象的getField()或getField()方法可以获取当前类的所有属性或指定属性。Filed类的常用方法如图

Filed类的常用方法
方法功能描述
String getName()返回属性的名称
intgetModifiers()返回修饰符的整型标识
getXxx(Object obj)获取属性的值,此处的Xxx对应Java8中的基本类型,如果属性是引用类型,则直接使用get(Object obj)方法
setXxx(Object obj,Xxx val)设置属性的值,此处的Xxx对应Java8中的基本类型,如果是属性是引用类型,则直接使用get(Object obj,Object val)方法
Class[]getType()返回当前属性的类型

下述代码调用getFileds()方法用于获取指定类的属性信息并显示

FieldDemo.java

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

class Person{
    private String name;
    private int age;
    private String address;

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", address='" + address + '\'' +
                '}';
    }
}
public class FieldDemo {
    public static void main(String[] args) {
        try {
            //获取Person类对应的Class对象
            Class<Person>personClazz = Person.class;
            System.out.println("-----Person类的属性-----");
            //返回声明的所有属性,包括私有的和受保护的,但不包括超类属性
            Field[] fields = personClazz.getDeclaredFields();
            for (Field f : fields){
                //获取属性的修饰符
                int mod = f.getModifiers();
                //使用Modifier工具类的方法获得真实的修饰符,并输出
                System.out.print(Modifier.toString(mod));
                //获取属性的类型,并输出
                Class type = f.getType();
                System.out.print(" " + type.getName());
                //获取属性的名称,并输出
                System.out.println(" " + f.getName());
            }
            //创建一个Person对象
            Person p = new Person();
            //使用getDeclaredField()方法表明可获取各种访问控制符的成员变量
            //获取Person类的name属性对象
            Field nameField = personClazz.getDeclaredField("name");
            //设置通过反射访问该成员变量时取消访问检查
            nameField.setAccessible(true);
            //为p对象的name属性设置值,因为String是引用类型,所以直接使用set()方法
            nameField.set(p,"张三");
            //获取Person类的age属性对象
            Field ageField = personClazz.getDeclaredField("age");
            //设置通过反射访问该成员变量时取消访问权限检查
            ageField.setAccessible(true);
            //调用setInt()方法为p对象的age属性设置值
            ageField.setInt(p,36);
            //获取Person类的address属性对象
            Field addressField = personClazz.getDeclaredField("address");
            //设置通过反射访问该成员变量时取消访问权限检查
            addressField.setAccessible(true);
            //为p对象的name属性设置值,因String是引用类型,所以直接使用set()方法
            addressField.set(p,"上海");
            //输出对象p的信息
            System.out.println("-----Person实例-----");
            System.out.println(p);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

上述代码定义了一个Person类,该类有name、age和address三个属性。在main()方法中先获取Person类所对应的Class对象,再获取该类的属性并显示,最后分别设置属性值并显示。

注意调用getDeclaredFields()方法可以获取包括私有和受保护的所有属性但不包括父类的属性;调用getFields()方法可以获取所有public属性包括从父类继承的公共属性

运行结果:

-----Person类的属性-----
private java.lang.String name
private int age
private java.lang.String address
-----Person实例-----
Person{name='张三', age=36, address='上海'}

Parameter类

Parameter类也是Java8新增的API,每个Parameter对象代表方法的一个参数。Parameter类中提供了许多方法来获取参数信息,常用方法如下

Parameter类的常用方法
方法功能描述
int getModifiers()获取参数的修饰符
String getName()获取参数的形参名
Type getParameterizedType()获取带泛型的新参类型
Class<?> getType()获取形参类型
boolean isVarArgs()判断该参数是否是可变参数
boolean isNamePresent()判断.class文件中是否包含方法的形参信息

使用javac命令编译Java源文件时,默认生成的.class文件不包含方法的形参名信息,因此调用getName()方法不能得到参数的形参名,调用isNamePresent()方法将返回false。如果希望javac命令编译Java源文件时保留形参信息,则需要为编译命令指定-parameters选项

下述代码演示Java8的方法参数反射功能

MethodParameterDemo.java

class MyClass{
    public void setName(String name){
    }
    public void display(String str, List<String> list){
    }
}
public class MethodParameterDemo {
    public static void main(String[] args) throws Exception{
        //获取MyClass的类对象
        Class<MyClass>clazz = MyClass.class;
        //获取MyClass类的所有public方法
        Method[] mtds = clazz.getMethods();
        for (Method m : mtds){
            //输出方法名
            System.out.println("方法名:" + m.getName());
            //输出该方法参数个数
            System.out.println("参数个数:" + m.getParameterCount());
            //获取该方法所有参数
            Parameter[] parameters = m.getParameters();
            int index = 1;
            //遍历所有参数,并输出参数信息
            for(Parameter p : parameters){
                if (p.isNamePresent()){
                    System.out.println("---第" + (index++) + "个参数信息---");
                    System.out.println("参数名:" + p.getName());
                    System.out.println("形参类型:" + p.getType());
                    System.out.println("泛型类型:" + p.getParameterizedType());
                }
            }
            System.out.println("--------------------------------------------");
        }
    }
}

上述代码定义了一个简单的MyClass类用于测试,该类包含两个带参数的方法。在main()方法中,先获取MyClass类的所有方法,并使用嵌套循环来输出该类中的所有方法及参数信息。输出参数信息的前提是p.isNamePresent()为true,即只有当.class文件中包含参数信息时,程序才会执行条件体内输出参数信息的三行代码。因此,需要使用javac-parameters命令编译该程序代码。(就是需要在黑窗口里编译运行)

javac -parameters -d.MethodParameterDemo

再使用java命令运行程序代码:

java test05.MethodParameterDemo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值