Java反射机制

目录

反射原理

深入理解Class类

 Class类常用的方法

 获得Class对象的四种方式

静态与动态加载

类加载的五个流程(面试题)

类加载的三个阶段(面试点)

一些常用的API

通过反射创建实例(反射爆破创建实例)

通过反射爆破操作私有属性

通过反射爆破调用私有方法

反射作业


反射原理

通过一个名叫Class的类去加载一个Cat类,返回一个Class对象。这个Class对象就是Cat类反射的对象,他会保存Cat类的完整结构。然后你可以通过Class类的对象去调用不同的方法,得到Cat类里面的各种信息。

最大的感受:当你启动一个程序的时候,我可以通过修改日志使你这个程序改变,而不需要改变源码。

 反射原理图

当我们在new一个Cat对象的时候,JVM底层会把字节码文件Cat.class类加载到堆里面,形成一个名叫“Class”的类对象,然后这个Class类对象里面包含了Cat.class的完整结构,当我们在使用Cat对象的时候他会自动匹配找到在堆里面去找到这个“Class”类对象的。(为啥会自动找到?因为他这个Class对象对应的Class类其实就是Cat对象的运行类型)

反射需要掌握的四个类:

 上述四个类中,一个Class对象实例就代表目标你那个.class字节码文件。他和.class字节码文件是映射的关系。一个Method类的对象就代表目标类中的某个具体方法,一个File类对象就代表目标类中的某个成员变量,一个Constructor类对象就代表目标类中的某个构造器。但是后面三种类都需要通过前面这个Class对象才能操作。也就是说当一个类映射成一个Class对象后,这个Class对象中包含了这个类中的所有结构(方法、属性、等等),此时我这个Class对象就相当于这个类了,然后我可以通过Method、Field、Constructor等方法去操作这个Class对象里面的映射内容。

深入理解Class类

我要对上述图片中的第二点:Class对象不是new出来的,而是系统创建的解释一下。

系统在什么样的情况下会创建Class对象?在我们传统的new Cat();这样创建一个对象的时候,他会先生成一个.class字节码文件,然后经过类加载ClassLoader类创建了一个Class对象,那这个Class对象就是系统创建的,他跟Cat类不就是反射关系,下面源码中这个String name参数就是.class字节码的路径

 同样的,在我们在直接调用Class.forName(全类名)生成Class对象的时候也是调用这个ClassLoader类去创建Class对象。

第三点:

 这个指的是系统在通过字节码文件去创建Class对象他只会创建一次。假如你前面用了传统的方法,new Cat();接着再用映射的方法Class.forName(全类名);

你在进行Class.forName(全类名);的时候他就不会在给你创建一个Class对象了,因为前面传统方法中new Cat();系统已经给你创建好了这个Class对象了。注意这个Class对象是放在堆里面的,堆是在内存里面开辟的。注意:全类名是全级 包名+类名。这个全集包名不包括src喔

最后一点:

 解析:在对Cat.class进行类加载的时候,他先把你这个Cat.class节码文件读入到方法区,然后在内存里面开辟堆空间,堆里面通过loadClass类生成一个Class类对象。此时的Class就是一个数据结构了,数据结构对我们来说是很方便操作的,这也是为什么是直接对这个Class来操作而不是对下面那个二进制文件来操作,二进制文件难操作。

 Class类常用的方法

//实体类User
public class User {
    public String name = "Tom";
    public int age = 13;
    public int id = 10086;
}
        //<?>表示不知道你这个路径下的目标类是啥类,所以可以接受任何类
        Class<?> c1 = Class.forName("com.yzh666.User");//获取目标类User的Class对象

        //会显示ci对应的目标对象User
        System.out.println(c1);//com.yzh666.User

        System.out.println(c1.getClass());//c1的运行类型就是java.lang.Class

        //得到c1对应的User类的全级包的名字
        System.out.println(c1.getPackage().getName());//com.yzh666

        //得到c1对应的User的全路径
        System.out.println(c1.getName());//com.yzh666.User

        //通过映射对象c1,获取对应的User实例对象
        Object o = c1.newInstance();//此时的o对象就是User类型
        User user = (User)o;//可以转User

        //通过反射对象获取name字段
        Field field = c1.getField("name");//他会把你这个name字段放在一个Field对象里面
        System.out.println(field.get(user));//Tom
        //field.get(user)这种方式获取字段的对应的值你这个字段在user对象中必须是public
        //私有属性怎么获取??思考,后面再说

        //通过反射对象c1设置user对象中的属性值
        field.set(user,"Jack");//因为你这个field对象前面已经对应了是"name"这个字段
        System.out.println(field.get(user));//Jacks

        //通过反射对象c1获得对应User对象中的属性名字
        Field[] fields = c1.getFields();
        for(Field f : fields){
            System.out.println(f.getName());//name,age,id
        }

 获得Class对象的四种方式

 在不同的阶段有不同的方式获取(目前来说掌握前面三种足矣)

