文章目录

  • 1.什么是反射?
  • 2.Class类对象的加载方式
  • 3.通过Class类加载对象获得成员变量Field
  • 4.获取构造器Constructor
  • 5.获取成员方法Method
  • 6.反射小案例


前言:本文主要说说反射的一些知识,我在初学反射时总是云里雾里,这是个啥玩意儿,如果你跟我也有过同样的“遭遇”,看看这篇文章或许多你有帮助。

内容主要包括反射的概念、Class类对象的加载方式、获取Method、Field、Constructor以及在最后我们利用反射来手撸一个小“框架”。

1.什么是反射?

反射(reflection)是Java特征之一,它允许执行中的Java程序自行检查或“理解”并操纵程序的内部属性。将类的各个属性封装成对象就是反射机制。 这话可能有点抽象,下面画一幅图来说明:

反射有什么好处java 反射是什么java_反射


学过Java的朋友们都知道我们在初学Java的时候肯定用过记事本写简单Java类然后用javac命令去编译它,编译完之后在本地磁盘上就会有对应的类名.class 文件,这个文件就是字节码文件,按照图中的例子来说,这个Student.class字节码文件里面保存了Student类的一些成员变量和构造方法以及普通成员方法(除了这些还有其他的)。然后类加载器ClassLoader将这个字节码文件加载进内存当中,在内存里面会有一个对象来描述这个字节码文件——Class类对象,使用Class类对象来描述所有字节码文件的特征和行为,里面当然就包括了成员变量、构造方法、普通成员方法(当然还有其他的),并将这些封装为不同的对象,如成员变量就封装成Field[] 对象等。然后将来通过这个Class类对象的一些行为就可以来创建对象了。

再回过头来看看这句话“将类的各个属性封装成对象就是反射机制”应该就容易理解了吧。

有人肯定会问把这些成员都封装起来有什么用呢,也就是说反射的好处是什么?

  • 可以在程序运行过程中操作这些对象。比如我们开发用的IDE(Eclipse、IDEA),当我们写到类名或对象然后再敲一个点时,IDE就会跳出一个提示框,提示我们这个对象有哪些方法或变量我们可以调用,这就运用到了反射。
  • 可以解耦,提高程序的可扩展性。
  • 用于调试器和测试工具。

接下里会讲讲反射的使用方法,包括Class类对象的加载方式、获取成员变量Field、获取构造器Constructor和获取成员方法Method。

2.Class类对象的加载方式

Class类对象有三种加载方式:

  • Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象。此用法多用于配置文件,将类名定义在配置文件中。读取文件,加载类
  • 类名.class:通过类名的属性class获取
  • 对象.getClass():getClass()方法在Object类中定义

上代码:
Student.java

package com.reflection.learn;

/**
 * Created on 2020/2/29
 * Package com.reflection.learn
 *
 * @author dsy
 */
public class Student {
    private String name;
    private Integer age;

    public String a;
    protected String b;

    public Student(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Student() {
    }

    public void eat(){
        System.out.println("在吃饭呢");
    }

    public void sing(String name){
        System.out.println("正在唱:"+name);
    }
	
	//getter 、 setter and toString

}

测试代码:

package com.reflection.learn;

/**
 * Created on 2020/2/29
 * Package com.reflection.learn
 *
 * @author dsy
 */
public class ClassThreeMethods {

    public static void main(String[] args) throws Exception {
        //1.使用Class.forName()方法加载Class类对象
        Class cls = Class.forName("com.reflection.learn.Student");
        System.out.println(cls);

        //2.使用类.class来加载Class类对象
        Class cls2 = Student.class;
        System.out.println(cls2);

        //3.使用对象.getClass()方法来加载Class类对象
        Student student = new Student();
        Class cls3 = student.getClass();
        System.out.println(cls3);


        System.out.println(cls==cls2);
        System.out.println(cls==cls3);
    }
}

打印输出:

class com.reflection.learn.Student
class com.reflection.learn.Student
class com.reflection.learn.Student
true
true

说明:从运行结果的两个true我们可以得出一个结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不管采取何种方式获得Class类对象都是同一个。不了解==运算符的可以看这里

3.通过Class类加载对象获得成员变量Field

Class类中提供了四种获取成员变量的方法:

  • Field getField(String name):获取单个指定名称的public修饰符的成员变量
  • Field[] getFields():获取一组public修饰符的成员变量
  • Field getDeclaredField(String name):获取单个指定名称的成员变量,不考虑修饰符
  • Field[] getDeclaredFields():获取一组成员变量,不考虑修饰符

下面通过代码来演示:

package com.reflection.learn;

import java.lang.reflect.Field;

/**
 * Created on 2020/2/29
 * Package com.reflection.learn
 *
 * @author dsy
 */
public class GetClassFields {

