学习Java反射机制

定义:

在Java的运行状态中对于任意一个实体类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够条用它的任意方法和属性;这种动态调用获取信息以及动态调用对象方法的功能称为Java的反射机制

一、Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。保存这些信息的类被称为Class

1、获得Class类。

①、利用Object类中getClass()方法获得一个Class类型的实例

	StudentBean studentBean = new StudentBean("小名",18,"男");
    Class cl= studentBean.getClass();

②、通过forName方法获得对应的Class对象

    String className = "java.lang.Double";
    try {
        Class cl = Class.forName(className);
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

这里的类名是包含包名的,并且className必须是类名或者接口名,否则会报ClassNotFoundException异常。

③、使用T.class获得Class对象。T为任意Java类型

    Class doubleClass = double.class;
    Class studentClass = StudentBean.class;

2、判断Class对象相等

虚拟机为每一个类型管理一个Class对象,因此,可以利用 == 运算符实现两个类对象比较操作。(注:判断对象所属的类是否相等,不能判断对象是否相等)

使用Class的newInstance()方法创建一个空参的类的实例。

    try {
        Object o = Class.forName(className).newInstance();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

newInstance()方法默认会调用对象的空参的构造方法创建一个对象。如果对象没有空参的构造方法,会抛出IllegalAccessException 异常

二、利用反射检查类的结构

Java反射包java.lang.reflect 有三个非常重要的类: Field、Method、Constructor,分别描述类的 域(全局变量、常量)、方法、构造器。三个类中有共同的方法getName()返回项目名称,getModifiers()返回修饰符。

Field,Method和Constructor都可以通过Class的get方法获得。

Class对象提供了提供了两种获得方式。使用 getFields()getMethods()getConstructors()返回的是public 类型的 域、方法、构造器。而使用getDeclaredFields()getDeclaredMethods()getDeclaredConstructors()获得的是全部的 域、方法和构造器。

下面我们通过一个例子来说明:

public class StudentBean extends BaseBean {
    private String name;
    private int age;
    private String sex;
    public int id;
    public StudentBean(){
    }
    public StudentBean(String name, int age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
    @Override
    String getString() {
        return "Student";
    }
}

上面创建了一个StudentBean 的类型,继承BaseBean 。有四个参数,两个构造方法,sex的get、set方法和继承父类的getString()。方法

下面我们通过反射来解析StudentBean 这个类。

1、解析Filed(Filed域,我理解是全局变量,常量)

    /**
     * 打印Fields
     */
    private static void printFields(Class cl) {
    	//获得全部的域(全局变量、常量)
        Field[] fields = cl.getDeclaredFields();
        for (Field field : fields) {
            //获得域的类型(变量的类型)
            Class type = field.getType();
            //获得域名(变量名)
            String name = field.getName();
            System.out.print("    ");
            //获得域的修饰符(变量的修饰符)
            String modifiers = Modifier.toString(field.getModifiers());
            System.out.print("域modifiers = " + modifiers);
            System.out.print("  域field type(类型) = " + type.getSimpleName() + "   name: " + name + "\n");
        }
    }

getModifiers()获得的是一个int类型的修饰值,不方便我们理解,可以使用Modifier.toString(field.getModifiers()) 把int类型的修饰符转换成String类型,方便查看。

2、解析Method(方法)

    /**
     * 打印方法
     * @param cl
     */
    private static void printMethods(Class cl) {
        //获得所有的方法
        Method[] methods = cl.getDeclaredMethods();
        //遍历方法
        for (Method method : methods) {
            //获得方法的返回类型
            Class returnType = method.getReturnType();
            //方法名
            String name = method.getName();
            System.out.print("    ");
            //获得方法的修饰符
            String modifiers = Modifier.toString(method.getModifiers());
            System.out.print("方法modifiers = " + modifiers);
            System.out.print("   方法返回类型:" + returnType.getSimpleName() + "   方法名name:" + name + "(");

            //获得方法参数的集合
            Class[] paramTypes = method.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                //获得参数类型
                System.out.print("参数类型:" + paramTypes[i].getSimpleName());
            }
            System.out.print(");\n");
        }
    }

3、解析构造器(构造方法)

    /**
     * 打印构造器
     * @param cl
     */
    private static void printConstructors(Class cl) {
        //获得所有的构造器对象
        Constructor[] constructors = cl.getDeclaredConstructors();
        for (Constructor c : constructors) {
            //构造方法名
            String name = c.getName();
            System.out.print("    ");
            //构造方法的修饰符
            String modifier = Modifier.toString(c.getModifiers());
            if (modifier.length() > 0) {
                System.out.print("构造器modifier = " + modifier);
            }
            System.out.print("  name = "+name + " (");
            //构造方法参数集合
            Class[] paramTypes = c.getParameterTypes();
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(", ");
                }
                //构造方法参数类型
                System.out.print(" 参数类型:"+paramTypes[i].getSimpleName());
            }
            System.out.print(");\n");
        }
    }

