JavaSE - 反射机制Reflection

反射机制

1- 静态语言 OR 动态语言

静态语言

  • 运行时结构不可变的语言就是静态语言。如Java、C、C++。
  • Java不是动态语言,但是Java有一定的动态性,可以利用反射机制获得类似动态语言的特性。

动态语言

主要动态语言:C#、JavaScript、Vue、Python。

在运行时可以改变其结构的语言,就是在运行时代码可以根据某些条件改变自身结构。

// js代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>arguments使用</title>
</head>
<body>
    <script>
        // 利用函数求任意个数的最大值
        function getMax() {
    
    
            // 定义最大值
            var max = arguments[0];
            for (var i = 1; i < arguments.length; i++) {
    
    
                if (arguments[i] > max) {
    
    
                    max = arguments[i];
                }
            }
            return max;
        }
        
        // 输出结果
        console.log(getMax(1, 2, 3));
        console.log(getMax(1, 2, 3, 4, 5));
        console.log(getMax(11, 2, 34, 444, 5, 100));
    </script>
</body>
</html>

2- 反射机制

2.1 基本概念

反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

Class c = Class.forName("类的权限名")

加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。可以通过这个对象看到类的结构。这个对象就像一面镜子,通过镜子看到类的结构,所以形象的称之为:反射

基本特点

反射是Java的高级技术,是Java独有的技术。反射的核心思想和关键就是得到编译以后的class文件对象

反射是工作在运行时的技术,因为只有运行之后才会有class类对象。

代码示例

实体类

package cn.guardwhy.reflection01;
// 实体类
class User {
    
    
    // 成员属性
    private int id;
    private int age;
    private String name;
    // 无参构造器
    public User() {
    
    

    }
    // 带参构造
    public User(int id, int age, String name) {
    
    
        this.id = id;
        this.age = age;
        this.name = name;
    }

    public int getId() {
    
    
        return id;
    }

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

    public int getAge() {
    
    
        return age;
    }

    public void setAge(int age) {
    
    
        this.age = age;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    @Override
    public String toString() {
    
    
        return "User{" +
                "id=" + id +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}

反射技术实现

package cn.guardwhy.reflection01;
// 反射
public class Test1 extends Object{
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1.通过反射获取类的Class对象
        Class c1 = Class.forName("cn.guardwhy.reflection01.User");

        System.out.println("c1的Class对象:" + c1); // c1的Class对象:class cn.guardwhy.reflection01.User

        Class c2 = Class.forName("cn.guardwhy.reflection01.User");
        System.out.println("c2的Class对象:" + c2); // c2的Class对象:class cn.guardwhy.reflection01.User

        //一个类被加载后,类的整个结构信息会被放到对应的Class对象中
        // 一个类只对应一个Class对象
        System.out.println("c1:" + c1.hashCode());  // c1:1735600054
        System.out.println("c2:" + c2.hashCode());  // c2:1735600054


    }
}

Java反射优点和缺点

优点:能实现动态创建对象和编译,体现出很大的灵活性。
缺点:对性能有影响。使用反射基本上是一种解释操作,这类操作总是慢于直接执行相同的操作。

2.2 Class类

源码分析

在Object类中定义了以下的方法,此方法将被所有子类继承,Object类是Java反射的源头,可以通过对象反射求出类的名称。

/**
  * Returns the runtime class of this {@code Object}. The returned
  * {@code Class} object is the object that is locked by {@code
  * static synchronized} methods of the represented class.
  *
  * <p><b>The actual result type is {@code Class<? extends |X|>}
  * where {@code |X|} is the erasure of the static type of the
  * expression on which {@code getClass} is called.</b> For
  * example, no cast is required in this code fragment:</p>
  *
  * <p>
  * {@code Number n = 0;                             }<br>
  * {@code Class<? extends Number> c = n.getClass(); }
  * </p>
  *
  * @return The {@code Class} object that represents the runtime
  *         class of this object.
  * @jls 15.8.2 Class Literals
*/
public final native Class<?> getClass();

Class类注意点

