java之反射的应用

作者: 宝塔山上的猫 | 来源:发表于2016-07-21 11:54 被阅读185次

    Java的反射库是一个被精心设计的工具集,使用它编写和动态的操纵java代码。

    那么java的反射能做什么呢?官方的说法是能够在运行中分析类的能力,能够在运行中查看对象。

    官方说法很正确,反射确实是在java代码在运行状态中分析类和对象,但一般人看不懂!

    所以简单的说,反射就是当你找到一个类,想创建这个类的对象,使用它的方法时,却失望的发现这个类的构造方法和方法不能被直接使用。这时你使用反射便能够构造出这个类,并且使用这个类中不支持直接使用的方法。

    java.lang.reflect包的内容

    在reflect包下有三个类,
    1、Filed类:代表类的成员对象(成员变量也叫类的属性)
    2、Method:代表类的方法
    3、Contructor:代表类的构造方法
    4、Array类:提供动态创建数组和访问数组元素的静态方法

    看起来一切似乎都很复杂,那我们直接用代码来代替吧!

    首先我们创建一个java项目,然后创建一个包,包名为com.example.reflection,然后创建Employee类,代码如下:

    public class Employee {
    
    public Employee(){
        System.out.println("无参数构造方法");
    }
    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 Employee(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    
    @Override
    public String toString() {
        return "Employee [name=" + name + ", age=" + age + "]";
    }
    
    private void work(){
        System.out.println("working...");
    }
    
    }
    

    一般情况下我们可以直接用new的方式创建这个类的实例,但是现在我们要使用反射来创建这个类,并且使用里面的方法。
    现在我们再创建一个ReflectionAPIDemo类,添加Main方法,代码如下:

    public class ReflectionAPIDemo {
       public static void main(String[] args)throws Exception {
    
        }
    }
    

    使用反射创建类的实例

    使用反射创建类的实例有两种方法,但是它们有个共同的特点,又或者说这是使用反射的基础步骤,就是必须获得想要创建类的Class对象。
    创建Class对象的方法很简单,只需知道类的绝对路径就可以了,也就是包名 + 类名,而Employee的绝对路径为:com.example.reflection.Employee。所以使用以下代码获得Employee的Class对象:

    Class<?> classType = Class.forName("com.example.reflection.Employee");
    

    当然,上面的写法是最常见的写法,其实要获得Class对象也有其他的写法,如果我们已经拥有了Employee对象的实例employee,那么可以用以下方法:

    Employee employee = new Employee();
    Class<?> classType = employee.getClass();
    

    又或者直接使用Employee类获得Class对象

    Class<?> classType = Employee.class;
    

    拓展一下,获取了Class对象之后也可以获取Employee的父类,也就是Object类,下面代码能输入Object的类名:

    System.println.out(classType.getSuperclass().getName());
    

    获得Class对象之后就能开始创建类的实例了,如下

    // 第一种创建类实例的方法
    Employee employee=(Employee)classType.newInstance();
    

    第一种创建类的实例的方法很简单,只需要使用newInstance()方法便可以了。而第二种获得类的实例的方法则稍显麻烦一点,它首先要获得一个Constructor对象,在Class类中有一个getConstructor()方法能够获取Constructor()对象,然后再通过Constructor的newInstance()方法获取类的实例,代码如下:

    Constructor<?> constructor = classType.getConstructor(new Class[]{});
    Employee employee=(Employee)constructor.newInstance(new Object[]{});
    

    我们仔细观察使用constructor.newInstance()这种方法获取对象的特点,发现它需要传入一个参数,在获取Constructor对象的时候传入了一个new Class[]{}数组,然后使用constructor.newInstance()要传入一个new Object[]{}数组,它们代表的是什么呢?

    其实使用Constructor来创建类的实例优点是它能够创建出带有参数的类的构造方法的实例,也就是能够使用Employee中的带参构造方法,代码如下:

    public Employee(String name, int age) {
      super();
      this.name = name;
      this.age = age;
    }
    

    但是我们之前的new Class[]{}数组和new Object[]{}数组并没有传入任何参数,所以使用的是不带参数的构造方法。用反射使用带参数的构造方法如下:

    Constructor<?> constructor = classType.getConstructor(new Class[]{String.class,int.class});
    Employee employee=(Employee)constructor.newInstance(new Object[]{"张三",30});
    

    现在我们发现new Class[]{}数组传入了String.class,int.class两个参数,这是因为Class数组要传入的是Class对象,而它所代表的Class对象则是Employee带参数构造方法中要传入的两个参数String name和int age的Class对象。而constructor.newInstance(new Object[]{"张三",30})中传入的参数就好理解了,它里面的是创建Employee对象时所要传入的实例。

    调用类的指定方法

    反射中同样有调用类的指定方法的方法,如我们要调用Employee中的toString()方法,代码如下:

    Method method = classType.getDeclaredMethod("toString", new Class[]{});
        //方法的调用
    String desc=(String)method.invoke(employee, new Object[]{});
    System.out.println(desc);
    

    getDeclaredMethod()方法返回的是Employee中的的方法,中第一个参数代表指定调用的方法名,第二个则是要调用方法中需要传入的参数的Class对象,toString()方法不需要传入参数,所以传入一个空的Class数组。
    method.invoke()则是代表要调用运行这个方法了,第一个传入的参数表面我们要调用的方法是属于哪一个对象的,在这里我们传入employee。需要注意的是如果我们调用的方法是static静态方法,那么这个参数可以为null。第二个参数new Object[]{}数组则是我们要调用方法所要传入的参数,这里也为空。

    这时我们就能够调用Empleyee中的toString()方法了。

    现在有一个问题,如果我们想要调用Employee中修饰为private中的work()方法怎么办?标识为private中的方法只能在本类中使用,无法在外部使用,这能调用吗?答案是可以的,只要设置Method类中的setAccessible()为true便可以调用,代码如下:

    Method method = classType.getDeclaredMethod("work", new Class[]{});
    method.setAccessible(true);
    //方法的调用
    method.invoke(employee, new Object[]{});
    

    当然了,Java中调用private的方法这是属性是不太建议的,因为这破坏了java的封装性。

    当然,上面是调用我们指定的方法,Class类中也提供了获取类中方法的其他方法,如下
    获得当前类以及超类的public Method

    Method[] arrMethods = classType.getMethods();
    

    获得当前类申明的所有Method

    Method[] arrMethods = classType.getDeclaredMethods();
    

    获取当前类以及超类制定的publci Method

    Method method = classType.getMethod(String name, Class<?>...parameterTypes);
    

    当然,通过反射动态运行指定Method还是用invoke()方法,如下:

    Object obj = method.invoke(Object obj, Object...args);
    

    调用类的属性

    Employee中有标注为private的属性name,代码如下:

        private String name;
    

    现在我们用反射调用并且设置它的参数,代码如下:

    Field field = classType.getDeclaredField("name");
    field.setAccessible(true);
    field.set(employee, "李四");
    System.out.println(field.get(employee));
    

    在输出结果中发现我们确实调用了Employee中的属性name,并且给它赋值了。

    同样reflect中还有很多获取和设置属性的方法,如下:

    获得当前类以及超类的public Field

    Filed[] arrFields = classType.getFields();
    

    获得当前类申明的所有Field

    Field[] arrFields = classType.getDeclaredFields();
    

    获得当前类以及超类制定的public Field

    Field field = classType.getField(String name);
    

    获得当前类申明的制定的Field

    Field field = classType.getDeclaredField(String name);
    

    通过反射动态设定Field的值

    field.set(Object obj, Object value);
    

    通过反射动态获取Field的值

    Object obj = field.get(Object obj);
    

    总结

    本文只是将Java中常用的反射方法介绍一下,Java的反射库中还有这许多的知识在本文中并没有讲解,如果想要详细了解就只能看java的API文档了。

    值得注意的是java中获取Class对象也会有一些特殊的用法,如基本数据类型。

    当你使用int、long这些基本数据类型获取他们的Class对象时,你会发现他们居然有两种不同形式的获取类对象的方法,第一种是直接获取,第二种是使用其包装类获取,代码如下:

        Class<?> intClassTYPE = int.class;
        System.out.println(intClassTYPE.getName());
        Class<?> IntegerClassTYPE = Integer.TYPE;
        System.out.println(IntegerClassTYPE.getName());
    

    他们的输入的类名都是int,说明他们能够获取同一个对象。

    同理double.class与Double.TYPE也能获取double类名,long.class与Long.TYPE都是获取long类名的。

    但是如果你输入Integer.class时获取的Class对象这是Integer了,这点是使用基本数据类型获取Class对象需要注意的。

    友情链接:http://www.jianshu.com/p/f2adb1ac520a java中的I/O流

    相关文章

      网友评论

        本文标题:java之反射的应用

        本文链接:https://www.haomeiwen.com/subject/dtsbjttx.html