4、主程序代码

    public static void main(String[] args) {

        System.out.println("请输入类名");

        Scanner scanner = new Scanner(System.in);
        String className = scanner.next();

        try {
            System.out.println(className);
            Class cl = Class.forName(className);
            //获得父类的Class
            Class supercl = cl.getSuperclass();
            String modifiers = Modifier.toString(cl.getModifiers());
            if (modifiers.length() > 0)
                System.out.print("修饰符: " + modifiers);

            if (supercl != null && supercl != Object.class) {
                System.out.print("  extends  " + supercl.getSimpleName());
            }

            System.out.print("\n{\n");
            printConstructors(cl);
            System.out.println();
            printMethods(cl);
            System.out.println();
            printFields(cl);
            System.out.println("}");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();

        }
    }

getSuperclass()获得超类的Class对象

5、运行程序,输入完整StudentBean类名,运行结果如下:

请输入类名
com.zy.test.reflect.StudentBean
com.zy.test.reflect.StudentBean
修饰符: public  extends  BaseBean
{
    构造器modifier = public  name = com.zy.test.reflect.StudentBean ();
    构造器modifier = public  name = com.zy.test.reflect.StudentBean ( 参数类型:String,  参数类型:int,  参数类型:String);

    方法modifiers =    方法返回类型:String   方法名name:getString();
    方法modifiers = public   方法返回类型:String   方法名name:getSex();
    方法modifiers = public   方法返回类型:void   方法名name:setSex(参数类型:String);

    域modifiers = private  域field type(类型) = String   name: name
    域modifiers = private  域field type(类型) = int   name: age
    域modifiers = private  域field type(类型) = String   name: sex
    域modifiers = public  域field type(类型) = int   name: id
}

从输入结果可以看到,我们已经把StudentBean中所有的属性都解析出来了。

三、运行时使用反射解析对象

在项目运行时,我们可以通过反射获得类中当前域Field的值

        try {
            //获得指定域
            Field field = studentClass.getDeclaredField("age");
            //获得指定域的当前值
            int age = (int) field.get(studentBean);
            //为指定域设置新值
            field.set(studentBean,20);
            //输出结果。 getAge()后加的get方法
            System.out.println("age:" + age + "  getAge:" + studentBean.getAge());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

运行发现会报IllegalAccessException异常。
因为age修饰符是private,是一个私有域。只有getAge()方法才能访问到。Java安全机制只允许查看任意对象有哪些域,不允许读它的值。

Java反射机制的默认行为受限于Java的访问控制权限。可以调用Field、Method、Constructor的setAccessible()获得访问权限

        try {
            //获得指定域
            Field field = studentClass.getDeclaredField("age");
            //设置权限
            field.setAccessible(true);
            //获得指定域的当前值
            int age = (int) field.get(studentBean);
               //为指定域重新赋值
            field.set(studentBean,20);
            System.out.println("age:" + age + "  getAge:" + studentBean.getAge());
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

输出结果:

age:18  getAge:20

可以看到,我们通过反射获得指定域的Field对象,申请权限后,我们就可以不使用get,set方法直接对域进行取值和赋值。

可以使用AccessibleObject.setAccessible();对一组Field、Method、Constructor 对象设置权限

  //为一组对象设置权限
   AccessibleObject.setAccessible(studentClass.getDeclaredFields(),true);

四:调用任意方法

我们可以通过Field的get方法查看对象域的过程,与之相似在Method类中有个invoke()方法(英文:调用的意思),可以调用包装在Method对象中的方法。

Object invoke(Object obj, Object... args) 有两个参数,第一个是隐式参数第二个是显示参数。静态方法第一个参数传null。

下面通过Method获得getSex()方法的返回值

        try {
            //通过指定方法名,获得Method对象
            Method method = studentClass.getDeclaredMethod("getSex");
            //获得权限
            method.setAccessible(true);
            //获得方法的返回值
            String sex = (String) method.invoke(studentBean);
            System.out.println("sex:" + sex);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

输出:

sex:女

因为 invoke() 方法返回值必须是Object类型的,这就导致必须就行多次类型转换,这样会编译器错过检查代码的机会。而且使用反射获得方法的Method对象比直接调用get方法效率要低。

以上就是关于Java反射的一些基本知识点了。使用时还是要结合项目和需求来看。

参考:《Java核心技术 卷一》

猜你喜欢

转载自blog.csdn.net/sjdjdjdjahd/article/details/94630395