  • Class 本身也是一个类,Class 对象只能由系统建立对象。
  • 一个加载的类在 JVM 中只会有一个Class实例,一个Class对象对应的是一个加载到JVM中的一个.class文件。
  • 每个类的实例都会记得自己是由哪个 Class 实例所生成,通过Class可以完整地得到一个类中的所有被加载的结构。
  • Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象。

Class类的常用方法

方法名 方法作用
static ClassforName(String name) 返回指定类名name的Class对象
Object newInstance( ) 调用缺省构造函数,返回Class对象的一个实例
getName( ) 返回此Class对象所表示的实体(类,接口,数组类或 void)的名称。
Class getSuperClass( ) 返回当前Class对象的父类的Class对象。
Class[] getinterfaces( ) 获取当前Class对象的接口。
ClassLoader getClassLoader( ) 返回该类的类加载器。
Constructor[] getConstructors( ) 返回一个包含某些Constructor对象的数组。
Method getMothed(String name,Class… T) 返回一个Method对象,此对象的形参类型为paramType。
Field[] getDeclaredFields() 返回Field对象的一个数组。

获取Class类的实例

已知具体的类,通过类的class属性获取,该方法最为安全可靠,程序性能最高。

Class c3 = Student.class;

已知类的实例,调用该实例的getClass()方法获取Class对象。

Class c1 = person.getClass();

已知类的全类名,且该类在类路径下,可通过Class类的静态方法forName( )获取,可能抛出ClassNotFoundException。

Class c2 = Class.forName("cn.guardwhy.reflection01.Student");

内置基本数据类型可以直接用类名.Type

 Class c4 = Integer.TYPE;

代码示例

package cn.guardwhy.reflection01;
/*
* 获取Class类对象
*/

// 基类
class Person{
    
    
    // 姓名
    public String username;
    // 无参构造
    public Person() {
    
    

    }
    // 带参构造
    public Person(String username) {
    
    
        this.username = username;
    }

    @Override
    public String toString() {
    
    
        return "Person{" +
                "username='" + username + '\'' +
                '}';
    }
}
// 学生类
class Student extends Person{
    
    
    public Student() {
    
    
        this.username = "Curry";
    }
}

// Teacher类
class Teacher extends Person{
    
    
    public Teacher() {
    
    
        this.username = "Kobe";
    }
}

public class ReflectTest02{
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1.获取person对象
        Person person = new Student();
        System.out.println("姓名是:" + person.username);

        // 2.方式一: 通过对象获得
        Class c1 = person.getClass();
        System.out.println(c1.hashCode());

        // 3.方式二: 类去加载class文件对象,通过类的全限名找到。
        Class c2 = Class.forName("cn.guardwhy.reflection01.Student");
        System.out.println(c2.hashCode());

        // 4.方式三:通过类的静态成员class获得
        Class c3 = Student.class;
        System.out.println(c3.hashCode());

        // 5.方式四:基本内置类型的包装类都有一个Type属性
        Class c4 = Integer.TYPE;
        System.out.println(c4);
        // 6.通过子类获得父类Class类对象
        Class c5 = c2.getSuperclass();
        System.out.println("父类Class对象:" + c5);
    }
}

执行结果

哪些数据类型可以有Class对象?

  • class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
  • interface:接口
  • []:数组
  • enum:枚举
  • annotation:注解@interface
  • primitive type:基本数据类型
  • void

代码示例

package cn.guardwhy.reflection01;

import java.lang.annotation.ElementType;

