『设计模式』反射,反射程序员的快乐!为什么我老是加班?为什么我工资不如他多?原来是我不懂反射!

『设计模式』反射,反射程序员的快乐!为什么我老是加班?为什么我工资不如他多?原来是我不懂反射!

23 种设计模式+额外常用设计模式汇总 (持续更新)
Java 是一门准动态语言,是因为存在反射机制,如果你不会是不是就等于白学了?
看完不会,请评论,我亲自给你解释,嘻嘻!
在这里插入图片描述

什么是动态语言?

动态语言,是指程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化。比如 JavaScript 便是一个典型的动态语言。

除此之外如 Ruby、Python、OC 等也都属于动态语言,而 C、C++、Java 等语言则不属于动态语言。

动态类型语言,就是类型的检查是在运行时做的,是不是合法的要到运行时才判断,例如 JavaScript 就没有编译错误,只有运行错误。

静态语言
  而静态类型语言的类型判断是在运行前判断(如编译阶段),比如 java 就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口,而动态类型语言却不需要,

Java 的反射机制被视为 Java 为准动态语言的主要的一个关键性质,这个机制允许程序在运行时透过反射取得任何一个已知名称的 class 的内部信息,包括:

正在运行中的类的属性信息,正在运行中的类的方法信息,正在运行中的类的构造信息,正在运行中的类的访问修饰符,注解等等。

动态语言无时不刻在体现动态性,而静态语言也在通过其他方法来趋近于去弥补静态语言的缺陷。

为什么么要使用反射:

  1. 反射是框架设计的灵魂
    框架: 半成品软件。可以在框架的基础上进行软件开发,简化编码。学习框架并不需要了解反射,但是要是想自己写一个框架,那么就要对反射机制有很深入的了解。
  2. 解耦,提高程序的可扩展性
  3. 在运行时判断任意一个对象所属的类。
  4. 在运行时构造任意一个类的对象。
  5. 在运行时判断任意一个类所具有的成员变量和方法。
  6. 在运行时调用任意一个对象的方法。

什么是反射:

定义:

JAVA 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 java 语言的反射机制。

简单来说: 将类的各个组成部分封装成其他对象
在这里插入图片描述

反射机制的实现原理

在这里插入图片描述

Java 代码在计算机中经历的三个阶段

  1. Source 源代码阶段:_.java 被编译成_.class 字节码文件。
  2. Class 类对象阶段:*.class 字节码文件被类加载器加载进内存,并将其封装成 Class 对象(用于在内存中描述字节码文件),Class 对象将原字节码文件中的成员变量抽取出来封装成数组 Field[],将原字节码文件中的构造函数抽取出来封装成数组 Construction[],在将成员方法封装成 Method[]。当然 Class 类内不止这三个,还封装了很多,我们常用的就这三个。
  3. RunTime 运行时阶段:创建对象的过程 new。
获取 Class 对象的方式:
  1. Class.forname(“类全名”):
    将字节码加载进内存,返回 Class 对象。
    一般用于: 配置文件,将类名定义在配置文件中,读取文件,加载类。
  2. 类名.class:
    通过类名的属性 Class 获取
    一般用于: 参数传递
  3. 对象.getclass()获取:
    getclass()方法在 Object 类中定义
    一般用于: 对象获取字节码的方式

补充:
同一个字节码文件(*.class)在一次程序运行中,只会被加载一次,不论通过哪一种方式获取的 Class 对象都是同一个。

举例:
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public void Main() throws ClassNotFoundException {
//方式一:Class.forName("全类名");
Class cls1 = Class.forName("Test.Test"); //Person自定义实体类
System.out.println("cls1 = " + cls1);

//方式二:类名.class
Class cls2 = Test.class;
System.out.println("cls2 = " + cls2);

//方式三:对象.getClass();
Test tst= new Test();
Class cls3 =tst.getClass();
System.out.println("cls3 = " + cls3);

// == 比较三个对象
System.out.println("cls1 == cls2 : " + (cls1 == cls2)); //true
System.out.println("cls1 == cls3 : " + (cls1 == cls3)); //true
//结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,无论通过哪一种方式获取的Class对象都是同一个。
}

在这里插入图片描述

Class 对象功能:
获取功能:

