侧边栏壁纸
博主头像
coydone博主等级

记录学习,分享生活的个人站点

  • 累计撰写 306 篇文章
  • 累计创建 51 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

反射

coydone
2021-03-03 / 0 评论 / 0 点赞 / 353 阅读 / 10,642 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-05-01,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

反射概述

框架

半成品软件。可以在框架的基础上进行软件开发,简化编码。学习框架并不需要了解反射,但是要是想自己写一个框架,那么就要对反射机制有很深入的了解。

反射机制

Java的反射(Reflection)机制是Java的特性之一,反射机制是构建框架技术的基础所在。灵活掌握Java反射机制,对以后学习框架技术有很大的帮助。

Java的反射机制是指在运行状态中,动态获取信息以及调用对象方法的功能

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

Java反射有3个动态性质:运行时产生对象实例、运行期间调用方法、运行时更改属性。

反射原理

要想Java程序能够运行,Java类必须被java虚拟机加载。运行的程序都是在编译时就已经加载了所需要的类。Java反射机制在编译时并不确定是哪个类被加载了,而是在程序运行是才加载、探知、使用,这样的特点就是反射。这类似于光学中反射的概念。所以把Java的这种机制称为反射机制。在计算机科学领域,反射指一类应用,它们能够自描述和自控制。

Java代码在计算机中经历的三个阶段:

(1)Source源代码阶段:.Java被编译器编译成*.class字节码文件。

(2)Class类对象阶段:*.class字节码文件被类加载器加载进内存,并将其封装成Class对象(用于在内存中描述字节码文件),Class对象将原字节码文件中的成员变量抽取出来封装成数组Field[],将原字节码文件中的构造函数抽取出来封装成数组Construction[],在将成员方法封装成Method[]。当然Class类内不止这三个,还封装了很多,我们常用的就这三个。

(3)RunTime运行时阶段:创建对象的过程new。

自审

Java反射机制能够知道类的基本结构,这种对Java类结构的探知能力,称为Java类的“自审”。在使用IDEA时,Java代码的自动提示功能,就是利用了Java反射的原理,是对创建对象的探知和自审。

通过Java反射,可以实现以下功能:

  • 在运行时判断任意一个对象所属的类。

  • 在运行时构造任意一个类的对象。

  • 在运行时判断任意一个类所具有的方法和属性。

  • 在运行时调用任意一个对象的方法。

反射的好处

1、可以在程序运行过程中,操作这些对象。

2、可以解耦,提高程序的可扩展性。

Java反射常用API

使用Java反射技术常用的类:

  • Class类:反射的核心类,反射所有的操作都是围绕该类来生成的。通过Class类,可以获取类的属性、方法等内容信息。

  • Field类:表示类的属性,可以获取和设置类中属性的值。

  • Method类:表示类中的方法,它可以用来获取类中的方法的信息,或者执行方法。

  • Constructor类:表示类的构造方法。

获取Class对象的方式

获取Class对象的三种方式对应着java代码在计算机中的三个阶段:

(1)【Source源代码阶段】 Class.forName("全类名"):将字节码文件加载进内存,返回Class对象,多用于配置文件,将类名定义在配置文件中。读取文件,加载类。

(2)【Class类对象阶段】 类名.class:通过类名的属性class获取,多用于参数的传递。

(3)【Runtime运行时阶段】对象.getClass():getClass()方法是定义在Objec类中的方法,多用于对象的获取字节码的方式。

结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。

代码测试:

public class Person {
    private String name;
    private int age;
    //省略构造方法、getter()、setter()、toString()
}