//        方式一:Class.forName(全类名)
        Class<?> c1 = Class.forName("com.yzh666.User");
//        方式二:类名.class
        Class c2 = User.class;
//        方式三:对象名.getClass获取
        User user = new User();
        Class c3 = user.getClass();
        //看到这里,你会发现卧槽原来那个对象的运行类型就是Class
        //原来那个运行类型就是通过实体类映射而成的反射对象Class

//        方式四:通过ClassLoader对象获取(知道就行)
        ClassLoader classLoader = user.getClass().getClassLoader();
        Class c4 = classLoader.loadClass("com.yzh666.User");

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);

输出结果是:

显然都是同一个User对象

 哪些类型有Class对象

静态与动态加载

 

case “1”中的Dog是静态加载类,他在javac编译的时候就会开始加载。不管你在执行过程中有没有进入该选项case都会加载,如果Dog类出错,或者不存在则会报错,即使你没有进入。

case“2”是反射类,动态加载,只有在运行时真的执行到这个类才会加载,也就是说他不仅是运行时加载而且还要执行到该代码才会加载。在执行过程中进入到case“2”这个选项才会加载这个Person类。如果你在执行过程中没有进入到这里,即使你这个Person类不能存在他也不会报错。

类加载的五个流程(面试题)

必须记住这个点,在类加载完后,在堆空间生成一个Class对象,方法区会生成这个类对应的字节码二进制数据文件。

类加载的三个阶段(面试点)

 类加载的三个阶段中,前面两个加载和连接都是由JVM机来控制完成的,我们程序员是无法控制的,

第一个阶段没啥好说就是把你编译好的字节码文件.class(或者其他数据源比如jar包、网络)读入到内存中,那么此时内存中就有一个二进制的字节码文件了,然后在堆里面生成一个Class对象。这个Class对象是依赖于内存中的二进制文件来创建的。

第二个阶段中,

验证:主要目的是对前面内存中读入二进制文件的进行校验,比如文件描述符、字节码文件、元数据等信息符不符合当前虚拟机的要求,并且检查会不会危害虚拟机的自身安全。

如果你的这个项目比较大的话可以考虑使用-Xverify:none参数关闭掉大部分验证,达到缩短虚拟机验证的时间。

准备:准备阶段指的是对我们的静态变量进行一个默认的初始化。比如static int a = 3; a会默认赋值0。并且为其分配空间

解析:把符号引用转成直接引用。

初始化阶段:主要是加载执行我们程序员写的静态代码赋值,比如你前面不是给

static int a = 3;他这里才真正执行给这个3赋值,前提是你这个是静态的啊,因为静态成员,是类加载相关联的。

这里面,这个连接的准备阶段我要单独拉出来说一下,不同的修饰符修饰的属性他在这个阶段是怎么赋值的

一些常用的API

 

通过反射创建实例(反射爆破创建实例)

/**
 * @author 演示通过反射创建实例
 * @version 1.0
 */
public class ReflectCreateInstance {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        //1.获取Class对象
        Class<?> user1Class = Class.forName("com.yzh666.User1");
        //2.通过public的无参构造器创建实例
        Object o = user1Class.newInstance();
        //3.通过public的有参构造器创建实例
        //分两步,先得到对应的构造器,再来创建实例对象
        Constructor<?> constructor = user1Class.getConstructor(int.class);
        Object o1 = constructor.newInstance(13);

        //4.通过非public的构造器创建实例
        Constructor<?> constructor1 = user1Class.getDeclaredConstructor(int.class,String.class);
        constructor1.setAccessible(true);//爆破(设置可访问)
        Object o2 = constructor1.newInstance(18, "唐海生");
        //解读:首先获取非public的构造器得用getDeclaredConstructor
        //因为getConstructor这个只能用获取public修饰的的构造器
        //第二步,必须使用setAccessible方法爆破这个private,因为如果你不破解的话直接
        //调用newInstance他只能返回public类的啊。
    }


}

class User1{
    private int age = 10;
    private String name = "原始狗";

    public User1() {
        System.out.println("这是public无参构造器");
    }


    public User1(int age) {
        this.age = age;
        System.out.println("这是public的有参构造器"+age);
    }

