引言
变量
假设写一段打印向量维度的代码,多次打印某个值
print(100);
print(100);
print(100);
这里需要修改打印值的时候,需要修改三处,为了避免这种重复工作,可以用一个变量a表示常量
a = 100
print(a);
print(a);
print(a);
这样以后想要打印不同的值,只要修改a = 100 这一处就行了,这时第一级封装
方法
考虑上面这处代码段,如果每次需要这个业务逻辑都把这个代码段复制粘贴过去,就会出现很多冗余代码,极不美观,解决办法依然是封装,把这段代码写进一个方法,哪里用到只要调用这个方法即可,这时第二级封装
public void printNum(int a){
a = 100
print(a);
print(a);
print(a);
}
如此一来,以后需要这个逻辑处理,只需简单的调用printNum(100) 即可,原始的代码段只在方法中出现了一次而不是哪里用到写哪里,提高了代码复用率。
类
有时候需要被服用的代码段不仅仅是几个变量或是某个方法,而是若干变量和方法的组合,如何将它们组织成一种方便使用的数据结构?答案是类,一种由成员变量 ,方法 和构造器 组成的数据结构。
class PrintNum{
int a;
public PrintNum(){}
public PrintNum(int a){this.a = a;}
public void printNum(int b){
b = 100
print(b);
print(b);
print(b);
}
}
通过这样的定义,我们只需要实例化一个PrintNum 类型的对象PrintNum p = new PrintNum(); 就可以同时使用p的成员变量和方法,进一步提高了封装等级和代码复用率。
反射
? 上面的封装都考虑到了一件事:某些变量的值可能需要在实际被调用的时候才知道,所以不能在业务逻辑中写死,而是留一个形参以供调用者自己定义。现在考虑这样一种情况:项目里有很多类可以使用,在程序运行之前不知道要实例化谁(也就是程序员编代码的时候并不知道用户用哪些不用哪些,那就没办法提前实例化了),或者说要在运行时根据用户的输入决定实例化哪个类。说起来有点抽象,举个吃苹果的例子,程序需要根据用户的选择来决定实例化一个苹果还是别的以供食用:
public class Eat{
public Eat(){}
public void eat(){
print("请输入您要吃的食物");
Scanner sc = new Scanner(System.in);
String food = sc.next();
if(food.equals("apple")){
Apple apple = new Apple();
apple.eat();
}
else{
SomethingElse se= new SomethingElse();
es.eat();
}
}
}
? 上面这段代码由于事先不知道要实例化谁,使用判断语句来处理这个逻辑,缺点很明显,有多少个选项就要有多少个逻辑判断,代码主键臃肿。现在思考有没有一种方法能够在运行时更加优雅的实例化一个类,就像封装方法那样留出一个形参来决定实例化哪个类。
答案是有:反射。
首先依然考虑封装,有没有一种方法将所有的类都组织起来以供使用。就像类是一个把成员变量、构造器和方法组织起来的数据结构一样,有没有一种数据结构能把所有的类组织起来?答案是Class类,具体地说,每一个Class类的实例都对应一个类。
知识点
- 项目中有多少类就有多少个对应的Class实例
- 想要实例化哪个类只需要获取对应的Class对象即可
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0wvH5snS-1665134565364)(H:\gitfile\modiman.github.io\docs_posts\imgs\image-20221007164218924.png)]
那么在运行时,只要获取到了用户的需求,就可以定制实例化对象。
Class类
获取Class实例
获取class实例常用三种方法
类名.class :这种获取方式只有在编译前已经声明了该类的类型才能获取到 Class 对象
Class clazz = Students.class;
实例.getClass() :通过实例化对象获取该实例的 Class 对象
Students sp = new Students();
Class clazz = sp.getClass();
Class.forName(className) :通过类的全限定名获取该类的 Class 对象
Class clazz = Class.forName("com.bean.Students");
构造类的实例化对象
通过反射构造一个类的实例方式有2 种:
- Class 对象调用
newInstance() 方法
Class clazz = Class.forName("com.bean.Students");
Students stu = (Students) clazz.newInstance();
stu.getInfo();
复制代码
即使 Students 已经显式定义了构造方法,通过 newInstance() 创建的实例中,所有属性值都是对应类型的初始值,因为 newInstance() 构造实例会调用默认无参构造器。
- Constructor 构造器调用
newInstance() 方法
Class clazz = Class.forName("com.bean.Students");
Constructor constructor = clazz.getConstructor(String.class, int.class);
constructor.setAccessible(true);
Students stu = (SmallPineapple) constructor.newInstance("苏世", 25);
stu.getInfo();
通过Class对象调用 newInstance() 会走默认无参构造方法,如果想通过显式构造方法构造实例,需要提前从Class中调用getConstructor()方法获取对应的构造器,通过构造器去实例化对象。
应用场景
Spring IOC
Spring IOC的一种重要实现方式-依赖注入(DI)就需要使用反射的方式
首先Spring项目由一堆Bean组成,Bean本质上就是包括Controller、Model、Service等等在内的所有类
因为由用户管理这些Bean的实例化会使项目变得臃肿,所以Spring将对Bean的控制权交给了容器,这就叫控制反转
从本质上讲,java项目的运行逻辑就是实例化要使用的类得到对象,对这些对象进行操作。
在Spring中也不例外,使用model类的Bean封装数据,使用Service型的Bean处理业务逻辑,使用Controller型的Bean控制前后端的交互
之前已经说过,Spring将Bean的控制权交给了容器,那么容器什么时候实例化Bean?
两种加载
- 立即加载:容器在项目启动时实例化所有的类并将对象存放进容器
- 懒加载:容器在第一次使用Bean的时候才加载类
IOC中的反射
Spring使用xml配置bean,容器可以根据xml中Bean的全限定名获取Class实例得到Bean实例
AOP
AOP,面向切面编程,将业务逻辑剥离分层,分成一个一个的切面,将那些可以复用的非核心逻辑如日志从显式存在于代码中改为动态代理添加的方式,既能使代码更简洁,又能解耦合
AOP基于动态代理实现,动态代理基于反射实现
动态代理,运行时增强。比如有一个实现了某个接口的类,想要给他的某个方法添加一些功能,就可以使用动态代理创建一个同样实现了这个接口的代理类,重写要增强的方法并加入要添加的功能
以日志为例说明。在一个类中,有多个方法(连接点),现在想给其中的几个方法(切点)添加日志功能,
首先要知道日志添加在方法执行前还是执行后已经具体怎么做(处理),之后根据定义一个由切点和处理组成的切面类
|