/*
所有类型的Class
*/
public class ReflectTest03 {
    
    
    public static void main(String[] args) {
    
    
        Class c1 = Object.class; // 类对象
        Class c2 = Comparable.class; // 接口
        Class c3 = String[].class; // 一维数组
        Class c4 = int[][].class; // 二维数组
        Class c5 = Override.class; // 注解
        Class c6 = ElementType.class; // 枚举
        Class c7 = Integer.class; // 基本数据类型
        Class c8 = void.class; // void
        Class c9 = Class.class; // Class

        System.out.println(c1); // class java.lang.Object
        System.out.println(c2); // interface java.lang.Comparable
        System.out.println(c3); // class [Ljava.lang.String;
        System.out.println(c4); // class [[I
        System.out.println(c5); // interface java.lang.Override
        System.out.println(c6); // class java.lang.annotation.ElementType
        System.out.println(c7); // class java.lang.Integer
        System.out.println(c8); // void
        System.out.println(c9); // class java.lang.Class

        // 只要元素类型与维度是一样的,就是同一个Class
        int[] a1 = new int[10];
        int[] a2 = new int[100];
        System.out.println(a1.getClass().hashCode());   // 1735600054
        System.out.println(a2.getClass().hashCode());   // 1735600054
    }
}

3- Java内存分析

3.1 内存分析图

3.2 类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过以下步骤对该类进行初始化。

3.3 类加载器ClassLoader

加载

  • 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,
    然后生成一个代表这个类的java.lang.Class对象。

链接:将Java类的二进制代码合并到JVM的运行状态之中的过程。

  • 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
  • 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
  • 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

初始化

  • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
  • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
  • 虚拟机会保证一个类的( )方法在多线程环境中被正确加锁和同步。

3.4 类初始化

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象。
  • 调用类的静态成员(除了final常量)和静态方法。
  • 使用java.lang.reflect包的方法对类进行反射调用。
  • 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。
  • 通过数组定义类引用,不会触发此类的初始化
  • 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)

代码示例

package cn.guardwhy.reflection_02;
/**
 * 类什么时候初始化
*/

// 基类
class Person{
    
    
    // 定义静态变量a
    static int a = 10;
    static {
    
    
        System.out.println("父类被加载");
    }
}
// 派生类
class Student extends Person{
    
    
    static {
    
    
        System.out.println("子类被加载!!!");
        b = 30;
    }
    // 定义静态变量
    static int b = 21;
    // 定义常量
    static final int C = 11;
}
public class ReflectTest01 {
    
    
    // 静态方法
    static {
    
    
        System.out.println("main类被加载!!");
    }
    public static void main(String[] args) throws ClassNotFoundException {
    
    
        // 1.1 主动引用
        //Student stu = new Student();
        // 1.2 反射也会产生主动调用
        // Class.forName("cn.guardwhy.reflection_02.Student");

        // 2.1 不会产生类的引用方法, 子类不会被加载
        System.out.println(Student.a);
        // 2.2 调用常量
        System.out.println(Student.C);
    }
}

3.5 类加载器作用

类加载的作用

将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。

类缓存

标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

类加载器作用是用来把类(class)装载进内存的。JVM 规范定义了如下类型的类的加载器

引导类加载器

用C++编写的,是JVM自带的类加载器,负责Java平台核心库。用来装载核心类库,该加载器无法直接获取。

拓展类加载器

负责jre/lib/ext目录下的jar包或者-D java.ext.dirs 指定目录下的jar包装入工作库。

系统类加载器

负责java -classpath 或者 -D java.class.path所指的目录下的类与jar包装入工作,是最常用的加载器。

代码示例

package cn.guardwhy.reflection_02;

public class ReflectTest02 {
    
    
    public static void main(String[] args) throws ClassNotFoundException {
    
    
        // 1.获取系统类的加载器
        ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
        System.out.println(systemClassLoader);  // sun.misc.Launcher$AppClassLoader@18b4aac2
        // 2.获取系统类加载器的父类加载器
        ClassLoader parent = systemClassLoader.getParent();
        System.out.println(parent); // sun.misc.Launcher$ExtClassLoader@677327b6
        // 3.获取拓展类加载器的父类加载器--> 跟加载器
        ClassLoader parent1 = parent.getParent();
        System.out.println(parent1);    // null

        // 4.测试当前类的是哪个加载器加载的
        ClassLoader classLoader = Class.
        forName("cn.guardwhy.reflection_02.ReflectTest02")
        .getClassLoader();
        System.out.println(classLoader);
        // 5.测试JDK内置的类是谁加载的
        ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
        System.out.println(classLoader1);

        // 6.如何获得系统类加载器可以加载的路径
        System.out.println(System.getProperty("java.class.path"));
    }
}