    private User1(int age,String name) {
        this.age = age;
        this.name = name;
        System.out.println("这是private的有参构造器");
    }

    @Override
    public String toString() {
        return "User1{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    public void test(){
        System.out.println("测试");
    }
}

 //反射可以获取私有的方法属性,在反射面前一切都是纸老虎

通过反射爆破操作私有属性

public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InstantiationException {
        Class<?> studentClass = Class.forName("com.yzh666.Student");
        Object o = studentClass.newInstance();
        //操作public属性
        Field field = studentClass.getField("age");
        //此时这个field对象已经对应了一个名为age的属性,但是还没有指明是哪一个对象中的age属性
        field.set(o,13);//指名o对象中的age属性,通过反射操作age属性
        System.out.println(field.get(o));//get方法直接返回值,但需要传入o对象,打印:13

        //操作private属性
        Field declaredField = studentClass.getDeclaredField("name");
        //此时这个declaredField里面有一个private属性name
        //所以需要对他进行爆破
        declaredField.setAccessible(true);
//        declaredField.set(o,"温龙威"); 可以用这种
        declaredField.set(null,"唐海生");//也可以用这种
        System.out.println(declaredField.get(o));//唐海生
        //为什么可以用null,因为我的这个name属性是静态的,我不需要通过对象去操作
        //而我的这个declaredField对象在前面是已经关联到了studentClass这个Class对象了
        //所以他不通过对象,直接用null,是可以直接定位到studentClass这个类去操作静态属性的

    }
}
class Student{
    public int age;
    private static String name;
    public Student(){}

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

通过反射爆破调用私有方法

public class ReflectAccessibleMethod {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
        Class<?> bossClass = Class.forName("com.yzh666.Boss");
        Object o = bossClass.newInstance();
        //调用public的方法
        Method hi = bossClass.getMethod("hi",String.class);
        Object o1 = hi.invoke(o, "Jeremy");
        //调用非public的方法
        Method say = bossClass.getDeclaredMethod("say",int.class,String.class,char.class);
        say.setAccessible(true);
        Object tom = say.invoke(o,13, "Tom",'6');
    }
}

class Boss {
    public int age;
    private static String name;

    public Boss() {
    }
    public void hi(String name){
        System.out.println("hi " + name);
    }
    private static void say(int n, String s, char c) {
        System.out.println(n + " " + s + " " + c);
    }


}

反射作业

public class homework01 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException, NoSuchMethodException, InvocationTargetException {
        /**
         * 练习1:
         * 通过反射修改私有成员变量 com.hspedu.homework
         * Homework01.java定义PrivateTest类,
         * 有私有name属性,并且属性值为hellokitty,提供getName的公有方法
         * 创建PrivateTest的Class类,利用Class类得到私有的name属性,
         * 修改私有的name属性值并调用getName()的方法打印name属性值
         */

        //Class<?> PrivateTest_Class = Class.forName("com.yzh666.PrivateTest");//OK                   
        Class<PrivateTest> PrivateTest_Class = PrivateTest.class;
        Object o = PrivateTest_Class.newInstance();
        Field declaredField = PrivateTest_Class.getDeclaredField("name");
        declaredField.setAccessible(true);
        System.out.println(declaredField.get(o));//hello,kitty
        declaredField.set(o,"hello,Tom");//修改属性值

        Method getName = PrivateTest_Class.getMethod("getName");
        Object invoke = getName.invoke(o);//可转String
        System.out.println(invoke);//hello,Tom
    }
}

class PrivateTest{
    private String name = "hello,kitty";
    public String getName(){
        return name;
    }
}

//不管你需要对成员属性、方法做啥操作你都需要先获取到对应的属性对象和方法对象。

public class homework02 {
    public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException, IOException {
        /**
         * 利用Class类的forName方法得到File类的class 对象
         * 在控制台打印File类的所有构造器通过newInstance的方法创建File对象,
         * 并创建D: mynew.txt文件
         */
        Class<?> fileClass = Class.forName("java.io.File");
        Constructor<?> constructor = fileClass.getConstructor(String.class);
        Object o = constructor.newInstance("D:\\my.txt");
        Constructor<?>[] declaredConstructors = fileClass.getDeclaredConstructors();
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

//        File file = (File)o;
//        file.createNewFile();
        //如果你向上面这样做去创建这个文件,虽然可以但是这不是反射

        Method createNewFile = fileClass.getMethod("createNewFile");
        Object invoke = createNewFile.invoke(o);

    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

new麻油叶先生

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值