程序员内功修炼大法【反射、内省】(中)

举报
XiaoLin_Java 发表于 2021/12/07 23:15:56 2021/12/07
2.9k+ 0 0
【摘要】 四、反射 4.1、什么是反射​ 在程序的运行过程中,通过字节码文件动态的获取类中的成员信息(构造器、方法、字段),这种就叫做反射。目的是为了通过程序自动获取构造器来创建对象、获取方法并调用。 4.2、字节码对象​ Java代码会经历三个阶段:​ 我们可以通过多个实物,发现他们的共性,来抽象成一个类,类就是对象的模板,而一个个的实体就是对象​ 字节码也是真实存在的文件,每一个字节码都是一个实...

四、反射

4.1、什么是反射

​ 在程序的运行过程中,通过字节码文件动态的获取类中的成员信息(构造器、方法、字段),这种就叫做反射。目的是为了通过程序自动获取构造器来创建对象、获取方法并调用。

4.2、字节码对象

​ Java代码会经历三个阶段:

在这里插入图片描述

​ 我们可以通过多个实物,发现他们的共性,来抽象成一个类,类就是对象的模板,而一个个的实体就是对象

在这里插入图片描述

​ 字节码也是真实存在的文件,每一个字节码都是一个实例,而JVM要来存放这些字节码就需要抽象成模板,再通过模板来创建对象,存放每份字节码的信息。当要使用某份字节码时(比方说创建Person对象),就从JVM中调出存了Person.class内容的Class对象,然后再去实例化Person对象。3.

​ JDK中定义好了Class类:java.lang.Class,该类中有大量gte开头的方法,表示可以使用字节码对象来获取信息,所以我们当我们拿到了字节码对象就可以直接操作当前字节码中的构造器、方法、字段等等。

在这里插入图片描述

4.3、获取字节码对象

​ 通过API,我们可以得知Class没有公共的构造器,原因是因为Class对象在加载类时由Java虚拟机自动构建。

方式一

​ 通过Class类的forName()方法来获取字节码对象**(常用)**,多用于配置文件,将类名定义在配置文件中。读取文件,加载类以及各种流行框架中。

Class.forName(String className);  //通过类的全限定名来获取字节码对象,全限定类名是包名+类型
Class.forName("java.lang.String");  //JVM中存在则返回,不存在就抛出异常 

方拾二

​ 通过对象的getClass()方法来获取字节码对象,多用于对象的获取字节码的方式。

new User().getClass();  //通过父类Object中的getClass方法

方式三

​ 通过类型(基本类型)的class字段来获取字节码对象,多用于参数的传递。

int.class;

总结

  1. 以上的三种方式第一种是使用最多的,在各种框架中都有使用。
  2. 同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
@Test
public  void testGetClass() throws Exception {
    // 1 通过类的全限定名 Class.forName();
    Class clz1 = Class.forName("cn.linstudy.domain.Person");
    // 2 通过对象的getClass() 方法
    Person p = new Person();
    Class clz2 = p.getClass();
    // 3 通过class 字段去获取
    Class clz3 = Person.class;
    // 字节码只会加载一次,所有不管用的哪种方式去获取字节码,都是同一个
    System.out.println(clz1 == clz2);   //true
    System.out.println(clz2 == clz3);   //true
    System.out.println(clz1 == clz3);   //true
        // int 类型和int数据类型不是同一个
    System.out.println(int.class);
    System.out.println(int[].class);
}

4.4、获取构造器

​ 使用反射的目的无外乎是使用程序动态操作类的成员,比如说方法,而且操作方法首先得有对象,而对象是通过构造器来创建的,所以必须先获取构造器。

4.4.1、获取所有构造器

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

public Constructor<?>[] getDeclaredConstructors();:获取所有的构造器(包括非public)

4.4.2、获取指定的构造器

public Constructor getConstructor(Class... parameterTypes);

public Constructor getDeclaredConstructor(Class...parameterTypes)

​ parameterTypes : 参数的类型(构造方法的参数列表的类型).

结论

​ 带着 s 表示获取多个.带着 Declared 表示忽略权限,包括私有的也可以获取到。

4.4.3、获取构造器练习

