哈啰,各位小伙伴们,这里是每天进步一点点的花栗鼠小K
前些天一直在写设计模式相关的内容,面试常遇到的模式也介绍七七八八了,写的实在有点心累了。于是乎,小K打算本期换个方向,换个心情聊点别的面试题。
今天就来聊一下反射吧,它算是程序员最基础的知识啦。反射之于Java程序员,如知网之于大学生。试想,面试官问到反射时,咱总不能说:反射?反射是个什么东西?,那岂不是要撤销程序猿的身份
调侃太过就不礼貌了,话休烦絮,小K带大家走近反射
照例哈,咱们先从是什么开始
01
—
什么是反射?
反射(
Reflection
) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说『自审』,并能直接操作程序的内部属性和方法,即程序可以访问、检测和修改它本身状态或行为的一种能力
反射是所有注解实现的原理,尤其在框架设计中,有不可替代的作用,比如Bean,此处@一波栗子为——《一文搞懂bean的生命周期》
通俗点说,镜子知道吧。镜子可以清晰地照出我是谁、别人是谁。反映到程序中,反射就是用来让开发者知道这个类中有什么成员,以及别的类中有什么成员
那么围绕反射的这个特性,面试过程经常会遇到以下问题:
如何反射获取 Class 对象 如何反射获取类中的所有字段 如何反射获取类中的所有构造方法 如何反射获取类中的所有非构造方法
Oracle官方文档指出,反射的应用主要包含如下:
反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码 测试时可以利用反射 API
访问类的私有成员,以保证测试代码覆盖率
02
—
反射API
直接上图
反射 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 对象的三种方式
-
通过字符串获取Class对象,这个字符串必须带上完整路径名
Class peopleClass1 = Class.forName("reflect.People");
-
通过类的class属性
Class peopleClass2 = People.class;
-
通过对象的 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 对象,接下来就可以随心所欲啦!
获取成员变量
获取字段有两个 API
:getDeclaredFields
和 getFields
。
它们的区别是:
getDeclaredFields
用于获取所有声明的字段,包括公有字段和私有字段
getFields
仅用来获取公有字段
-
获取所有声明字段
Field[] declaredFields = peopleClass1.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("declaredField: " + declaredField);
}
-
获取所有公有字段
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
获取构造方法
获取构造方法同样包含了两个 API
:getDeclaredConstructors
和 getConstructors
它们的区别是:
getDeclaredConstructors
:用于获取所有构造方法
getConstructors
:仅用于获取公有构造方法
-
获取所有声明的构造方法
Constructor[] declaredConstructors = peopleClass1.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("declaredConstructor: " + declaredConstructor);
}
-
获取所有公有的构造方法
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
是:getDeclaredMethods
和 getMethods
它们的区别是:
getDeclaredMethods
:获取所有声明的非构造函数
getMethods
:仅获取公有非构造函数
-
获取所有声明的函数
Method[] declaredMethods = peopleClass1.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("declaredMethod: " + declaredMethod);
}
-
获取所有公有的函数
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
的方法获取到了People
的Class
对象然后反射调用它的私有构造方法
private People(String name)
,构建出newInstance
再将其公有字段age
设置为 24最后反射调用其私有方法
display
,传入参数“sixChestNuts”
,并打印出这个方法的返回值。其中,
setAccessible
函数用于动态获取访问权限,Constructor
、Field
、Method
都提供了此方法,让我们得以访问类中的私有成员。
04
—
总结
本期主要讲述了Java的反射机制,从对象、字段、构造函数和方法四个角度入手,细致介绍了面试中常遇到的这类问题。文中采用了一整段代码,将反射的 API
灵活地结合起来,配以细致的注解,保证感兴趣的小伙伴一遍通透。
本期就到这了,这里是花栗鼠小K,下次有🌰,我再来,拜拜~~~
关注六只栗子,面试不迷路!
作者 花栗鼠小K
编辑 一口栗子
原文始发于微信公众号(六只栗子):K博士:反射?反射是个什么东西?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/88561.html