public class Demo {
    public static void main(String[] args) throws ClassNotFoundException {
        //方式一:Class.forName("全类名");//包名+类名
		//Person自定义实体类
        Class cls1 = Class.forName("com.coydone.fanshe.Person");
        System.out.println("cls1 = " + cls1);
        //方式二:类名.class
        Class cls2 = Person.class;
        System.out.println("cls2 = " + cls2);
        //方式三:对象.getClass();
        Person person = new Person("张三", 20);
        Class cls3 = person.getClass();
        System.out.println("cls3 = " + cls3);
        // == 比较三个对象
        System.out.println("cls1 == cls2 : " + (cls1 == cls2));  //true
        System.out.println("cls1 == cls3 : " + (cls1 == cls3));  //true
        //结论:同一个字节码文件(*.class)在一次程序运行过程中,
        // 只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
    }
}
//运行结果
//cls1 = class com.coydone.fanshe.Person
//cls2 = class com.coydone.fanshe.Person
//cls3 = class com.coydone.fanshe.Person
//cls1 == cls2 : true
//cls1 == cls3 : true

Class对象功能

在Java程序中使用反射的基本步骤如下:

1、导入java.lang.reflect.*

2、获取需要操作的类的Java.lang.Class对象。

3、调用Class的方法获取Field、Method等对象。

4、使用反射API进行操作。

获取功能

1、获取成员变量

Field[] getFields():获取所有public修饰的成员变量
Field getField(String name):获取指定名称的public修饰的成员变量
Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
Class<?>getType()
int getModifiers()
String getName()

2、获取构造方法

Constructor<?>[] getConstructors():获取所有public修饰的构造器
Constructor<T> getConstructor(类<?>... parameterTypes):获取所有public修饰的构造器
Constructor<?>[] getDeclaredConstructors():获取所有的构造器
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes) Constructor<T>:获取所有的构造器

3、获取成员方法

Method[] getMethods()
public int getModifiers()//Modifier.toString(mod); //还原修饰符
public Class<?>getReturnType()
public Class<?>[] getparameterTypes()
public Class<?>[] getExceptionTypes()
public static String toString(int mod)
Method getMethod(String name, 类<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)

4、 获取全类名

String getName() 
取得类所在的包
public Package getPackage()//得到一个类所在的包
public String getName()//得到名字

Field:成员变量

  • 设置值:void set(Object obj, Object value)

  • 获取值:get(Object obj)

  • 忽略访问权限修饰符的安全检查 setAccessible(true):暴力反射

import java.lang.reflect.Field;

public class Demo02 {
    public static void main(String[] args) throws Exception {
        //0、获取Person的Class对象
        Class personClass = Person.class;
        //1、Field[] getFields()获取所有public修饰的成员变量
        Field[] fields = personClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }
        //2.Field getField(String name)
        Field id = personClass.getField("id");
        //获取成员变量id的值 [也只能获取公有的,获取私有的或者不存在的字符会抛出异常]
        Person p = new Person("张三", 20);
        Object value = id.get(p);
        System.out.println("value = " + value);
        //设置属性id的值
        id.set(p, 1111);
        System.out.println(p);

        //public int com.coydone.fanshe.Person.id
        //value = 0
        //Person{name='张三', age=20, id=1111}
        Field[] declaredFields = personClass.getDeclaredFields();
        for (Field filed : declaredFields) {
            System.out.println(filed);
        }
        Field d = personClass.getDeclaredField("name");     //private String d;
        Person p2 = new Person("张ww", 20);
        //忽略访问权限修饰符的安全检查
        d.setAccessible(true);//暴力反射
        Object value2 = d.get(p2);
        System.out.println("value2 = " + value2);
        //private java.lang.String com.coydone.fanshe.Person.name
        //private int com.coydone.fanshe.Person.age
        //public int com.coydone.fanshe.Person.id
        //value2 = 张ww
    }
}

Constructor:构造方法

创建对象:T newInstance(Object... initargs)

注意:如果使用空参数构造方法创建对象,操作可以简化:Class对象的newInstance方法

修改测试的实体类

public class Person {
    private String name;
    private Integer age;
    //无参构造函数
    public Person() {
    }
    //单个参数的构造函数,且为私有构造方法
    private Person(String name){
    }
    //有参构造函数   
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
}

