目录

1.定义:

2.用途:

3.反射的使用:

3.1获取class文件的三种方式:

3.2反射获取构造函数:

3.3反射获取成员变量:

3.4反射获取成员方法:

4.反射的利与弊:


1.定义:

        Java的反射机制(reflection)机制就是在运行状态中,对于任何一个类,都能获取这个类的属性和方法。对于如何一个对象,都能够调用它任意的方法和属性(包括private修饰的字段),那么既然我们能够拿到这些方法和属性,那么我们当然就可以修改它们了。这种动态获取信息以及动态调用方法的功能就被称为Java语言的反射机制。

        通俗地讲,反射就是把类里的属性和方法映射为一个个Java对象,我们通过操控这些对象来调用或修改类里的属性和方法。

        想要解剖一个类,必然要拿到这个类的字节码文件对象。而解剖使用的就是Class类的方法。

Class类:

         Class 类的实例表示正在运行的 Java 应用程序中的类和接口。也就是jvm中有N多的实例每个类都有该Class对象。(包括基本数据类型)

        Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为 一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类。

        Class类没有公共的构造方法,Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。也就是这不需要我们自己去处理创建,JVM已经帮我们创建好了。

2.用途:

        反射在实际项目中应用的非常的广泛,很多设计和开发都和反射有关,比如通过反射去调用字节码文件、调用系统隐藏 Api、动态代理的设计模式,Android 逆向、著名的 Spring 框架、各类 Hook 框架等等。

3.反射的使用:

我们来试着获取Student类的属性和方法:

package com.MyRefection;

public class Student {
    private String name;
    private int age;
    public int id;

    private Student(String name) {
        this.name = name;
    }

    protected Student(int age) {
        this.age = age;
    }

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

    public void study(int time) {
        System.out.println(this.name + "学习了" + time + "个小时");
    }
    
    private void eat(String food) {
        System.out.println(this.name + "中午吃了" + food);
    }

    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 int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

3.1获取class文件的三种方式:

第一种,使用 Class.forName("类的全路径名"); 静态方法。 前提:已明确类的全路径名。

 此处,我们有一个Student类,我们要获取它的全路径->

java反射可以动态给类添加属性 java反射增加属性_开发语言

java反射可以动态给类添加属性 java反射增加属性_jvm_02

点击这个Copy Reference就可以获取到我们的全路径啦

 tips:这种方式获取的全路径只有复制到双引号""或注释里才会显示完整~~

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        //第一种,使用 Class.forName("类的全路径名"); 静态方法。 前提:已明确类的全路径名。
        Class class1 = Class.forName("com.MyRefection.Student");

        //我们来输出一下
        System.out.println(class1);//运行结果:class com.MyRefection.Student
    }
}

这种方法是最常用,最安全的。

第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class。

package com.MyRefection;

public class Main {
    public static void main(String[] args){
        //第二种,使用 .class 方法。 说明:仅适合在编译前就已经明确要操作的 Class。
        Class class2 = Student.class;

        //我们来输出一下
        System.out.println(class2);//运行结果:class com.MyRefection.Student
    }
}

第三种,使用类对象的 getClass() 方法。

package com.MyRefection;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        //第三种,使用类对象的 getClass() 方法。
        Student student = new Student("张三", 16, 116);
        Class class3 = student.getClass();

        //我们来输出一下
        System.out.println(class3);//运行结果:class com.MyRefection.Student
    }
}

使用这种方法要先创建该类的实例对象。

那么用这三种方法创建的Class对象是否相等呢?

System.out.println(class1 == class2 && class2 == class3);//运行结果:true

答案是true~~

3.2反射获取构造函数:

方法

功能

getConstructor(Class... parameterTypes)

获得该类中与参数类型匹配的公有构造方法

getConstructors()

获得该类的所有公有构造方法

getDeclaredConstructor(Class... parameterTypes)