    public static void main(String[] args) throws Exception {
        //0.先通过类.class()方法来获得Class类对象
        Class clazz = Student.class;

        //四种方式:
        //Field  getField(String name):获取单个指定名称的public修饰符的成员变量
        //Field[]	getFields():获取一组public修饰符的成员变量
        //Field	getDeclaredField(String name):获取单个指定名称的成员变量,不考虑修饰符
        //Field[]	getDeclaredFields():获取一组成员变量,不考虑修饰符

        //1.Field  getField(String name)
        Field field = clazz.getField("a");//此处若传入"b",则会报错,因为成员b不是public修饰的
        //获取成员变量a的值
        Student student = new Student();
        Object value = field.get(student);
        System.out.println(value);  //String默认值为null
        //设置a的值
        field.set(student,"hello");
        System.out.println(student);

        //2.Field[]	getFields()
        Field[] fields = clazz.getFields();
        for (Field field1 :fields){
            System.out.println(field1.getName());
        }

        //3.Field	getDeclaredField(String name):获取指定成员变量,不考虑修饰符
        Field declaredField = clazz.getDeclaredField("name");
        System.out.println(declaredField);
        //访问name的值,因为name是private修饰的,虽说可以访问这个成员变量但不能访问其值,访问前要忽略访问修饰符的安全检查
        declaredField.setAccessible(true); //此句不加会有异常:IllegalAccessException
        Object value1 = declaredField.get(student);
        System.out.println(value1);

        //4.Field[]	getDeclaredFields():获取所有的成员变量,不考虑修饰符
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field aa:declaredFields){
            System.out.println(aa);
        }
       
    }
}

打印输出:

null
Student{name='null', age=null, a='hello', b='null'}
a
private java.lang.String com.reflection.learn.Student.name
null
private java.lang.String com.reflection.learn.Student.name
private java.lang.Integer com.reflection.learn.Student.age
public java.lang.String com.reflection.learn.Student.a
protected java.lang.String com.reflection.learn.Student.b

4.获取构造器Constructor

主要有四种方式:

  • Constructor getConstructor(Class<?>… parameterTypes):获得一个public修饰的构造器并指定构造器的参数类型
  • Constructor<?>[] getConstructors():获取所有的public构造方法
  • Constructor getDeclaredConstructor(Class<?>… parameterTypes):获取一个构造器并指定构造器的参数类型,不限制修饰符
  • Constructor<?>[] getDeclaredConstructors():获取所有的构造方法,不限制修饰符

上代码:
我们先在Student类里面加一个private修饰符的构造方法:

package com.reflection.learn;

/**
 * Created on 2020/2/29
 * Package com.reflection.learn
 *
 * @author dsy
 */
public class Student {
   // ......
   
    private Student(String name){
        this.name = name;
    }

   //......
   
}

演示代码:

package com.reflection.learn;

import java.lang.reflect.Constructor;

/**
 * Created on 2020/2/29
 * Package com.reflection.learn
 *
 * @author dsy
 */
public class GetConstructors {
    public static void main(String[] args) throws Exception {
        //0.先通过类.class()方法来获得Class类对象
        Class<Student> clazz = Student.class;

        //四种方式:
        //Constructor<T>	getConstructor(Class<?>... parameterTypes)
        //Constructor<?>[]	getConstructors()
        //Constructor<T>	getDeclaredConstructor(Class<?>... parameterTypes)
        //Constructor<?>[]	getDeclaredConstructors()

        //1.Constructor<T>	getConstructor(Class<?>... parameterTypes):获得一个public修饰的构造器并指定构造器的参数类型
        Constructor<Student> constructor = clazz.getConstructor(String.class,Integer.class);
        //通过获得的构造器来实例化对象
        Student student = constructor.newInstance("小明",22);
        System.out.println(student);

        //2.Constructor<?>[]	getConstructors():获取所有的public构造方法
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor constructor1 : constructors){
            System.out.println(constructor1);
        }

        //3.Constructor<T>	getDeclaredConstructor(Class<?>... parameterTypes):获取一个构造器并指定构造器的参数类型,不限制修饰符
        Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
        //通过获得的构造器来实例化对象
        declaredConstructor.setAccessible(true);
        Student declaredStudent = (Student) declaredConstructor.newInstance("小明");
        System.out.println(declaredStudent);


        //4.Constructor<?>[]	getDeclaredConstructors():获取所有的构造方法,不限制修饰符
        Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
        for (Constructor constructor2 : declaredConstructors){
            System.out.println(constructor2);
        }

        //如果使用空参构造来实例化对象可以使用如下方法
        //Student student1 = clazz.newInstance();


    }
}

