Java 的反射技术


反射

1. 速查清单

在这里插入图片描述


2. 类加载器

a. 类的加载

  • 当我们的程序在运行后,第一次使用某个类的时候,会将此类的 .class 文件读取到内存,并将此类的所有信息存储到一个 Class 对象中;
    在这里插入图片描述
  1. 上图中,Class 对象是指 java.lang.Class 类的对象,此类由 Java 类库提供,专门用于存储类的信息;
  2. 我们程序中可以通过 “类名.class”,或者 “对象.getClass()” 方法获取这个 Class 对象;

b. 类的加载时机

  • 以下六种情况的任何一种,都可以导致 JVM 将一个类加载到方法区:
    1. 创建类的实例;
    2. 类的静态变量,或者为静态变量赋值;
    3. 类的静态方法;
    4. 使用反射方式来强制创建某个类或接口对应的 java.lang.Class 对象;
    5. 初始化某个类的子类;
    6. 直接使用 java.exe 命令来运行某个主类;

c. 类加载器

  • 类加载器:是负责将磁盘上的某个 class 文件读取到内存并生成 Class 的对象;
  • Java 中有三种类加载器,它们分别用于加载不同种类的 class:
    • 启动类加载器(Bootstrap ClassLoader):用于加载系统类库 <JAVA_HOME>\bin 目录下的 class,例如:rt.jar;
    • 扩展类加载器(Extension ClassLoader):用于加载扩展类库 <JAVA_HOME>\lib\ext 目录下的 class;
    • 应用程序类加载器(Application ClassLoader):用于加载我们自定义类的加载器;
public class Test {
    public static void main(String[] args) {
        System.out.println(Test.class.getClassLoader());//sun.misc.Launcher$AppClassLoader
        System.out.println(String.class.getClassLoader());//null(API中说明:一些实现可能使用null来表示引导类加载器。 如果此类由引导类加载器加载,则此方法将在此类实现中返回null。 )
    }
}

c. 双亲委派机制

在这里插入图片描述

上图展示了 “类加载器” 的层次关系,这种关系称为类加载器的 “双亲委派模型”:

  • “双亲委派模型"中,除了顶层的启动类加载器外,其余的类加载器都应当有自己的"父级类加载器”;
  • 这种关系不是通过 “继承” 实现的,通常是通过 “组合” 实现的。通过 “组合” 来表示父级类加载器;
  • “双亲委派模型” 的工作过程:
    • 某个 “类加载器” 收到类加载的请求,它首先不会尝试自己去加载这个类,而是把请求交给父级类加载器;
    • 因此,所有的类加载的请求最终都会传送到顶层的 “启动类加载器” 中;
    • 如果 “父级类加载器” 无法加载这个类,然后子级类加载器再去加载;

d. 双亲委派机制的好处

  • 双亲委派机制的一个显而易见的好处是:Java 的类随着它的类加载器一起具备了一种带有优先级的层次关系。例如:java.lang.Object。它存放在 rt.jar 中。无论哪一个类加载器要加载这个类,最终都是委派给处于顶端的 “启动类加载器” 进行加载,因此 java.lang.Object 类在程序的各种类加载器环境中都是同一个类;
  • 相反,如果没有 “双亲委派机制”,如果用户自己编写了一个 java.lang.Object,那么当我们编写其它类时,这种隐式的继承使用的将会是用户自己编写的 java.lang.Object 类,那将变得一片混乱;

3. 反射的概述

a. 反射的引入

  • 问题:IDEA 中的对象是怎么知道类有哪些属性,有哪些方法的呢?
    • 通过反射技术对象类进行了解剖得到了类的所有成员;

b. 反射的概念

  • 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法);

c. 使用反射操作类成员的前提

  • 要获得该类字节码文件对象,就是 Class 对象;

d. 反射在实际开发中的应用

  • 开发 IDE(集成开发环境),比如 IDEA,Eclipse;
  • 各种框架的设计和学习,比如 Spring,Hibernate,Struct,Mybaits…

4. Class 对象的获取方式

a. 三种获取方法

  • 方式 1:通过类名 .class 获得;
  • 方式 2:通过对象名 .getClass() 方法获得;
  • 方式 3:通过 Class 类的静态方法获得: static Class forName(“类全名”),注意:每一个类的 Class 对象都只有一个;
