Java基础之反射

一. 问题背景

在回顾动态代理的时候,涉及了反射知识,今天再来回顾一下Java的反射。

二. 反射

2.1 Java代码经历的3个阶段

Java代码经历3个阶段:源码阶段;class类对象阶段;运行时阶段
在这里插入图片描述
解释:如上图所示,首先我们编写的Java代码(是以.java文件保存的)是不能直接被jvm识别的。Java编译器会将java代码编译成字节码(字节码技术),此时是以.class文件保存。编译完成以后,如果程序运行时需要用到Person这个类,那么jvm才会将Person加载进虚拟机的方法区(懒加载)。加载完成后,此时可以得到成员变量数组fields;构造方法数组cons;成员方法数组methods。它们分别存储Person类中所有的成员变量、构造方法、成员方法。当我们需要new Person的时候,将会使用指定的构造器对象创建对象。此时创建出来的对象才会被放入堆中。

2.2 反射到底是什么?

反射,其实是将类的各个组成部分封装为其他对象。有成员变量数组、构造器数组、成员方法数组。

2.3 反射的好处

  1. 在程序运行的过程中,操作这些对象
  2. 可以解耦,提高程序的可扩展性

2.4 获取class对象的3种方式

有3种获取class对象的方式:class.forName("全类名");类名.class对象.getClass();

  1. Class.forName("全类名");: 将字节码文件加载进内存,返回class对象。多用于配置文件、读取文件、加载。

  2. 类名.class: 通过类名的class属性获取。多用于参数传值

  3. 对象.getClass(): getClass()方法在Object类中定义着。多用于对象的获取字节码的方式

以上3中方式,其实都是各自对应着一个java代码经历的阶段,如下:
在这里插入图片描述

例子: 获取class对象的3种方式

Person.java

package com.atguigu;

public class Person {
    private String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

Main.java

   @Test
    public void test1() throws Exception{
        //1.通过Class.forName("全类名")获取class对象
        Class cls1 = Class.forName("com.atguigu.Person");
        System.out.println(cls1);
        //2.通过类名.class获取class对象
        Class cls2 = Person.class;
        System.out.println(cls2);
        //3.通过对象.getClass()获取class对象
        Person person = new Person();
        System.out.println(person.getClass());

    }

测试效果:
在这里插入图片描述
注意:同一个类只会被加载1次,通过3种方式创建出来的对象都是同一个class对象

2.5 获取成员变量

常用的4种获取成员变量的方法,它们的功能会有些许区别:

  1. Field[] getFields();获取所有public修饰的成员变量

  2. Field getField(String name);获取指定名称的public修饰的成员变量

  3. Field[] getDeclaredFields();获取所有的成员变量

  4. Field getDeclaredField(String name);获取指定的成员变量

例子:
Person.java

package com.atguigu;

public class Person {
    public String name;
    private Integer age;