测试方法

import java.lang.reflect.Constructor;

public class Demo02 {
    public static void main(String[] args) throws Exception {
        /**
         * 2. 获取构造方法们
         *    Constructor<?>[] getConstructors()
         *    Constructor<T> getConstructor(类<?>... parameterTypes)
         */
        Class personClass = Person.class;
        //Constructor<?>[] getConstructors()
        Constructor[] constructors = personClass.getConstructors();
        for (Constructor constructor : constructors) {
            //Constructor 对象reflect包下的 import java.lang.reflect.Constructor;
            System.out.println(constructor);
        }

        System.out.println("==========================================");
        //获取无参构造函数   注意:Person类中必须要有无参的构造函数,不然抛出异常
        Constructor constructor1 = personClass.getConstructor();
        System.out.println("constructor1 = " + constructor1);
        //获取到构造函数后可以用于创建对象
        Object person1 = constructor1.newInstance();
        //Constructor类内提供了初始化方法newInstance();方法
        System.out.println("person1 = " + person1);

        System.out.println("==========================================");
        //获取有参的构造函数 
        //public Person(String name, Integer age) 参数类型顺序要与构造函数内一致,且参数类型为字节码类型
        Constructor constructor2 = personClass.getConstructor(String.class, Integer.class);
        System.out.println("constructor2 = " + constructor2);

        //创建对象
        //获取的是有参的构造方法,就必须要给参数
        Object person2 = constructor2.newInstance("张三", 23);
        System.out.println(person2);
        System.out.println("=========================================");
        //对于一般的无参构造函数,我们都不会先获取无参构造器之后在进行初始化。
        //而是直接调用Class类内的newInstance()方法
        Object person3 = personClass.newInstance();
        System.out.println("person3 = " + person3);
        //使用的 Class.forName("").newInstance; 
        //其本质上就是调用了类内的无参构造函数来完成实例化的
        //故可以得出结论:
        //在使用Class.forName("").newInstance;反射创建对象时,一定要保证类内有无参构造函数
    }
}

Method:方法对象

执行方法:Object invoke(Object obj, Object... args)

获取方法名称:String getName();

修改测试的实体类

public class Person {
    private String name;
    private Integer age;
    //无参构造函数
    public Person() {
    }
    //有参构造函数
    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //无参方法
    public void eat(){
        System.out.println("eat...");
    }
    //重载有参方法
    public void eat(String food){
        System.out.println("eat..."+food);
    }
}
/**
 * 3. 获取成员方法们:
 * Method[] getMethods()
 * Method getMethod(String name, 类<?>... parameterTypes)
 */
import java.lang.reflect.Method;
public class Demo02 {
    public static void main(String[] args) throws Exception {
        Class personClass = Person.class;
        //获取指定名称的方法
        Method eat_method1 = personClass.getMethod("eat");
        //执行方法
        Person person = new Person();
        Object rtValue = eat_method1.invoke(person);//如果方法有返回值类型可以获取到,没有就为null
        //输出返回值 eat方法没有返回值,故输出null
        System.out.println("rtValue = " + rtValue);
        System.out.println("--------------------------------------------");
        //获取有参的构造函数  有两个参数 第一个方法名 第二个参数列表 ,不同的参数是不同的方法(重载)
        Method eat_method2 = personClass.getMethod("eat", String.class);
        //执行方法
        eat_method2.invoke(person,"饭");
        System.out.println("============================================");
        //获取方法列表
        Method[] methods = personClass.getMethods();
        for(Method method : methods){
            //注意:获取到的方法名称不仅仅是我们在Person类内看到的方法
            System.out.println(method);   //继承下来的方法也会被获取到(当然前提是public修饰的)
        }
    }
}

getName()方法

getName()方法获取的方法名是仅仅就是方法名(不带全类名),且不带有参数列表。