public class Student{

}
public class Test {
    public static void main(String[] args) throws ClassNotFoundException {
        // 获得Student类对应的Class对象
        Class c1 = Student.class;

        // 创建学生对象
        Student stu = new Student();
        // 通过getClass方法
        Class c2 = stu.getClass();
        System.out.println(c1 == c2);

        // 通过Class类的静态方法获得: static Class forName("类全名")
        Class c3 = Class.forName("Student");
        System.out.println(c1 == c3);
        System.out.println(c2 == c3);
    }
}
/*
输出
true
true
true
 */

b. Class 类常用方法

  • String getSimpleName(); 获得类名字符串:类名;
  • String getName(); 获得类全名:包名 + 类名;
  • T newInstance(); 创建 Class 对象关联类的对象;
public class Test {
    public static void main(String[] args) throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得类名字符串:类名
        System.out.println(c.getSimpleName());
        // 获得类全名:包名+类名
        System.out.println(c.getName());
        // 创建对象
        //Student stu = (Student) c.newInstance();//Java 9后newInstance()方法显示过时
        Student stu = (Student) c.getDeclaredConstructor().newInstance();
        System.out.println(stu);
    }
}
/*
输出
Student
Student
Student@33c7353a
 */

5. 反射之操作构造方法

a. Constructor 类概述

  • 反射之操作构造方法的目的:获得 Constructor 对象来创建类的对象;
  • Constructor 类概述:类中的每一个构造方法都是一个 Constructor 类的对象;

b. Class 类中与 Constructor 相关的方法

  • Constructor getConstructor(Class... parameterTypes)
    根据参数类型获得对应的 Constructor 对象;
    只能获得 public 修饰的构造方法;
  • Constructor getDeclaredConstructor(Class... parameterTypes)
    根据参数类型获得对应的 Constructor 对象;
    可以是 public、protected、(默认)、private 修饰符的构造方法;
  • Constructor[] getConstructors()
    获得类中的所有构造方法对象,只能获得 public 的;
  • Constructor[] getDeclaredConstructors()
    获得类中的所有构造方法对象;
    可以是 public、protected、(默认)、private 修饰符的构造方法;

c. Constructor 对象常用方法

  • T newInstance(Object... initargs)
    根据指定的参数创建对象;
  • void setAccessible(true)
    设置 “暴力反射” ——是否取消权限检查,true 取消权限检查,false 表示不取消;

d. 示例代码

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

    //公有构造方法
    public Student(String name, String sex, int age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    //私有构造方法
    private Student(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
}
import org.junit.Test;
import java.lang.reflect.Constructor;

public class TestStudent {

    /*
      Constructor[] getConstructors()
           获得类中的所有构造方法对象,只能获得public的
      Constructor[] getDeclaredConstructors()
            获得类中的所有构造方法对象,包括private修饰的
    */
    @Test
    public void test03() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        //  获得类中的所有构造方法对象,只能获得public的
        // Constructor[] cons = c.getConstructors();
        // 获取类中所有的构造方法,包括public、protected、(默认)、private的
        Constructor[] cons = c.getDeclaredConstructors();
        for (Constructor con:cons) {
            System.out.println(con);
        }
    }

    /*
       Constructor getDeclaredConstructor(Class... parameterTypes)
           根据参数类型获得对应的Constructor对象
    */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得两个参数构造方法对象
        Constructor con = c.getDeclaredConstructor(String.class,String.class);
        // 取消权限检查(暴力反射)
        con.setAccessible(true);
        // 根据构造方法创建对象
        Object obj = con.newInstance("rose","女");
        System.out.println(obj);
    }

    /*
        Constructor getConstructor(Class... parameterTypes)
            根据参数类型获得对应的Constructor对象
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得无参数构造方法对象
        Constructor con = c.getConstructor();
        // 根据构造方法创建对象
        Object obj = con.newInstance();
        System.out.println(obj);

        // 获得有参数的构造方法对象
        Constructor con2 = c.getConstructor(String.class, String.class,int.class);
        // 创建对象
        Object obj2 = con2.newInstance("jack", "男",18);
        System.out.println(obj2);
    }
}
/*
输出
java.lang.NoSuchMethodException: Student.<init>()
Student@1b40d5f0
public Student(java.lang.String,java.lang.String,int)
private Student(java.lang.String,java.lang.String)
 */

