执行完execute和update后存储过程变成invalid_学会反射后,我被录取了(干货)

反射是一个非常重要的知识点,在学习Spring 框架时,Bean的初始化用到了反射,在破坏单例模式时也用到了反射,在获取标注的注解时也会用到反射······

当然了,反射在日常开发中,我们没碰到过多少,至少我没怎么用过。但面试是造火箭现场,可爱的面试官们又怎会轻易地放过我们呢?反射是开源框架中的一个重要设计理念,在源码分析中少不了它的身影,所以,今天我会尽量用浅显易懂的语言,让你去理解下面这几点:

(1)反射的思想以及它的作用: 概念篇

(2)反射的基本使用及应用场景: 应用篇

(3)使用反射能给我们编码时带来的优势以及存在的缺陷: 分析篇

我把 Java基础相关的文章整理成了 PDF,关注微信公众号 Java后端,回复 666 下载。

反射的思想及作用

有反必有正,就像世间的阴和阳,计算机的0和1一样。天道有轮回,苍天...(净会在这瞎bibi)

在学习反射之前,先来了解正射是什么。我们平常用的最多的new方式实例化对象的方式就是一种正射的体现。假如我需要实例化一个HashMap,代码就会是这样子。

Map map = new HashMap<>;
map.put(1, 1);

某一天发现,该段程序不适合用 HashMap 存储键值对,更倾向于用LinkedHashMap存储。重新编写代码后变成下面这个样子。

Map map = new LinkedHashMap<>;
map.put(1, 1);

假如又有一天,发现数据还是适合用 HashMap来存储,难道又要重新修改源码吗?

发现问题了吗?我们每次改变一种需求,都要去重新修改源码,然后对代码进行编译,打包,再到 JVM 上重启项目。这么些步骤下来,效率非常低。

4088dccd32e12c001694e2e1f45e58c1.png

对于这种需求频繁变更但变更不大的场景,频繁地更改源码肯定是一种不允许的操作,我们可以使用一个开关,判断什么时候使用哪一种数据结构。

public Map getMap(String param) {
Map map = ;
if (param.equals("HashMap")) {
map = new HashMap<>;
} else if (param.equals("LinkedHashMap")) {
map = new LinkedHashMap<>;
} else if (param.equals("WeakHashMap")) {
map = new WeakHashMap<>;
}
return map;
}

通过传入参数param决定使用哪一种数据结构,可以在项目运行时,通过动态传入参数决定使用哪一个数据结构。

如果某一天还想用TreeMap,还是避免不了修改源码,重新编译执行的弊端。这个时候,反射就派上用场了。

在代码运行之前,我们不确定将来会使用哪一种数据结构,只有在程序运行时才决定使用哪一个数据类,而反射可以在程序运行过程中动态获取类信息调用类方法。通过反射构造类实例,代码会演变成下面这样。

public Map getMap(String className) {
Class clazz = Class.forName(className);
Consructor con = clazz.getConstructor;
return (Map) con.newInstance;
}

无论使用什么 Map,只要实现了Map接口,就可以使用全类名路径传入到方法中,获得对应的 Map 实例。例如java.util.HashMap / java.util.LinkedHashMap····如果要创建其它类例如WeakHashMap,我也不需要修改上面这段源码

我们来回顾一下如何从 new一个对象引出使用反射的。

  • 在不使用反射时,构造对象使用 new 方式实现,这种方式在编译期就可以把对象的类型确定下来。

  • 如果需求发生变更,需要构造另一个对象,则需要修改源码,非常不优雅,所以我们通过使用开关,在程序运行时判断需要构造哪一个对象,在运行时可以变更开关来实例化不同的数据结构。

  • 如果还有其它扩展的类有可能被使用,就会创建出非常多的分支,且在编码时不知道有什么其他的类被使用到,假如日后Map接口下多了一个集合类是xxxHashMap,还得创建分支,此时引出了反射:可以在运行时才确定使用哪一个数据类,在切换类时,无需重新修改源码、编译程序。

第一章总结:

  • 反射的思想在程序运行过程中确定和解析数据类的类型。

  • 反射的作用:对于在编译期无法确定使用哪个数据类的场景,通过反射可以在程序运行时构造出不同的数据类实例

反射的基本使用