获得该类中与参数类型匹配的构造方法

getDeclaredConstructors()

获得该类所有构造方法

newInstance()

创建实例

setAccessible(boolean flag)

表示是否取消权限的校验

注意:带有Declared字眼的就代表可以获取所有方法或属性(包括private修饰的)

1.getConstructors()

package com.MyRefection;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.创建Constructor[]数组来获取构造方法
        Constructor[] constructors1 = clazz.getConstructors();

        //遍历一下看看效果:
        for (Constructor constructor : constructors1) {
            System.out.println(constructor);
        }
        //执行结果:
        //public com.MyRefection.Student(java.lang.String,int,int)
        //只能访问到public修饰的构造函数

    }
}

2.getDeclaredConstructors()

package com.MyRefection;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.使用getDeclaredConstructors方法来获取全部构造方法
        Constructor[] constructors2 = clazz.getDeclaredConstructors();
        //遍历一下看看效果:
        for (Constructor constructor : constructors2) {
            System.out.println(constructor);
        }
        //执行结果:
        //public com.MyRefection.Student(java.lang.String,int,int)
        //protected com.MyRefection.Student(int)
        //private com.MyRefection.Student(java.lang.String)
        //可以访问其他修饰符(private/protected)修饰的构造函数

    }
}

3.getConstructor(Class... parameterTypes)

package com.MyRefection;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.获取单个构造函数
        Constructor constructor1 = clazz.getConstructor();
        //输出一下看看效果
        System.out.println(constructor1);
        //执行之后报错了~~
    }
}

java反射可以动态给类添加属性 java反射增加属性_jvm_03

 原因是我们Student的构造函数中并没有无参构造函数,我们想要调用哪个函数,就要在getConstructor()中加该函数的参数类型的字节码文件,我们以这个含三个参数的构造方法为例:

java反射可以动态给类添加属性 java反射增加属性_开发语言_04

想要获取这个构造函数,应该这样写:

package com.MyRefection;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.获取单个构造函数
        Constructor constructor1 = clazz.getConstructor(String.class, int.class, int.class);
        //输出一下看看效果
        System.out.println(constructor1);
        //执行结果:public com.MyRefection.Student(java.lang.String,int,int)
    }
}

 注意,

java反射可以动态给类添加属性 java反射增加属性_java_05

 这里传递的是参数类型的字节码文件!!!

4.getDeclaredConstructor(Class... parameterTypes)

        注意getConstructor(Class... parameterTypes)并不能获取私有的构造函数,这时候就要使用getDeclaredConstructor(Class... parameterTypes):

java反射可以动态给类添加属性 java反射增加属性_java_06

被private修饰

package com.MyRefection;

import java.lang.reflect.Constructor;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.获取单个构造函数(包含被private/protected修饰的)
        Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
        //输出一下看看效果
        System.out.println(constructor2);
        //执行结果:
        //private com.MyRefection.Student(java.lang.String)
    }
}

OK,现在我们获取到这个构造函数了,那我们可以使用它来做什么事情呢?

1.我们可以使用constructor2这个对象来查看它所指向的构造函数是什么类型的:

//我们可以使用constructor2这个对象来查看它所指向的构造函数是什么类型的
        System.out.println(constructor2.getModifiers());
        //执行结果:2

为什么执行结果是2呢?

        原因是通过这个函数我们获取到的是常量字段值,是int类型,而不是String类型,下面是反射的常量字段值:

java反射可以动态给类添加属性 java反射增加属性_构造函数_07

 可以看到private的常量字段值是2~~

2.获取构造方法的参数:

java反射可以动态给类添加属性 java反射增加属性_开发语言_08