6. 反射之操作成员方法

a. Method 类概述

  • 反射之操作成员方法的目的:操作 Method 对象来调用成员方法;
  • Method 类概述:每一个成员方法都是一个 Method 类的对象;

b. Class 类中与 Method 相关的方法

  • Method getMethod(String name,Class...args);
    根据方法名和参数类型获得对应的构造方法对象,只能获得 public 的
  • Method getDeclaredMethod(String name,Class...args);
    根据方法名和参数类型获得对应的构造方法对象,包括 public、protected、(默认)、private 的;
  • Method[] getMethods();
    获得类中的所有成员方法对象,返回数组,只能获得 public 修饰的且包含父类的;
  • Method[] getDeclaredMethods();
    获得类中的所有成员方法对象,返回数组,只获得本类的,包括 public、protected、(默认)、private 的;

c. Method 对象常用方法

  • Object invoke(Object obj, Object... args)
    调用指定对象 obj 的该方法;
    args:调用方法时传递的参数;
  • void setAccessible(true)
    设置 “暴力访问” ——是否取消权限检查,true 取消权限检查,false 表示不取消;

d. 示例代码

public class Student {
    private void eat(String str) {
        System.out.println("Eat" + str);
    }

    private void sleep() {
        System.out.println("Sleep");
    }

    public void study(int a) {
        System.out.println("Study " + a);
    }
}
import org.junit.Test;

import java.lang.reflect.Method;

public class TestStudent {

    // 反射操作静态方法
    @Test
    public void test04() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 根据方法名获得对应的公有成员方法对象
        Method method = c.getDeclaredMethod("eat", String.class);
        // 通过method执行对应的方法
        method.invoke(null, "蛋炒饭");
    }

    /*
     * Method[] getMethods();
     * 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
     * Method[] getDeclaredMethods();
     * 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
     */
    @Test
    public void test03() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
        // Method[] methods = c.getMethods();
        // 获得类中的所有成员方法对象,返回数组,只获得本类的,包含private修饰的
        Method[] methods = c.getDeclaredMethods();
        for (Method m : methods) {
            System.out.println(m);
        }
    }

    /*
       Method getDeclaredMethod(String name,Class...args);
           * 根据方法名和参数类型获得对应的构造方法对象,
    */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c = Student.class;

        // 根据Class对象创建学生对象
        Student stu = (Student) c.getDeclaredConstructor().newInstance();
        // 获得sleep方法对应的Method对象
        Method m = c.getDeclaredMethod("sleep");
        // 暴力反射
        m.setAccessible(true);

        // 通过m对象执行sleep方法
        m.invoke(stu);
    }

    /*
        Method getMethod(String name,Class...args);
            * 根据方法名和参数类型获得对应的构造方法对象,
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;

        // 根据Class对象创建学生对象
        Student stu = (Student) c.getDeclaredConstructor().newInstance();
        // 获得study方法对应的Method对象
        Method m = c.getMethod("study");
        // 通过m对象执行study方法
        m.invoke(stu);


        /// 获得study方法对应的Method对象
        Method m2 = c.getMethod("study", int.class);
        // 通过m2对象执行study方法
        m2.invoke(stu, 8);
    }
}
/*
输出
java.lang.NoSuchMethodException: Student.study()
Sleep
private void Student.sleep()
public void Student.study(int)
private void Student.eat(java.lang.String)
java.lang.NullPointerException
 */

7. 反射之操作成员变量

a. Field 类概述

  • 反射之操作成员变量的目的:通过 Field 对象给对应的成员变量赋值和取值;
  • Field 类概述:每一个成员变量都是一个 Field 类的对象;

b. Class 类中与 Field 相关的方法

  • Field getField(String name);
    根据成员变量名获得对应 Field 对象,只能获得 public 修饰;
  • Field getDeclaredField(String name);
    根据成员变量名获得对应 Field 对象,包括 public、protected、(默认)、private 的;
  • Field[] getFields();
    获得所有的成员变量对应的 Field 对象,只能获得 public 的;
  • Field[] getDeclaredFields();
    获得所有的成员变量对应的 Field 对象,包括 public、protected、(默认)、private 的;

