Java反射(用浅显的例子带你入坑) --- Java内功心法

目录

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类,我们要获取它的全路径->

点击这个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);
        //执行之后报错了~~
    }
}

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

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

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)
    }
}

 注意,

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

4.getDeclaredConstructor(Class... parameterTypes)

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

被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类型,下面是反射的常量字段值:

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

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

​
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);
        //执行结果:出错啦~~
    }
}

出错的原因是这个成员变量被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);
        }
        
    }
}

执行结果:

  从执行结果可以发现,除了有本身的成员方法外,还有继承自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 等等。

反射的坏处:

1. 使用反射会有效率问题,会导致程序效率降低。具体参考这里:(6条消息) 反射真的很耗时吗,反射 10 万次,耗时多久。_Q.E.D.的博客-CSDN博客

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值