@Test
public void testGetConstructor() throws NoSuchMethodException {
    // 获取字节码对象
    Class clz = Person.class;
    // 1 获取所有 public 构造器
    Constructor[] cons1 = clz.getConstructors();
    for(Constructor con : cons1){
        System.out.println(con);
    }
    System.out.println("--------");
    // 2 获取所有构造器,包括 private
    Constructor[] cons2 = clz.getDeclaredConstructors();
    for(Constructor con : cons2){
        System.out.println(con);
    }
     // 3 获取无参构造器
    Constructor con1 = clz.getConstructor();
    System.out.println(con1);
    // 4 获取带参构造器
    Constructor con2 = clz.getConstructor(Long.class, String.class);
    System.out.println(con2);
    // 5 获取指定 private 构造器
    Constructor con3 = clz.getDeclaredConstructor(String.class);
    System.out.println(con3);
}

常见错误

​ 参数不匹配,报错.找不到指定的构造器

在这里插入图片描述

4.4.4、调用构造器创建对象

public Object newInstance(Object... initargs);// initargs: 调用该构造器传递的实际参数.参数列表一定要匹配(类型,个数,顺序).

@Test
public void testCreateObject() throws Exception {
    // 获取字节码对象
    Class clz = Class.forName("cn.linstudy.domain.Person");
    // 获取带参数构造器,参数为参数类型
    Constructor con1 = clz.getConstructor(Long.class, String.class);
    //调用构造器
    Object obj = con1.newInstance(1L, "小狼");
    System.out.println(obj);
    // 获取带有参数的 private 构造器
    Constructor con2 = clz.getDeclaredConstructor(String.class);
    // 调用私有构造器,必须先设置为可访问
    con2.setAccessible(true);
    Object obj2 = con2.newInstance("小码");
    System.out.println(obj2);
}

注意: 不能直接访问没有权限(非public)的成员,如果想要使用反射去操作非public的成员.必须设置一个可以访问的标记.

​ 我们尝试私有化构造器来创建对象,结果被告知权限不够
在这里插入图片描述

​ 解决办法如下:

public void setAccessible(boolean flag): 传递一个true,表示可以访问,表示不管权限.

在这里插入图片描述

​ 从 API 中我们可以发现,Constructor,Field,MethodAccessibleObject的子类,因为这三种成员都是可以被访问private 修饰符修饰的。

package com.test.reflect;

import java.lang.reflect.Constructor;

/**
 * @author Xiao_Lin
 * @date 2020/12/28 20:17
 */
public class TestReflect {

  public static void main(String[] args) throws Exception {
    Class<?> student = Class.forName("com.test.reflect.Student");
    System.out.println(student);
    Constructor<?> constructor = student.getDeclaredConstructor(String.class);
    constructor.setAccessible(true);
    Object zs = constructor.newInstance("张三");
    System.out.println(zs);

  }
}

在这里插入图片描述

​ 只要看到传入全限定名,基本上都是要使用反射,通过全限定名来获取字节码对象. 只要看到无指定构造器但是能创建对象,基本上都是要通过字节码对象的 newInstance 去创建对象.

4.5、获取方法

4.5.1、获取所有方法

  1. public Method[] getMethods();: 可以获取到所有的公共的方法,包括继承的。
  2. public Method[] getDeclaredMethods();:获取到本类中所有的方法,包括非public的,不包括继承的。

4.5.2、获取指定的方法

  1. public Method getMethod(String name, Class<?>... parameterTypes);

  2. public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

    name: 方法名,parameterTypes: 当前方法的参数列表的类型

    注意,要找到某一个指定的方法,必须要使用方法签名才能定位到,而方法签名=方法名+参数列表,经验和获取构造器的经验一样,带着s表示获取多个,带着declared表示忽略访问权限。

4.5.3、获取方法的练习

@Test
public  void testGetMethod() throws Exception {
    /**
    	1 获取所有 public 方法,包括父类的 
    	2 获取所有方法,包括 private 不包括父类的 
    	3 获取指定参数的public 的方法,包括父类的 
    	4 获取指定参数的private 方法,不包括父类的
    **/
    // 1 获取字节码对象
    Class clz = Class.forName("cn.linstudy.domain.Person");
    // 2 获取构造器来创建对象
    // 3 获取方法
    //1 获取所有 public 方法,包括父类的
    Method[] methods = clz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println("---------");
    //2 获取所有方法,包括 private 不包括父类的
    Method[] methods2 = clz.getDeclaredMethods();
    for(Method m : methods2){
        System.out.println(m);
    }
    System.out.println("---------");
    //3 获取指定参数的 public 的方法,包括父类的
    Method sayHelloMethod = clz.getMethod("sayHello", String.class);
    System.out.println(sayHelloMethod);
    //4 获取指定参数的private 方法,不包括父类的
    Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class);
    System.out.println(doWorkMethod);
}