c. Field 对象常用方法

  • void set(Object obj, Object value)
  • void setInt(Object obj, int i)
  • void setLong(Object obj, long l)
  • void setBoolean(Object obj, boolean z)
  • void setDouble(Object obj, double d)
  • Object get(Object obj)
  • int getInt(Object obj)
  • long getLong(Object obj)
  • boolean getBoolean(Object ob)
  • double getDouble(Object obj)
  • void setAccessible(true); 暴力反射,设置为可以直接访问私有类型的属性;
  • Class getType(); 获取属性的类型,返回 Class 对象;

setXxx 方法都是给对象 obj 的属性设置使用,针对不同的类型选取不同的方法;
getXxx 方法是获取对象 obj 对应的属性值的,针对不同的类型选取不同的方法;

d. 示例代码

public class Student {
    public String name;
    private String gender;

    public String toString() {
        return "Student [name = " + name + " , gender = " + gender + "]";
    }
}
import org.junit.Test;
import java.lang.reflect.Field;

public class TestStudent {
    /*
        Field[] getFields();
            * 获得所有的成员变量对应的Field对象,只能获得public的
        Field[] getDeclaredFields();
            * 获得所有的成员变量对应的Field对象,包含private的
     */
    @Test
    public void test02() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 获得所有的成员变量对应的Field对象
        // Field[] fields = c.getFields();
        // 获得所有的成员变量对应的Field对象,包括private
        Field[] fields = c.getDeclaredFields();
        for (Field f : fields) {
            System.out.println(f);
        }
    }

    /*
        Field getField(String name);
            根据成员变量名获得对应Field对象,只能获得public修饰
        Field getDeclaredField(String name);
            *  根据成员变量名获得对应Field对象,包含private修饰的
     */
    @Test
    public void test01() throws Exception {
        // 获得Class对象
        Class c = Student.class;
        // 创建对象
        Object obj = c.getDeclaredConstructor().newInstance();
        // 获得成员变量name对应的Field对象
        Field f = c.getField("name");
        // 给成员变量name赋值
        // 给指定对象obj的name属性赋值为jack
        f.set(obj, "jack");

        // 获得指定对象obj成员变量name的值
        System.out.println(f.get(obj)); // jack
        // 获得成员变量的名字
        System.out.println(f.getName()); // name


        // 给成员变量gender赋值
        // 获得成员变量gender对应的Field对象
        Field f1 = c.getDeclaredField("gender");
        // 暴力反射
        f1.setAccessible(true);
        // 给指定对象obj的gender属性赋值为男
        f1.set(obj, "男");

        System.out.println(obj);
    }
}
/*
输出
jack
name
Student [name = jack , gender = 男]
public java.lang.String Student.name
private java.lang.String Student.gender
 */

8. 练习题

  1. 请定义以下类:
public class Student {
    private String name;
    private int age;

    //公有、无参构造
    
    //公有、全参构造
    
    //公有、get/set方法
    
    //普通成员方法
    public void show() {
        System.out.println("Name: " + name + ", Age: " + age);
    }
}
  1. 请编写 main() 方法:
    (1) 使用反射获取 Student 的 Class 对象;
    (2) 获取 “公有、全参构造方法”;
    (3) 调用 “公有、全参构造方法”传入:“柳岩”,17 两个参数;
    (4) 反射获取 show() 方法;
    (5) 调用 show() 方法;
  2. 答案:
public class Student {

    private String name;
    private int age;

    public Student() {
    }

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

    public void show() {
        System.out.println("Name: " + name + ", Age: " + 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;
    }
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class TestStudent {
    public static void main(String[] args) throws Exception {
        //使用反射获取Student的Class对象
        Class clazz = Student.class;
        //获取 “公有、全参构造方法”
        Constructor con = clazz.getDeclaredConstructor(String.class, int.class);
        //调用 “公有、全参构造方法”传入:“柳岩”,17两个参数
        Student stu = (Student) con.newInstance("柳岩", 17);
        //反射获取show()方法
        Method show = clazz.getDeclaredMethod("show");
        //调用show()方法
        show.invoke(stu);
    }
}
/*
输出
Name: 柳岩, Age: 17
 */
发布了310 篇原创文章 · 获赞 315 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Regino/article/details/105155824