5.获取成员方法Method

主要有四种方式:

  • Method getDeclaredMethod(String name, Class<?>… parameterTypes)
  • Method[] getDeclaredMethods()
  • Method getMethod(String name, Class<?>… parameterTypes)
  • Method[] getMethods()

演示代码:

package com.reflection.learn;

import java.lang.reflect.Method;

/**
 * Created on 2020/3/1
 * Package com.reflection.learn
 *
 * @author dsy
 */
public class GetMethods {
    public static void main(String[] args) throws Exception {


        //0.通过类.class方法获取Class类对象
        Class clazz = Student.class;


        //获取Method的四种方式:
        //Method	getDeclaredMethod(String name, Class<?>... parameterTypes)
        //Method[]	getDeclaredMethods()
        //Method	getMethod(String name, Class<?>... parameterTypes)
        //Method[]	getMethods()

        //1.Method	getDeclaredMethod(String name, Class<?>... parameterTypes)
        Method eat_Method = clazz.getMethod("eat");
        System.out.println(eat_Method);
        //执行eat方法
        Student student = new Student();
        eat_Method.invoke(student);
        //获取sing方法并执行
        Method sing_Method = clazz.getMethod("sing", String.class);
        System.out.println(sing_Method);
        sing_Method.invoke(student,"《海阔天空》");


        //获取所有public修饰的方法
        //打印发现除了Student类里面我们手写的显示的方法还有Object类里面的方法,如:equals、hashCode等等
        Method[] methods = clazz.getMethods();
        for (Method method:methods){
            System.out.println(method);
            //获取方法名称
            System.out.println(method.getName());
        }

        //其他方法也类似不再赘述

    }
}

6.反射小案例

看了上面那么多的方法演示是不是有点觉得学的不明不白,这些方法用在哪里呢?都说反射是框架的灵魂,接下来我们利用上面的方法来开发一个小“框架”,需求为:在不改变该类的任何代码的前提下,任意创建类的对象,执行其中任意的方法。
如何实现:

  • 反射
  • 配置文件

步骤:

  • 1、将需要创建对象的全类名和需要执行的方法写在配置文件当中。
  • 2、在程序中加载读取配置文件
  • 3、使用反射技术来加载类文件进内存
  • 4、创建对象
  • 5、执行方法

1.首先我们创建一个Car类:

package com.example.learn;

/**
 * Created on 2020/3/1
 * Package com.example.learn
 *
 * @author dsy
 */
public class Car {

    public void stop(){
        System.out.println("正在刹车........");
    }
}

2.好了之后我们在项目的src下面创建一个config.properties文件:

反射有什么好处java 反射是什么java_反射_02


反射有什么好处java 反射是什么java_System_03


里面输入config.properties即可,然后打开该文件写上如下配置:

className = com.example.learn.Car
methodName = stop

3.创建Example_Reflection类:

package com.example.learn;

import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * Created on 2020/3/1
 * Package com.example.learn
 *
 * @author dsy
 */
public class Example_Reflection {

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

        //1.加载配置文件
        Properties properties = new Properties();
        ClassLoader classLoader = Example_Reflection.class.getClassLoader();
        InputStream inputStream = classLoader.getResourceAsStream("config.properties");
        properties.load(inputStream);
        //取出配置文件中的数据
        String className = properties.getProperty("className");
        String methodName = properties.getProperty("methodName");

        //2.利用反射将类文件加载进内存
        Class clazz = Class.forName(className);

        //3.创建对象
        Object object = clazz.newInstance();

        //4.获取stop方法
        Method method = clazz.getMethod(methodName);

        //5.执行方法
        method.invoke(object);
    }
}

执行main方法并打印输出:

正在刹车........

这样我们的目的就实现了,那肯定有人会说,需求不是创建任何类的对象吗,你现在只创建了一个。别急我们现在Car的同级目录下再创建一个类名为Bird:

package com.example.learn;

/**
 * Created on 2020/3/1
 * Package com.example.learn
 *
 * @author dsy
 */
public class Bird {

    public void fly(){
        System.out.println("flying......");
    }
}

我现在要实现在不改变小“框架”Example_Reflection 类的前提下,创建Bird类的对象并执行fly方法怎么办呢?我们只需在配置文件config.properties中稍作改变即可:

className = com.example.learn.Bird
methodName = fly

然后再执行Example_Reflection 的main方法打印输出:

flying......

到此大功告成,这个小“框架”就被我们实现了,我们没有改变该类的任何代码,只改变了配置文件就实现了这个功能。更改代码与更改配置文件的利弊我就不多说了,显然更改配置文件要方便得多。

喜欢本文的话就点个赞吧

参考:
javadoc文档关于Class类 某位老师的讲解