1 获取成员变量们

1
2
3
4
5
6
Field[] getFields() :获取所有public修饰的成员变量
Field getField(String name) 获取指定名称的 public修饰的成员变量

Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
Field getDeclaredField(String name)
//需要忽略访问权限修饰符的安全检查 setAccessible(true):暴力反射,不然会报错

具体测试看下文!

2.获取构造方法们

1
2
3
4
5
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(类<?>... parameterTypes)

Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)

具体测试看下文!

3.获取成员方法们:

1
2
3
4
5
6
Method[] getMethods()
Method getMethod(String name, 类<?>... parameterTypes)

Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, 类<?>... parameterTypes)
//需要忽略访问权限修饰符的安全检查 setAccessible(true):暴力反射,不然会报错

具体测试看下文!

4.获取全类名

1
String getName()

getClass()方法是 Object 类的方法,需要注意一点获取的类名是全类名(带有路径)
举例:

1
2
3
4
5
6
7
8
9
package Test;
public class Reflect {
public static void main(String[] args) throws Exception {
Class Tst = Test.class;
String s=Tst.getName();
System.out.println(s);
}
}

运行结果:
在这里插入图片描述

![在这里插入图片描述](https://img-blog.csdnimg.cn/20200503024319516.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzYyNzExOA==,size_16,color_FFFFFF,t_70)
Field:成员变量
  1. 设置值 void set(Object obj, Object value)
  2. 获取值 get(Object obj)

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package Test;
public class Test {
public String a;
protected String b;
private String c;
String d;
}


package Test;

import java.lang.reflect.Field;

public class Reflect {
public static void main(String[] args) throws Exception {
Class Tst = Test.class;
Test tst=new Test();
System.out.println("-------------------测试getField--------------------");

Field[] fields=Tst.getFields();
for(Field f:fields)
{
System.out.println(f);
}

Field a=Tst.getField("a");
a.set(tst, "我是设置值");
System.out.println(a.get(tst));

System.out.println("\n-------------测试getDeclaredField------------------");

Field[] fields2=Tst.getDeclaredFields();
for(Field f:fields2)
{
f.setAccessible(true);//不加出不来,详情请看上文
System.out.println(f);
}

Field b=Tst.getDeclaredField("b");
b.set(tst, "我是私有的设置值");
System.out.println(b.get(tst));

}
}

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

Constructor:构造方法

创建对象:T newInstance(Object… initargs)
注意:如果使用空参数构造方法创建对象,操作可以简化:Class 对象的 newInstance 方法
作用就是用它来创建对象
举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package Test;

public class Test {
public String a;
//构造方法
public Test( ) {}
public Test(String a) {
this.a = a;
}

}

package Test;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class Reflect {
public static void main(String[] args) throws Exception {
Class Tst = Test.class;
Constructor[] constructors = Tst.getConstructors();
for (Constructor constructor : constructors) { // Constructor 对象reflect包下的 import java.lang.reflect.Constructor;
System.out.println(constructor);
}
System.out.println("------------------无参构造函数创建对象----------------------");
Constructor cst=Tst.getConstructor(); //获得无参构造函数
System.out.println(cst);
Object test=cst.newInstance(); //利用无参构造函数创建对象
System.out.println(test);

System.out.println("------------------有参构造函数创建对象----------------------");
Constructor cst2=Tst.getConstructor(String.class); //获得有参构造函数
System.out.println(cst2);
Object test2=cst2.newInstance("张3");//利用有参构造函数创建对象
System.out.println(test2);

System.out.println("------------------基于Class创建对象----------------------");

Object test3=Tst.newInstance();
//只能用于无参构函数,而且已经被弃用,不建议使用
System.out.println(test3);

}
}

在这里插入图片描述
喜欢问问题的小朋友要来了?
为什么没有 getDeclaredConstructor 方法和 getDeclaredConstructors 方法?
为什么?为什么?
有啊!!
getDeclaredConstructor 方法可以获取到任何访问权限的构造器,而 getConstructor 方法只能获取 public 修饰的构造器。具体不再测试。此外在构造器的对象内也有 setAccessible(true);方法,并设置成 true 就可以操作了。
关于为什么要使用 private 访问权限的构造器,使用这个构造器不就不能外部访问了嘛,不也就无法进行实例化对象了吗?无法在类的外部实例化对象正是私有构造器的意义所在,在单例模式下经常使用,整个项目只有一个对象,外部无法实例化对象,可以在类内的进行实例化并通过静态方法返回,由于实例化的对象是静态的,故只有一个对象,也就是单例的,这就是单例模式中的饿汉模式,不管是否调用,都创建一个对象。

Method:方法对象

执行方法:Object invoke(Object obj, Object… args)
获取方法名称:String getName();

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package Test;

public class Test {
public String a;

// 构造方法
public Test() {
}

public Test(String a) {
this.a = a;
}

public void do_Something() {
System.out.println("吃饭睡觉打豆豆");
}
public String do_Something(String s) {
System.out.println("吃饭睡觉打"+s);
return "爽";
}
}


package Test;

import java.lang.reflect.Method;

public class Reflect {
public static void main(String[] args) throws Exception {
Class Tst = Test.class;
Method[] mtd=Tst.getMethods();
for(Method m:mtd) System.out.println(m);
}
}


运行结果:
在这里插入图片描述
举例 2.0:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package Test;

public class Test {
public String a;

// 构造方法
public Test() {
}

public Test(String a) {
this.a = a;
}

public void do_Something() {
System.out.println("吃饭睡觉打豆豆");
}
public String do_Something(String s) {
System.out.println("吃饭睡觉打"+s);
return "爽";
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package Test;

import java.lang.reflect.Method;

public class Reflect {
public static void main(String[] args) throws Exception {
Class Tst = Test.class;
Test test = new Test();

System.out.println("-----------------无参测试----------------------");
Method mtd1 = Tst.getMethod("do_Something");// 获得无参的方法
Object return_Value = mtd1.invoke(test); // 调用方法
// 有返回值就得到一个值,没有就得到一个null
System.out.println(return_Value);

System.out.println("-----------------含参测试----------------------");
Method mtd2 = Tst.getMethod("do_Something", String.class);// 获得无参的方法
Object return_Value2 = mtd2.invoke(test, "张三"); // 调用方法
// 有返回值就得到一个值,没有就得到一个null
System.out.println(return_Value2);
}
}

运行结果:
在这里插入图片描述

总结

这时候又会有小朋友问:
为什么要这么麻烦,我直接调用不就好了?

不知你是否发现,从类的创建的方法的使用,所有的一切都是用的字符串,那么也就是说,我可以通过读入数据,或者配置文件的方式,创建类,调用方法。

举个简单点的例子:
就拿英雄联盟这款游戏来说,这游戏三天两头的轮换一个娱乐模式,难道每次上线都要对源代码进行修改,今天在 Client 调用“无限活力”,明天就要调用”魄罗大乱斗”,每天就对着源码改?几万行的代码就这么放心让你改?除非你老板想做空公司,故意的!必然不可能,这时候我们就算哪一个 txt 文件,就放一行字符串,用反射之后,只用改 txt 文件不就完了!不用反射,是做不到用字符串创建类,和运行方法(别抬杠,写个 if-else 或者 switch 啥的)。

举例可能不太恰当,一般不会使用 txt,一般使用 XML 或者 java 配置文件。
在这里插入图片描述

写在最后:
我叫风骨散人,名字的意思是我多想可以不低头的自由生活,可现实却不是这样。家境贫寒,总得向这个世界低头,所以我一直在奋斗,想改变我的命运给亲人好的生活,希望同样被生活绑架的你可以通过自己的努力改变现状,深知成年人的世界里没有容易二字。目前是一名在校大学生,预计考研,热爱编程,热爱技术,喜欢分享,知识无界,希望我的分享可以帮到你!
如果有什么想看的,可以私信我,如果在能力范围内,我会发布相应的博文!
感谢大家的阅读!😘 你的点赞、收藏、关注是对我最大的鼓励!


『设计模式』反射,反射程序员的快乐!为什么我老是加班?为什么我工资不如他多?原来是我不懂反射!
https://chiamzhang.github.io/2024/06/29/『设计模式』反射,反射程序员的快乐!为什么我老是加班?为什么我工资不如他多?原来是我不懂反射!/
Author
Chiam
Posted on
June 29, 2024
Licensed under