4.6、调用方法

public Object invoke(Object obj, Object... args);:

obj: 表示调用该方法要作用到那个对象上…

args:调用方法的实际参数方法的返回值表示,调用该方法是否有返回值,如果有就返回,如果没有返回null。

传统的调用方法

Student t = new Student(1, "张三"); 
t.sleep(5);// 张三,睡5个小时。

使用反射创建对象调用方法

Method m = clz.getMethod(“sleep”, int.class);// 找到sleep方法。
m.invoke(obj, 5);// 睡,5个小时。
public class Person {
    private Long id;
    private String name;
    public Person() {
    }
    public Person(Long id, String name) {
        this.id = id;
        this.name = name;
    }
    private Person(String name) {
        this.name = name;
    }
    public void sayHello(){
        System.out.println("hello");
    }
    public String sayHello(String name){
        System.out.println(name + ": hello");
        return "您好";
    }
    public static void sayHello(String name,Long id){
        System.out.println("调用静态方法");
    }
    private void doWork(){
        System.out.println("doWork");
    }
    private void doWork(String name){
        System.out.println(name + ": doWork");
    }
    // getter方法 setter 方法
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
@Test
public  void testGetMethod() throws Exception {
    // 1 获取字节码对象
    Class clz = Class.forName("com.reflect.Person");
    // 2 获取构造器来创建对象
    Object obj = clz.newInstance(); // 使用公共的无参数的构造器
    // 3 获取方法
    //1 获取所有 public 方法,包括父类的
    Method[] methods = clz.getMethods();
    for(Method m : methods){
        System.out.println(m);
    }
    System.out.println("---------");
    //2 获取所有方法,包括 private 不包括父类的
    Method[] methods2 = clz.getDeclaredMethods();
    for(Method m : methods2){
        System.out.println(m);
    }
    System.out.println("---------");
    //3 获取指定参数的 public 的方法,包括父类的
    Method sayHelloMethod = clz.getMethod("sayHello", String.class);
    System.out.println(sayHelloMethod);
    // 调用方法
    Object val1 = sayHelloMethod.invoke(obj, "张三");

    System.out.println("值1:" + val1);
    //4 获取指定参数的private 方法,不包括父类的
    Method doWorkMethod = clz.getDeclaredMethod("doWork", String.class);
    System.out.println(doWorkMethod);
    // 设置可访问
    doWorkMethod.setAccessible(true);
    // 调用私有的方法
    doWorkMethod.invoke(obj,"李四");
     // 调用静态方法
	Method staticSayHelloMethod = clz.getDeclaredMethod("sayHello", String.class, 
Long.class);
	// 不需要对象去调用,但是参数必须加上null,不然会把后面的参数作为调用方法的对象了.
	staticSayHelloMethod.invoke(null,"小明",1L);
}

注意:

  1. 方法也是可以被访问私有修饰符修饰的,所以,如果要访问非 public 修饰的方法,需要在访问之前设置可访问 method.setAccessible(true);
  2. 如果调用的是静态方法,是不需要对象的,所以此时在invoke方法的第一个参数,对象直接传递一个
    null 即可。

4.7、获取字段

4.7.1、获取单个字段

public Field getField(String name);:

public Field getDeclaredField(String name);

name 要获取的字段的名称

4.7.2、获取所有字段

public Field[];

getFields() ;

public Field[] getDeclaredFields();

@Test
public void testField() throws Exception {
    // 1 获取字节码对象
    Class clz = Person.class;
    Object obj = clz.newInstance();
    // 2 获取字段
    Field[] fs = clz.getFields();
    for(Field f: fs){
        System.out.println(f);
    }
    Field[] fs2 = clz.getDeclaredFields();
    for(Field f: fs2){
        System.out.println(f);
    }
    // 获取单个字段
    Field nameField = clz.getDeclaredField("name");
    System.out.println(nameField);
}

4.8、操作字段

get(Object obj);

set(Object obj,Object value);

// 设置私有字段可访问
nameField.setAccessible(true);
// 操作name字段
// 设置那么字段的数据
nameField.set(obj,"小狼");
// 获取name字段的数据
Object nameValue = nameField.get(obj);
System.out.println(nameValue);
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

作者其他文章

评论(0

抱歉,系统识别当前为高风险访问,暂不支持该操作

    全部回复

    上滑加载中

    设置昵称

    在此一键设置昵称,即可参与社区互动!

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

    *长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。