K博士:反射?反射是个什么东西?

K博士:反射?反射是个什么东西?

哈啰,各位小伙伴们,这里是每天进步一点点的花栗鼠小K

前些天一直在写设计模式相关的内容,面试常遇到的模式也介绍七七八八了,写的实在有点心累了。于是乎,小K打算本期换个方向,换个心情聊点别的面试题

今天就来聊一下反射吧,它算是程序员最基础的知识啦。反射之于Java程序员,如知网之于大学生。试想,面试官问到反射时,咱总不能说:反射?反射是个什么东西?,那岂不是要撤销程序猿的身份

调侃太过就不礼貌了,话休烦絮,小K带大家走近反射

照例哈,咱们先从是什么开始



01


什么是反射?



反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说『自审』,并能直接操作程序的内部属性和方法,即程序可以访问、检测和修改它本身状态或行为的一种能力

反射是所有注解实现的原理,尤其在框架设计中,有不可替代的作用,比如Bean,此处@一波栗子为——《一文搞懂bean的生命周期

通俗点说,镜子知道吧。镜子可以清晰地照出我是谁、别人是谁。反映到程序中,反射就是用来让开发者知道这个类中有什么成员,以及别的类中有什么成员

那么围绕反射的这个特性,面试过程经常会遇到以下问题:

  • 如何反射获取 Class 对象
  • 如何反射获取类中的所有字段
  • 如何反射获取类中的所有构造方法
  • 如何反射获取类中的所有非构造方法

Oracle官方文档指出,反射的应用主要包含如下:

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率



02


反射API



直接上图

K博士:反射?反射是个什么东西?
反射API脑图

Java 类的成员包括以下三类:属性字段、构造函数、方法。反射的 API 恰恰也是与这几个成员相关

小K在这里先创建一个People

public class People {
    private String name;
    public int age;

    public People() {
    }

    private People(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }
    
    private String display(String input) {
        System.out.println("display: "+name+", "+age+", "+input);
        return "I love you";
    }
}

可以看到,Student 类中有两个字段、两个构造方法、两个函数,且都是一个私有,一个公有。由此可知,People类基本涵盖了平时常用的所有类成员。

PS:这里对构造函数和 display()设置为私有,是为了后续介绍『通过反射获取私有成员后应该如何使用』,做一个铺垫。



03


API的调用



获取 Class 对象的三种方式

  1. 通过字符串获取Class对象,这个字符串必须带上完整路径名
Class peopleClass1 = Class.forName("reflect.People");
  1. 通过类的class属性
Class peopleClass2 = People.class;
  1. 通过对象的 getClass() 函数
People people = new People();
Class peopleClass3 = people.getClass();
  • 第一种方法是通过类的全路径字符串获取 Class 对象,这也是我们平时最常用的反射获取 Class 对象的方法
  • 第二种方法有限制条件:需要导入类的包
  • 第三种方法已经有了 People 对象,不再需要反射

运行代码

System.out.println("class1 = " + peopleClass1 + "n" +
        "class2 = " + peopleClass2 + "n" +
        "class3 = " + peopleClass3 + "n" +
        "class1 == class2 ? " + (peopleClass1 == peopleClass2) + "n" +
        "class2 == class3 ? " + (peopleClass2 == peopleClass3));

运行结果如下:

class1 = class reflect.People
class2 = class reflect.People
class3 = class reflect.People
class1 == class2 ? true
class2 == class3 ? true

通过这三种方式获取到的 Class 对象是同一个,也就是说 Java 运行时,每一个类只会生成一个 Class 对象

既然已经获取了Class 对象,接下来就可以随心所欲啦!

获取成员变量

获取字段有两个 APIgetDeclaredFieldsgetFields

它们的区别是:

getDeclaredFields 用于获取所有声明的字段,包括公有字段和私有字段

getFields 仅用来获取公有字段

  1. 获取所有声明字段
Field[] declaredFields = peopleClass1.getDeclaredFields();
for (Field declaredField : declaredFields) {
    System.out.println("declaredField: " + declaredField);
}
  1. 获取所有公有字段
Field[] fields = peopleClass1.getFields();
for (Field field : fields) {
    System.out.println("field: " + field);
}

运行结果如下:

declaredField: private java.lang.String reflect.People.name
declaredField: public int reflect.People.age
field: public int reflect.People.age

获取构造方法

获取构造方法同样包含了两个 APIgetDeclaredConstructorsgetConstructors

它们的区别是:

getDeclaredConstructors :用于获取所有构造方法

getConstructors:仅用于获取公有构造方法

  1. 获取所有声明的构造方法
Constructor[] declaredConstructors = peopleClass1.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
    System.out.println("declaredConstructor: " + declaredConstructor);
}
  1. 获取所有公有的构造方法