4- 获取类的结构

4.1 获取Constructor构造器对象

代码示例

实体类

package cn.guardwhy_03;
/**
 学生类
 */
public class Student {
    
    
    // 成员变量
    private String name;
    private int age;
    // 无参构造器
    public Student() {
    
    

    }
    // 带参构造器
    public Student(String name, int age) {
    
    
        System.out.println("==有参构造器==");
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

反射获取Constructor构造器对象

package cn.guardwhy_03;

import org.junit.Test;

import java.lang.reflect.Constructor;

/**
反射获取Constructor构造器对象.反射的第一步是先得到Class类对象。

反射中Class类型获取构造器提供了很多的API:
     1. Constructor getConstructor(Class... parameterTypes)
        根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
     2. Constructor getDeclaredConstructor(Class... parameterTypes)
        根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
     3. Constructor[] getConstructors()
        获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!
     4. Constructor[] getDeclaredConstructors()
        获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
总结:
     获取全部构造器,建议用getDeclaredConstructors();
     获取某个构造器,建议用getDeclaredConstructor(Class... parameterTypes)
     他们都无所谓权限,只要申明了就可以去取!!反射是破环封装性的!!
 */
public class TestReflect01 {
    
    
     @Test
     public void getConstructors(){
    
    
         // 1.反射第一步先得到Class类对象
         Class clazz = Student.class;
         // 2.getConstructors():获取全部的构造器,只能拿public修饰的构造器。
         Constructor[] cons1 = clazz.getConstructors();
         // 遍历操作
         for(Constructor c : cons1){
    
    
             System.out.println(c.getName() + " ==" + c.getParameterCount());
         }
         /*
         * cn.guardwhy_03.Student ==0
         * cn.guardwhy_03.Student ==2
         */
         
         // 3.getDeclaredConstructors(): :获取全部的构造器,主要你写了就可以拿到,无所谓权限。
         Constructor[] cons2 = clazz.getDeclaredConstructors();
         // 4.遍历操作
         for(Constructor c : cons2){
    
    
             System.out.println(c.getName() + " ==" + c.getParameterCount());
         }
         
         /*
         * cn.guardwhy_03.Student ==0
         * cn.guardwhy_03.Student ==2
         */
     }

     @Test
    public void getConstructor() throws Exception{
    
    
         // 1.反射的第一步先得到Class对象
         Class clazz = Student.class;
         // 2.拿到两个参数的构造器
         Constructor c1 = clazz.getDeclaredConstructor(String.class, int.class);
         System.out.println(c1.getName() + " ==" + c1.getParameterCount());  // cn.guardwhy_03.Student==2

         // 3.定位无参数构造器
         Constructor c2 = clazz.getDeclaredConstructor();   // 只要申明了就可以定位获取
         System.out.println(c2.getName() + " ==" + c1.getParameterCount()); // cn.guardwhy_03.Student ==2
     }
}

构造器创建对象

package cn.guardwhy_03;

import org.junit.Test;

import java.lang.reflect.Constructor;

/**
反射_获取Constructor构造器然后通过这个构造器创建对象。

 Constructor的API:
     1. T newInstance(Object... initargs)
        创建对象,注入构造器需要的数据。
     2. void setAccessible(true)
        修改访问权限,true暴力攻破权限,false表示保留不可访问权限(暴力反射)
总结:
    Constructor对象可以调用newInstance得到一个类的对象。反射是破坏了封装性!!!
 */
public class TestReflect02 {
    
    
    @Test
    public void createObj1() throws Exception{
    
    
        // 需求:使用反射调用无参数构造器创建一个对象。
        // 1.得到Class类对象
        Class clazz = Student.class;
        // 2.定位无参数构造器
        Constructor cons1 = clazz.getDeclaredConstructor();
        // 3.通过无参数构造器调用自己的newInstance方法创建一个对象
        cons1.setAccessible(true); // 暴力打开权限
        // 4.调用无参构造器
        Student s = (Student) cons1.newInstance();
        // 5.输出对象
        System.out.println(s);  // Student{name='null', age=0}
    }

    @Test
    public void createObj2() throws Exception{
    
    
        // 需求:使用反射调用有参数构造器创建一个对象。
        // 1.得到Class对象
        Class clazz = Student.class;
        // 2.定义无参构造器
        Constructor cons2 = clazz.getDeclaredConstructor(String.class, int.class);
        // 3.通过无参数构造器调用自己的newInstance方法创建一个对象
        Student s = (Student)cons2.newInstance("curry", 10);
        // 输出对象
        System.out.println("对象属性:" + s);    // 对象属性:Student{name='curry', age=10}
    }
}

4.2 获取Field成员变量

实体类

package cn.guardwhy_04;

public class Student {
    
    
    // 成员变量
    private String name;
    private int age ;
    private String color ;
    public static String school1;
    public static final String school2 = "中山大学医学院";

    // 无参构造
    public Student() {
    
    

    }
    // 带参构造
    public Student(String name, int age, String color) {
    
    
        this.name = name;
        this.age = age;
        this.color = color;
    }

    /**
     * set.get方法
     * @return
     */
    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 String getColor() {
    
    
        return color;
    }

    public void setColor(String color) {
    
    
        this.color = color;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}

获取Field成员变量

package cn.guardwhy_04;

import org.junit.Test;
import java.lang.reflect.Field;

/**
 反射_获取Field成员变量.反射的第一步是先得到Class类对象。
 1、Field getField(String name);
    根据成员变量名获得对应Field对象,只能获得public修饰
 2.Field getDeclaredField(String name);
    根据成员变量名获得对应Field对象,只要申明了就可以得到
 3.Field[] getFields();
    获得所有的成员变量对应的Field对象,只能获得public的
 4.Field[] getDeclaredFields();
 获得所有的成员变量对应的Field对象,只要申明了就可以得到
 */
public class FieldDemo01 {
    
    
    @Test
    public void test01(){
    
    
        // 1.先获取class类文件,class文件
        Class clazz = Student.class;
        // 2.获取全部成员变量
        Field[] fields = clazz.getDeclaredFields();
        for(Field f : fields){
    
    
            System.out.println(f.getName() + "===>" + f.getType());
        }
        /*
        * name===>class java.lang.String
        * age===>int
        * color===>class java.lang.String
          *    school1===>class java.lang.String
         *   school2===>class java.lang.String
        */
    }

    @Test
    public void test02() throws Exception{
    
    
        // 1.先获取Class类对象,class文件
        Class clazz = Student.class;
        // 2.获取某个成员变量:根据成员变量的名称定位成员变量对象
        Field name = clazz.getDeclaredField("name");
        System.out.println(name.getName() + "===" + name.getType());    // name===class java.lang.String
    }
}

成员变量赋值和取值

package cn.guardwhy_04;

import org.junit.Test;

import java.lang.reflect.Field;

/**
反射获取成员变量,取值和赋值。

 Field的方法:给成员变量赋值和取值
     void  set(Object obj, Object value):给对象注入某个成员变量数据
     Object get(Object obj):获取对象的成员变量的值。
     void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
     Class getType(); 获取属性的类型,返回Class对象。
     String getName(); 获取属性的名称。
 */
public class FieldDemo02 {
    
    
    @Test
    public void test01(){
    
    
        try {
    
    
            // 1.创建对象
            Student stu = new Student();
            // 2.得到class对象
            Class clazz = Student.class;
            // 3.获取成员变量
            Field name = clazz.getDeclaredField("name");
            name.setAccessible(true);
            Field age = clazz.getDeclaredField("age");
            age.setAccessible(true);
            Field color = clazz.getDeclaredField("color");
            color.setAccessible(true);
            // 4.赋值操作
            name.set(stu, "curry");
            age.set(stu, 10);
            color.set(stu, "yellow");

            System.out.println(stu);    // Student{name='curry', age=10, color='yellow'}

            // 5.取值操作
            System.out.println(name.get(stu));
            System.out.println(age.get(stu));
            System.out.println(color.get(stu));
            /*
            * curry
            * 10
            * yellow 
            */
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}

4.3 获取Method方法

代码示例

Dog类

package cn.guardwhy_05;
/**
 * Dog类
 */
public class Dog {
    
    
    // 成员变量
    private String name;
    // 无参构造器
    public Dog() {
    
    

    }
    // 带参构造器
    public Dog(String name) {
    
    
        this.name = name;
    }

    public String getName() {
    
    
        return name;
    }

    public void setName(String name) {
    
    
        this.name = name;
    }

    // 成员方法
    public void run(){
    
    
        System.out.println("狗刨的很快...");
    }

    private void eat(String name){
    
    
        System.out.println("狗吃" + name);
    }

    // 静态方法
    public static void inAddr(){
    
    
        System.out.println("在学校吃骨头..");
    }
}

获取Method方法并执行

package cn.guardwhy_05;

import org.junit.Test;

import java.lang.reflect.Method;

/**
 反射获取类的Method方法对象:
    1、Method getMethod(String name,Class...args);
        根据方法名和参数类型获得对应的方法对象,只能获得public的

    2、Method getDeclaredMethod(String name,Class...args);
        根据方法名和参数类型获得对应的方法对象,包括private的

    3、Method[] getMethods();
        获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

    4、Method[] getDeclaredMethods();
        获得类中的所有成员方法对象,返回数组,只获得本类申明的方法。

Method的方法
     Object invoke(Object obj, Object... args)
     * 触发的是哪个对象的方法执行。
     * args:调用方法时传递的实例参数
 */
public class MethodDemo01 {
    
    
    @Test
    public void getAllMethods() throws Exception{
    
    
        // 1.获取全部方法
        System.out.println("======获取全部方法============");
        Class clazz = Dog.class;
        Method[] ms = clazz.getDeclaredMethods();
        // 遍历操作
        for(Method m : ms){
    
    
            System.out.println(m.getName() + "==>" + m.getParameterCount());
        }
        /*
        * ======获取全部方法============
        * run==>0
        * getName==>0
        * setName==>1
        * eat==>1
        * inAddr==>0
        */

        System.out.println("======获取某个方法============");
        // 2.获取某个方法
        Method eat = clazz.getDeclaredMethod("eat", String.class);
        System.out.println(eat.getName() + "==>" + eat.getParameterCount());  // eat==>1
    }

    @Test
    public void methodInvoke() throws Exception{
    
    
        // 1.定位方法的目的是为了执行
        Class clazz = Dog.class;
        Method eat = clazz.getDeclaredMethod("eat", String.class);

        // 暴力打开
        eat.setAccessible(true);
        // 创建对象
        Dog dog = new Dog("泰迪");
        Object rs = eat.invoke(dog, "骨头");  // 狗吃骨头
        System.out.println("rs:" + rs); // rs:null

        Method getName = clazz.getDeclaredMethod("getName");
        Object rs1 = getName.invoke(dog);
        System.out.println("rs1:" + rs1);   // rs1:泰迪
        
    }
}

4.4 Class对象,有啥用?

创建类的对象:调用Class对象的newInstance()方法

  • 类必须有一个无参数的构造器。
  • 类的构造器的访问权限需要足够。

无参的构造器创建对象

  • 通过Class类的==getDeclaredConstructor(Class … parameterTypes)==取得本类的指定形参类型的构造器。
  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数,通过Constructor实例化对象。

调用指定的方法

  • 通过Class类的getMethod(String name,Class…parameterTypes)方法取得一个Method对象,并设置此方法操作时所需要的参数类型。

  • 然后使用Object invoke(Object obj, Object[] args)进行调用,并向方法中传递要设置的obj对象的参数信息。

  • Object 对应原方法的返回值,若原方法无返回值,此时返回null。若原方法若为静态方法,此时形参Object obj可为null。

  • 如果原方法形参列表为空,则Object[] args为null。若原方法声明为private,则需要在调用此invoke()方法前,显式调用方法对象的setAccessible(true)方法,将可访问private的方法。

代码示例

package cn.guardwhy.reflection_02;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/*
* 通过反射,动态创建对象
*/
public class ReflectTest04 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 1.1获得Class对象
        Class c1 = Class.forName("cn.guardwhy.reflection_02.User");
        // 1.2 构造一个对象
        User user1 = (User) c1.newInstance(); // 本质是调用了类的无参构造器
        System.out.println(user1);   // User{id=0, age=0, name='null'}
        System.out.println("===========");

        // 2.1 通过构造器创建对象
         Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
         User user2 = (User) constructor.newInstance("guardwhy", 1, 26);
         System.out.println(user2); // User{id=1, age=26, name='guardwhy'}
         System.out.println("===========");

        // 3.1 通过反射调用普通方法
        User user3 = (User) c1.newInstance();
        // 3.2 通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);
        // 3.3 invoke: 激活的意思,(对象,"方法的值")
        setName.invoke(user3, "guardwhy");
        System.out.println(user3.getName());    // guardwhy

        // 4.1 通过反射操作属性
        System.out.println("===========");
        User user4 = (User) c1.newInstance();
        Field name = c1.getDeclaredField("name");
        // 4.2 不能直接操作私有属性,暴力破解
        name.setAccessible(true);
        name.set(user4, "Curry");
        System.out.println(user4.getName());
    }
}

setAccessible

  • Method和Field、Constructor对象都有setAccessible()方法。setAccessible作用是启动和禁用访问安全检查的开关
  • 参数值为true则指示反射的对象在使用时应该取消Java语言访问检查。
  • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为true。

代码示例

package cn.guardwhy.reflection_02;

import java.lang.reflect.Method;

/*
* 性能测试
*/
public class ReflectTest05 {
    
    
    public static void main(String[] args) throws Exception {
    
    
        // 调用方法
        test01();
        test02();
        test03();
    }

    // 1.普通方法
    public static void test01(){
    
    
        User user = new User();
        long startTime = System.currentTimeMillis();
        // 遍历操作
        for (int i = 0; i < 100000000; i++) {
    
    
            user.getName();
        }

        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行10亿次数:" + (endTime - startTime) + "ms");
    }

    // 2.反射方法调用
    public static void test02() throws Exception {
    
    
        User user = new User();
        // 得到c1类对象
        Class c1 = user.getClass();
        long startTime = System.currentTimeMillis();
        // 拿到方法
        Method getName = c1.getDeclaredMethod("getName", null);
        for (int i = 0; i < 100000000; i++) {
    
    
            // 激活方法
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行10亿次数:" + (endTime - startTime) + "ms");
    }

    // 反射方式,关闭检查
    public static void test03() throws Exception {
    
    
        User user = new User();
        // 得到c1类对象
        Class c1 = user.getClass();
        long startTime = System.currentTimeMillis();
        // 拿到方法
        Method getName = c1.getDeclaredMethod("getName", null);
        // 暴力破解
        getName.setAccessible(true);
        for (int i = 0; i < 100000000; i++) {
    
    
            // 激活方法
            getName.invoke(user, null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式(关闭检查)执行10亿次数:" + (endTime - startTime) + "ms");
    }
}

执行结果

猜你喜欢

转载自blog.csdn.net/hxy1625309592/article/details/114437562