Java核心基础加强系列-反射

反射概述

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

这是每本Java书都会写的一段介绍,那么到底什么是反射,我们可以这样理解,针对Java程序中的每个类,都会存在这样一个名为Class的对象,该对象保存着类中所有的信息,包括属性,方法等,无论该类有多少个实例对象,我们可以从任意一个实例对象中获取到该Class对象。

而针对Java中的每个类,我们可以将其的组成部分提取出来,用相应的反射类来表示,比如一个最基本的类有属性,构造方法,实例方法,我们可以在代码中动态地获取类中的每个组成部分,并用相应的对象来表示,进而实现对各部分的操作。

使用反射的前提是获取类的Class对象。

获取类的Class对象有三种方式,想必大家都知道,就不再赘述。

反射API的基本使用

在实例中,我们新建一个名为Student的类,其属性和方法如下:

public class Student {
    private String name = "默认是哈哈";
    private Integer age = 12;
    public Student() {
        System.out.println("没有参数的构造函数");
    }
    public Student(String name) {
        System.out.println("一个参数name的构造函数");
        this.name = name;
    }
    private Student(Integer age) {
        System.out.println("一个参数age的私有构造函数");
        this.age = age;
    }
    public Student(String name, Integer age) {
        System.out.println("两个参数的构造函数");
        this.name = name;
        this.age = age;
    }
    public void Test(){
        System.out.println("调用公用test方法");
    }
    private void test2(String nanme){
        System.out.println("调用私有test2方法");
    }
    private void show(){
        System.out.println("showshowshowshow call");
    }
   }

获取构造函数,并通过调用构造函数生成对象。

public class TestConstructs {

    public static void main(String[] args)throws Exception{
    //通过Class对象的静态方法加载Class对象,需要填写类的全路径
        Class stuClass = Class.forName("listeningrain.cn.reflect.model.Student");
        System.out.println("----------------获取所有公共构造方法--------------");
        Constructor[] constructors = stuClass.getConstructors();
    
        System.out.println("----------------获取所有构造方法(包括私有的)--------------");
        Constructor[] allConstructor = stuClass.getDeclaredConstructors();
    
        System.out.println("----------------获取无参构造函数--------------");
        Constructor constructor = stuClass.getConstructor(null);
   
        System.out.println("----------------获取指定的构造函数--------------");
        Constructor constructor2 = stuClass.getConstructor(String.class);
       
        Constructor constructor3 = stuClass.getDeclaredConstructor(Integer.class);
    }
}

可以看到,在从class对象中获取构造函数时,有两种方法getConstructors()getDeclaredConstructors(),区别就是getConstructors()只能获取到限定符为public的构造函数,而getDeclaredConstructors可以获取所有的构造函数,包括私有的。除了可以获取所有的构造函数外,还可以获取指定的构造函数,通过getDeclaredConstructor(Class class)其参数是方法的参数的类型,记得是参数的类型,可以参考上面的例子。

    Class stuClass = Class.forName("listeningrain.cn.reflect.model.Student");
    System.out.println("----------------获取指定的构造函数,并生成对象--------------");
    Student student = (Student) constructor.newInstance();
    System.out.println(student);

运行上面的代码,可以看到输出:

没有参数的构造函数
Student{name='默认是哈哈', age=12}

生成了相应的对象,我们在默认的构造函数中写的代码也被调用了。

值得注意的是,若是想调用私有的构造函数,则需要在调用之前通过constructor.setAccessible(true);解除私有限定,否则会抛出java.lang.IllegalAccessException异常

获取属性,并实现对私有属性的更改

public class TestProperties {

    public static void main(String[] args)throws Exception{
        Class stuClass = Class.forName("listeningrain.cn.reflect.model.Student");
        System.out.println("-------------获取所有属性(包括私有)--------------");
        Field[] fields = stuClass.getDeclaredFields();
 
        System.out.println("-------------给属性设置值-------------");
        Field name = stuClass.getDeclaredField("name");
        Student student = (Student)stuClass.getConstructor().newInstance();
        System.out.println("设置前: "+student);
        name.setAccessible(true);
        name.set(student,"我擦");
        System.out.println("设置后: "+student);
    }
}

运行上面的代码,相应的输出是:

-------------获取所有属性(包括私有)--------------
private java.lang.String listeningrain.cn.reflect.model.Student.name
private java.lang.Integer listeningrain.cn.reflect.model.Student.age
-------------给属性设置值-------------
没有参数的构造函数
设置前: Student{name='默认是哈哈', age=12}
设置后: Student{name='我去', age=12}

在上面的代码中,调用set方法时,需要传入两个参数:第一个是需要被操作的对象,第二个是需要被设置的值。

获取实例方法并调用

    public class TestMethod {
        public static void main(String[] args) throws Exception{
            Class stuClass = Class.forName("listeningrain.cn.reflect.model.Student");
            System.out.println("------------获取所有的成员方法------------");
            Method[] declaredMethods = stuClass.getDeclaredMethods();
            for(Method method : declaredMethods){
                System.out.println(method);
            }
            System.out.println("------------调用方法--------------");
            Student student = (Student)stuClass.getConstructor().newInstance();
            //获取某个具体方法时,传入参数为方法名和参数类型
            Method test2 = stuClass.getDeclaredMethod("test2",String.class);
            test2.setAccessible(true);  //若方法的限定符为private,则需要解除私有限定
            test2.invoke(student,"123");
        }
    }

在利用反射调用函数时,需要传入两个参数,第一个是需要被操作的对象,第二个是方法需要的参数。

越过泛型参数类型检查

public class TestGeneric {

    public static void main(String[] args) throws Exception{
        List<String> list = new ArrayList();
        list.add("阿峰");
        list.add("哈哈");
        for(String data : list){
            System.out.println(data);
        }
        System.out.println("----------反射越过泛型类型检查----------");
        Class listClass = list.getClass();
        Method add = listClass.getMethod("add", Object.class);
        add.invoke(list,100);/调用add函数,将100添加进list
        for(Object data : list){
            System.out.println(data);
        }
    }
}

以上代码的运行结果是:

阿峰
哈哈
----------反射越过泛型类型检查----------
阿峰
哈哈
100

其实原理很简单,这涉及到泛型的擦除,在代码被编译后,上例中泛型的String会被擦除,成为Object类型,这也就是上例代码中在获取add方法时,传入的参数类型并不是String.class,而是Object.class,泛型的擦除并不是本文的重点,当然也非常重要,具体的大家可以去查阅相应的资料。

反射的高级应用

之前写过一篇关于Java内省的文章,并实现了我们自己的BeanUtils.copyProperties()工具,算是反射的高级应用吧,可以点击这里查看

已有 1 条评论
  1. 静心听雨

    自己占个沙发

    静心听雨 回复
发表新评论