Constructor[] constructors = peopleClass1.getConstructors();
for (Constructor constructor : constructors) {
    System.out.println("constructor: " + constructor);
}

运行结果如下:

declaredConstructor: public reflect.People()
declaredConstructor: private reflect.People(java.lang.String)
constructor: public reflect.People()

获取非构造方法

同样地,获取非构造方法的两个 API是:getDeclaredMethodsgetMethods

它们的区别是:

getDeclaredMethods:获取所有声明的非构造函数

getMethods:仅获取公有非构造函数

  1. 获取所有声明的函数
Method[] declaredMethods = peopleClass1.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
    System.out.println("declaredMethod: " + declaredMethod);
}
  1. 获取所有公有的函数
Method[] methods = peopleClass1.getMethods();
for (Method method : methods) {
    System.out.println("method: " + method);
}

运行结果如下:

declaredMethod: private java.lang.String reflect.People.display(java.lang.String)
declaredMethod: public void reflect.People.setAge(int)
method: public void reflect.People.setAge(int)
method: public final void java.lang.Object.wait() throws java.lang.InterruptedException
method: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method: public boolean java.lang.Object.equals(java.lang.Object)
method: public java.lang.String java.lang.Object.toString()
method: public native int java.lang.Object.hashCode()
method: public final native java.lang.Class java.lang.Object.getClass()
method: public final native void java.lang.Object.notify()
method: public final native void java.lang.Object.notifyAll()

从输出中我们看到,getMethods 方法不仅获取到了我们声明的公有方法 setStudentAge ,还获取到了很多 Object 类中的公有方法。Object 是所有 Java 类的父类,所有对象都默认实现了 Object 类的方法。而getDeclaredMethods 是无法获取到父类中的方法的。

API 综合实践

接下来,小K将综合上面提及的反射 API ,带大家理一下通常实际应用中,反射如何处理和调用

// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class peopleClass = Class.forName("reflect.People");
// 2.获取声明的构造方法,传入所需参数的类名,如果有多个参数,用','连接即可
Constructor peopleConstructor = peopleClass.getDeclaredConstructor(String.class);
// 如果是私有的构造方法,需要调用下面这一行代码使其可使用,公有的构造方法则不需要下面这一行代码
peopleConstructor.setAccessible(true);
// 使用构造方法的newInstance方法创建对象,传入构造方法所需参数,如果有多个参数,用','连接即可
Object people = peopleConstructor.newInstance("小K");
// 3.获取声明的字段,传入字段名
Field peopleAgeField = peopleClass.getDeclaredField("age");
// 如果是私有的字段,需要调用下面这一行代码使其可使用,公有的字段则不需要下面这一行代码
// studentAgeField.setAccessible(true);
// 使用字段的set方法设置字段值,传入此对象以及参数值
peopleAgeField.set(people,24);
// 4.获取声明的函数,传入所需参数的类名,如果有多个参数,用','连接即可
Method peopleShowMethod = peopleClass.getDeclaredMethod("display", String.class);
// 如果是私有的函数,需要调用下面这一行代码使其可使用,公有的函数则不需要下面这一行代码
peopleShowMethod.setAccessible(true);
// 使用函数的invoke方法调用此函数,传入此对象以及函数所需参数,如果有多个参数,用','连接即可。函数会返回一个Object对象,使用强制类型转换转成实际类型即可
Object result = peopleShowMethod.invoke(people,"sixChestNuts");
System.out.println("result: " + result);

运行结果如下:

display: 小K, 24, sixChestNuts
result: I love you

小K带大家梳理一下逻辑:

  • 先用第一种全路径获取 Class 的方法获取到了 PeopleClass 对象

  • 然后反射调用它的私有构造方法 private People(String name),构建出 newInstance再将其公有字段 age 设置为 24

  • 最后反射调用其私有方法 display,传入参数 “sixChestNuts”,并打印出这个方法的返回值。

    其中,setAccessible 函数用于动态获取访问权限,ConstructorFieldMethod 都提供了此方法,让我们得以访问类中的私有成员。



04


总结



本期主要讲述了Java的反射机制,从对象、字段、构造函数和方法四个角度入手,细致介绍了面试中常遇到的这类问题。文中采用了一整段代码,将反射的 API 灵活地结合起来,配以细致的注解,保证感兴趣的小伙伴一遍通透。

本期就到这了,这里是花栗鼠小K,下次有🌰,我再来,拜拜~~~

关注六只栗子,面试不迷路!


K博士:反射?反射是个什么东西?

K博士:反射?反射是个什么东西?

K博士:反射?反射是个什么东西?

K博士:反射?反射是个什么东西?



作者    花栗鼠小K

编辑   一口栗子  


原文始发于微信公众号(六只栗子):K博士:反射?反射是个什么东西?

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88561.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!