package com.MyRefection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.获取单个构造函数
        Constructor constructor1 = clazz.getConstructor(String.class, int.class, int.class);
        //输出一下看看效果
        System.out.println(constructor1);
        //执行结果:
        //public com.MyRefection.Student(java.lang.String,int,int)



        //3.获取三个参数的构造函数的参数
        Parameter[] parameters = constructor1.getParameters();
        //打印一下
        for (Parameter parameter : parameters) {
            System.out.println(parameter);
        }
        //结果:
        //java.lang.String arg0
        //int arg1
        //int arg2

    }
}

3.使用反射创建对象:

package com.MyRefection;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Parameter;

public class Main {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.获取单个构造函数(包含被private/protected修饰的)
        Constructor constructor2 = clazz.getDeclaredConstructor(String.class);
        //输出一下看看效果
        System.out.println(constructor2);
        //执行结果:
        //private com.MyRefection.Student(java.lang.String)


        //3.使用反射创建对象,这里我们使用private修饰的构造函数来创建对象
        Student student = (Student) constructor2.newInstance("张三");
        //执行结果:报错了~~
    }
}

原因是这个构造函数被private修饰,不能直接创建,我们需要使用setAccessible(boolean flag)来取消权限的校验:

//3.使用反射创建对象,这里我们使用private修饰的构造函数来创建对象

        constructor2.setAccessible(true);//设置为true表示取消对权限的校验

        Student student = (Student) constructor2.newInstance("张三");
        //打印一下这个student对象
        System.out.println(student);
        //执行结果:com.MyRefection.Student@1b6d3586

注意如果这个构造函数是public修饰的则可以不用加setAccessible(boolean flag)语句~~

3.3反射获取成员变量:

方法

功能

setAccessible(boolean flag)

获得某个公有的属性对象

getFields()

获得所有公有的属性对象

getDeclaredField(String name)

获得某个属性对象

getDeclaredFields()

获得所有属性对象

void set(Object obj, Object value)

赋值

Object get(Object obj)

获取值

        前四个方法与前面反射获取构造函数大相径庭,这里就不过多讲解了(直接上代码):

package com.MyRefection;

import java.lang.reflect.Field;

public class Main2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.创建Field数组来获取公有的成员变量
        Field[] fields1 = clazz.getFields();
        //遍历一下
        for (Field field : fields1) {
            System.out.println(field);
        }
        //输出结果:
        //public int com.MyRefection.Student.id


        //3.创建Field[]数组来获取所有成员变量
        Field[] fields2 = clazz.getDeclaredFields();
        //遍历一下
        for (Field field : fields2) {
            System.out.println(field);
        }
        //输出结果:
        //private java.lang.String com.MyRefection.Student.name
        //private int com.MyRefection.Student.age
        //public int com.MyRefection.Student.id


        //4.获取单个公有的成员变量
        Field field3 = clazz.getField("id");//传递的参数是想要获取的成员变量的名称
        //输出一下
        System.out.println(field3);
        //输出结果
        //public int com.MyRefection.Student.id


        //5.获取单个私有的成员变量
        Field field4 = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称
        //输出一下
        System.out.println(field4);
        //输出结果
        //private java.lang.String com.MyRefection.Student.name
    }
}

主要为大家讲解一下get和set方法:

get():

package com.MyRefection;

import java.lang.reflect.Field;

public class Main2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");


        //2.获取单个私有的成员变量
        Field name = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称

        //3.使用这个name来获取成员变量的值
        //要想获得成员变量的值首先要创建对象
        Student student = new Student("张三", 16, 116);

        Object value = name.get(student);//这里传递的参数是创建的对象
        System.out.println(value);
        //执行结果:出错啦~~
    }
}

java反射可以动态给类添加属性 java反射增加属性_开发语言_09

出错的原因是这个成员变量被private修饰,不能直接获取,要使用setAccessible(boolean flag)来取消权限的校验:

//2.获取单个私有的成员变量
        Field name = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称

        //3.使用这个name来获取成员变量的值
        //要想获得成员变量的值首先要创建对象
        Student student = new Student("张三", 16, 116);

        //4.取消访问权限的校验
        name.setAccessible(true);
        
        //5.获取成员变量的值
        Object value = name.get(student);//这里传递的参数是创建的对象
        System.out.println(value);
        //执行结果:张三

 