Java 反射的主要组成部分有4个:

  • Class:任何运行在内存中的所有类都是该 Class 类的实例对象,每个 Class 类对象内部都包含了本来的所有信息。记着一句话,通过反射干任何事,先找 Class 准没错!

  • Field:描述一个类的属性,内部包含了该属性的所有信息,例如数据类型,属性名,访问修饰符······

  • Constructor:描述一个类的构造方法,内部包含了构造方法的所有信息,例如参数类型,参数名字,访问修饰符······

  • Method:描述一个类的所有方法(包括抽象方法),内部包含了该方法的所有信息,与Constructor类似,不同之处是 Method 拥有返回值类型信息,因为构造方法是没有返回值的。

我总结了一张脑图,放在了下面,如果用到了反射,离不开这核心的4个类,只有去了解它们内部提供了哪些信息,有什么作用,运用它们的时候才能易如反掌

d726b3e5bb4bf5ca1fffe9491a909fae.png

我们在学习反射的基本使用时,我会用一个SmallPineapple类作为模板进行说明,首先我们先来熟悉这个类的基本组成:属性,构造函数和方法

public classSmallPineapple{
public String name;
public int age;
private double weight; // 体重只有自己知道

publicSmallPineapple {}

publicSmallPineapple(String name, int age) {
this.name = name;
this.age = age;
}
publicvoidgetInfo {
System.out.print("["+ name + " 的年龄是:" + age + "]");
}
}

反射中的用法有非常非常多,常见的功能有以下这几个:

  • 在运行时获取一个类的 Class 对象

  • 在运行时构造一个类的实例化对象

  • 在运行时获取一个类的所有信息:变量、方法、构造器、注解

获取类的 Class 对象

在 Java 中,每一个类都会有专属于自己的 Class 对象,当我们编写完.java文件后,使用javac编译后,就会产生一个字节码文件.class,在字节码文件中包含类的所有信息,如属性构造方法方法······当字节码文件被装载进虚拟机执行时,会在内存中生成 Class 对象,它包含了该类内部的所有信息,在程序运行时可以获取这些信息。

获取 Class 对象的方法有3种:

  • 类名.class:这种获取方式只有在编译前已经声明了该类的类型才能获取到 Class 对象

Class clazz = SmallPineapple.class;
  • 实例.getClass:通过实例化对象获取该实例的 Class 对象

SmallPineapple sp = new SmallPineapple;
Class clazz = sp.getClass;
  • Class.forName(className):通过类的全限定名获取该类的 Class 对象

Class clazz = Class.forName("com.bean.smallpineapple");

拿到 Class对象就可以对它为所欲为了:剥开它的皮(获取类信息)、指挥它做事(调用它的方法),看透它的一切(获取属性),总之它就没有隐私了。

不过在程序中,每个类的 Class 对象只有一个,也就是说你只有这一个奴隶。我们用上面三种方式测试,通过三种方式打印各个Class对象都是相同的。

Class clazz1 = Class.forName("com.bean.SmallPineapple");
Class clazz2 = SmallPineapple.class;
SmallPineapple instance = new SmallPineapple;
Class clazz3 = instance.getClass;
System.out.println("Class.forName == SmallPineapple.class:" + (clazz1 == clazz2));
System.out.println("Class.forName == instance.getClass:" + (clazz1 == clazz3));
System.out.println("instance.getClass == SmallPineapple.class:" + (clazz2 == clazz3));
76dedaf057b6fab7fb069f03270297c8.png

内存中只有一个 Class 对象的原因要牵扯到 JVM 类加载机制双亲委派模型,它保证了程序运行时,加载类时每个类在内存中仅会产生一个Class对象。在这里我不打算详细展开说明,可以简单地理解为 JVM 帮我们保证了一个类在内存中至多存在一个 Class 对象

构造类的实例化对象

通过反射构造一个类的实例方式有2种:

  • Class 对象调用newInstance方法

Class clazz = Class.forName("com.bean.SmallPineapple");
SmallPineapple smallPineapple = (SmallPineapple) clazz.newInstance;
smallPineapple.getInfo;
// [ 的年龄是:0]

即使 SmallPineapple 已经显式定义了构造方法,通过 newInstance 创建的实例中,所有属性值都是对应类型的初始值,因为 newInstance 构造实例会调用默认无参构造器

  • Constructor 构造器调用newInstance方法

Class clazz = Class.forName("com.bean.SmallPineapple");
Constructor constructor = clazz.getConstructor(String.class, int.class);
constructor.setAccessible(true);
SmallPineapple smallPineapple2 = (SmallPineapple) constructor.newInstance("小菠萝
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值