    public Person() {
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

Main.java

  @Test
    public void test2() throws NoSuchFieldException, IllegalAccessException {
        Person person = new Person("zhangsan", 18);
        Class cls = person.getClass();

        Field[] fields = cls.getFields();//获取所有public修饰的成员变量
        System.out.println(Arrays.asList(fields));
        for (Field f : fields) {
            System.out.println(f.get(person));
        }

        System.out.println("-------------");

        Field name = cls.getField("name");//获取指定名称的public修饰的成员变量
        System.out.println(name);
        System.out.println(name.get(person));

        System.out.println("-------------");

        Field[] declaredFields = cls.getDeclaredFields();//获取所有的成员变量
        System.out.println(Arrays.asList(declaredFields));
        for (Field f : declaredFields) {
            if(!f.isAccessible()){
                f.setAccessible(true);
            }
            System.out.println(f.get(person));
        }

        System.out.println("-------------");

        Field age = cls.getDeclaredField("age");
        System.out.println(age);
        age.setAccessible(true);
        System.out.println(age.get(person));


    }

测试效果:
在这里插入图片描述
注意:当成员变量是private修饰时,使用class对象获取到的成员变量是不可访问的。若要访问我们需要设置field.setAccessible(true);。常常称这种方式为暴力反射。构造器与成员方法被private修饰时,也是需要设置field.setAccessible(true);才可以访问。

2.6 获取构造器

方法如下:

  1. Constructor<?>[] getConstructors();获取所有public修饰的构造器

  2. Constructor<?> getConstructor(Class<?>...parameterTypes);获取指定的public修饰的构造器

  3. Constructor<?> getDeclaredConstructors();获取所有的构造器

  4. Constructor<?> getDeclaredConstructor(Class<?>...parameterTypes);获取指定的构造器

例子:

  @Test
    public void test3() throws NoSuchMethodException {

        Class cls = Person.class;

        Constructor[] constructors = cls.getConstructors();//获取所有的public修饰的构造器
        System.out.println(Arrays.asList(constructors));

        System.out.println("-------------");

        Constructor constructor =  cls.getConstructor(null);//获取无参构造器
        System.out.println(constructor);

        System.out.println("-------------");

        Constructor[] declaredConstructors = cls.getDeclaredConstructors();//获取所有构造器
        System.out.println(Arrays.asList(declaredConstructors));

        System.out.println("-------------");

        //获取指定的构造器
        Constructor declaredConstructor = cls.getDeclaredConstructor(String.class, Integer.class);
        System.out.println(declaredConstructor);
    }

2.7 获取成员方法

  1. Method[] getMethods();获取所有public修饰的成员方法

  2. MethodgetMethod(String name);获取指定名称的public修饰的成员方法

  3. Method[] getDeclaredMethods();获取所有的成员方法

  4. MethodgetDeclaredMethod(String name);获取指定的成员方法

例子:

 @Test
    public void test4() throws NoSuchMethodException {

        Class cls = Person.class;

        Method[] methods = cls.getMethods();//获取所有的public修饰的成员方法
        System.out.println(Arrays.asList(methods));

        System.out.println("-------------");

        Method method =  cls.getMethod("getAge");//获取指定的public修饰的成员方法
        System.out.println(method);

        System.out.println("-------------");

        Method[] declaredMethods = cls.getDeclaredMethods();//获取成员方法
        System.out.println(Arrays.asList(declaredMethods));

        System.out.println("-------------");

        Method declaredMethod = cls.getDeclaredMethod("getName");//获取指定的成员方法
        System.out.println(declaredMethod);
    }

测试结果:
在这里插入图片描述

2.8 使用成员变量对象获取值

 @Test
    public void test6() throws Exception {
        Class cls = Person.class;
        Field name = cls.getDeclaredField("name");
        Person p = new Person("zhangsan", 19);
        System.out.println(name.get(p));
    }

2.9 使用构造器对象创建对象

  @Test
    public void test5() throws Exception {
        Class cls = Person.class;
        Constructor declaredConstructor =
                cls.getDeclaredConstructor(String.class, Integer.class);
        Object o = declaredConstructor.newInstance("zhangsan", 19);
        System.out.println(o);
    }

2.10 使用方法对象调用方法

 @Test
    public void test7() throws Exception {
        Class cls = Person.class;
        Method m = cls.getDeclaredMethod("getName");
        Person p = new Person("zhangsan", 10);
        System.out.println(m.invoke(p));
    }

2.11 练习:实现一个框架

需求:写一个“框架”,不改变该类的任何代码,可以帮我们创建任意类的对象,并且执行其中任意方法。

分析如何实现:

  • 使用配置文件
  • 反射

步骤:

  1. 将需要创建的对象的全类名以及需要执行的方法 定义 在配置文件中

  2. 在程序中加载读取配置文件

  3. 使用反射将类文件加载进内存

  4. 创建对象、执行方法

代码如下:
Main.java

 @Test
    public void test8() throws Exception {
        //1.加载配置文件
        Properties pro = new Properties();
        ClassLoader classLoader = Main.class.getClassLoader();//获取类加载器
        InputStream is = classLoader.getResourceAsStream("pro.properties");//获取并加载配置文件资源
        pro.load(is);

        //2.获取配置文件中定义的数据
        String className = pro.getProperty("className");
        String methodName = pro.getProperty("methodName");

        //3.加载该类进内存
        Class<?> cls = Class.forName(className);
        Object o = cls.newInstance();
        Method m = cls.getDeclaredMethod(methodName);
        m.invoke(o);


    }

A.java

package com.atguigu;

public class A {
     public void sayHello() {
         System.out.println("This is sayHello method.");
     }
}

pro.properties文件位置如下:
在这里插入图片描述
pro.properties代码:

className=com.atguigu.A
methodName=sayHello

测试效果:
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值