set():

package com.MyRefection;

import java.lang.reflect.Field;

public class Main2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.获取单个私有的成员变量
        Field name = clazz.getDeclaredField("name");//传递的参数是想要获取的成员变量的名称

        //3.使用这个name来获取成员变量的值
        //要想获得成员变量的值首先要创建对象
        Student student = new Student("张三", 16, 116);

        //4.同样取消访问权限的校验
        name.setAccessible(true);

        //5.使用这个name来修改成员变量的值:张三 -> 李四
        name.set(student, "李四");
        System.out.println(student.getName());
        //执行结果:李四
    }
}

3.4反射获取成员方法:

方法

功能

getMethod(String name, Class... parameterTypes)

获得该类某个公有的方法

getMethods()

获得该类所有公有的方法

getDeclaredMethod(String name, Class... parameterTypes)

获得该类某个方法

getDeclaredMethods()

获得该类所有方法

Object invoke(Object obj, 0bject... args)

运行方法

 

我们来看一下这个getMethods()可以获取到什么方法

package com.MyRefection;

import java.lang.reflect.Method;

public class Main3 {
    public static void main(String[] args) throws ClassNotFoundException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.创建Method[]数组来获取所有公有成员方法
        Method[] methods1 = clazz.getMethods();
        //遍历一下
        for (Method method : methods1) {
            System.out.println(method);
        }
        
    }
}

执行结果:

java反射可以动态给类添加属性 java反射增加属性_开发语言_10

java反射可以动态给类添加属性 java反射增加属性_jvm_11

java反射可以动态给类添加属性 java反射增加属性_jvm_12

  从执行结果可以发现,除了有本身的成员方法外,还有继承自Object类的常用方法~~

同样前四个方法和之前的大相径庭,就不做详细讲解了(直接上代码)~~

package com.MyRefection;

import java.lang.reflect.Method;

public class Main3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //2.创建Method[]数组来获取所有公有成员方法
        Method[] methods1 = clazz.getMethods();

        //3.创建Method[]数组来获取所有成员方法(包括private/protected修饰的)
        Method[] methods2 = clazz.getDeclaredMethods();

        //4.获取单个公有成员方法
        Method method3 = clazz.getMethod("study", int.class);
        //第一个参数是方法名,第二个参数是要传入的参数的类型

        //5.获取单个公有成员方法
        Method method4 = clazz.getDeclaredMethod("eat", String.class);
        //第一个参数是方法名,第二个参数是要传入的参数的类型
    }
}

invoke方法

package com.MyRefection;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main3 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        //1.获取.class字节码文件
        Class clazz = Class.forName("com.MyRefection.Student");

        //以私有方法为例:

        //2.获取单个公有成员方法
        Method method4 = clazz.getDeclaredMethod("eat", String.class);
        //第一个参数是方法名,第二个参数是要传入的参数的类型

        //3.创建Student类的对象
        Student student = new Student("张三", 16, 110);

        //4.取消访问权限的校验
        method4.setAccessible(true);

        //5.调用invoke方法
        method4.invoke(student, "汉堡包");
        //第一个参数为创建的对象,之后的参数是要调用方法的参数
        //执行结果:张三中午吃了汉堡包

    }
}

注意:同样的,如果要调用的方法是private/protected修饰的,也要取消访问权限的校验。

4.反射的利与弊:

反射的好处:

1.对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。

2. 增加程序的灵活性和扩展性,降低耦合性,提高自适应能力。

3. 反射已经运用在了很多流行框架如:Struts、Hibernate、Spring 等等。

反射的坏处:

 2. 反射技术绕过了源代码的技术,因而会带来维护问题。反射代码比相应的直接代码更复杂 。