import java.lang.reflect.Method;
public class Demo02 {
    public static void main(String[] args) throws Exception {
        Class personClass = Person.class;
        Method[] methods = personClass.getMethods();
        for(Method method : methods){
            System.out.println(method);
            //获取方法名
            String name = method.getName();
            System.out.println(name);
        }
    }
}

关于获取成员方法们的另外两个方法

Method[] getDeclaredMethods()  
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
method.setAccessible(true);   //暴力反射

同之前的叙述一样,带有Declared关键字的方法这两个方法,可以获取到任意修饰符的方法。同样的提供了setAccessible(true);方法进行暴力反射。

综上说述:对于反射机制来说,在反射面前没有公有私有,都可以通过暴力反射解决

获取类名

getClass()方法是Object类的方法,需要注意一点获取的类名是全类名(带有路径)

Class personClass = Person.class;
String className = personClass.getName();
System.out.println(className);//带有路径的全类名(包名+类名)

案例

写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法。

实现

(1)配置文件,(2)反射

步骤

(1)将需要创建的对象的全类名和需要执行的方法定义在配置文件中

(2)在程序中加载读取配置文件

(3)使用反射技术来加载类文件进内存

(4)创建对象

(5)执行方法

代码实现

结构:

src
    com.coydone.test
    		Person.java
    		ReflectTest.java
    		Student.java

Person类

package com.coydone.test;
public class Person {
    public void eat(){
        System.out.println("eat...");
    }
}

Student类

package com.coydone.test;
public class Student {
    public void study(){
        System.out.println("learn...");
    }
}

编写配置文件

以后我们在配置文件内看见全类名,就应该想到可能使用到了反射

src下新建一个test.properties,在文件中写如下内容

className = com.coydone.test.Person
methodName = eat

编写测试方法(模拟框架)

package com.coydone.test;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
public class ReflectTest {
    public static void main(String[] args) throws Exception {
        /**
         * 前提:不能改变该类的任何代码。可以创建任意类的对象,可以执行任意方法
         * 即:拒绝硬编码
         */
        //1.加载配置文件
        //1.1创建Properties对象
        Properties pro = new Properties();
        //1.2加载配置文件,转换为一个集合
        //1.2.1获取class目录下的配置文件  使用类加载器
        ClassLoader classLoader = ReflectTest.class.getClassLoader();
        InputStream is = classLoader.getResourceAsStream("test.properties");
        pro.load(is);
        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");
        //3.加载该类进内存
        Class cls = Class.forName(className);
        //4.创建对象
        Object obj = cls.newInstance();
        //5.获取方法对象
        Method method = cls.getMethod(methodName);
        //6.执行方法
        method.invoke(obj);
    }
}

运行结果

修改配置文件,再次运行

#将配置文件内的信息修改为Student类及类内的方法
className = com.coydone.test.Student
methodName = study

好处

我们这样做有什么好处呢,对于框架来说,是人家封装好的,我们拿来直接用就可以了,而不能去修改框架内的代码。但如果我们使用传统的new形式来实例化,那么当类名更改时我们就要修改Java代码,这是很繁琐的。修改Java代码以后我们还要进行测试,重新编译、发布等等一系列的操作。而如果我们仅仅只是修改配置文件,就来的简单的多,配置文件就是一个实实在在的物理文件。

此外使用反射还能达到解耦的效果,假设我们使用的是new这种形式进行对象的实例化。此时如果在项目的某一个小模块中我们的一个实例类丢失了,那么在编译期间就会报错,以导致整个项目无法启动。而对于反射创建对象Class.forName("全类名");这种形式,我们在编译期需要的仅仅只是一个字符串(全类名),在编译期不会报错,这样其他的模块就可以正常的运行,而不会因为一个模块的问题导致整个项目崩溃。这就是Spring框架中IoC控制反转的本质。

0

评论区