👨🎓作者简介:一位大四、研0学生,正在努力准备大四暑假的实习
🌌上期文章:JAVASE进阶:网络编程(编程实现TCP、UDP传输)
📚订阅专栏:JAVASE进阶
希望文章对你们有所帮助
前段时间都在做别人课设,现在终于有时间继续学习新技术、新知识了。
这部分内容在学习java设计模式的时候肯定都讲过,做课程作业的时候也是自己编码实现过的,属于很重要很重要的内容。
反射
概述
官方描述的反射:反射允许对封装类的字段,方法和构造函数的信息进行编程访问,实际上就是允许对成员变量、成员方法、构造方法的信息进行编程访问。
当我们想查看一个对象的成员变量或者成员方法时,利用**对象.**的方式,idea即可弹出所有的可以获取的变量和可以调用的方法;当我们使用构造函数创建对象时,可能会忘记需要的形参,这时候我们使用Ctrl+P
即可查看需要的形参。这两种都是反射的体现,说白了就是把一个类中的所有东西都取出来做操作
但为什么不使用IO流来取出所有的内容呢?其实很容易想到,java中的多态使得我们难以用IO流区分不同的函数,而如果成员变量和局部变量的类型和名称都相同,IO流显然也难以识别出来。
而反射就可以很好的获取出成员变量、构造方法以及成员方法,还很好的对其结构进行解刨:
1、字段(成员变量):获取修饰符、获取名字、获取类型、赋值/获取值
2、构造方法:获取修饰符、获取名字、获取形参、创建对象
3、成员方法:获取修饰符、获取名字、获取形参、获取返回值、抛出的异常、获取注解、运行方法
所以对于反射的学习,先要学习如何获取,再学习如何解刨,这里的获取并不是直接获取.java文件,而是获取其字节码文件.class。
获取class对象的三种方式
1、Class.forName(“全类名”);
2、类名.class
3、对象.getClass
这三种方式都可以获取,那么其什么时候使用呢?这就关乎到java文件执行的全流程了:
1、源代码阶段:
Class.forName("全类名");
,将.java文件编译成.class文件,这部分操作是在硬盘执行的,并没有加载到内存,因此.java和.class都是源代码阶段
2、加载阶段:类名.class
,加载阶段将.class字节码文件加载到内存中
3、运行阶段:对象.getClass
,运行阶段会在内存中创建对象,使用该方法即可获取class对象。
public class MyReflectDemo1 {
public static void main(String[] args) throws ClassNotFoundException {
/**
* 方式一:Class.forName(全类名)
* 全类名:报名+类名 com.wang.MyReflect.Student
* 最为常用
*/
Class clazz1 = Class.forName("com.wang.MyReflect.Student");
/**
* 方式二:类名.class
* 常用来做参数传递,例如synchronized中的参数
*/
Class clazz2 = Student.class;
/**
* 方式三:对象.getClass
* 比较少用
*/
Student student = new Student();
Class clazz3 = student.getClass();
System.out.println(clazz1 == clazz2 && clazz1 == clazz3);
}
}
反射获取构造方法
java之中,万物皆对象,既然是对象,那就有对应的类:
构造方法:Constructor
字段(成员变量):Field
成员方法:Method
Class类中用于获取构造方法的方法:
方法 | 作用 |
---|---|
Constructor[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor getConstructor(parameterTypes) | 传递参数类型,返回单个公共构造方法对象 |
Constructor getDeclaredConstructor(parameterTypes) | 传递参数类型,返回单个构造方法对象 |
Constructor类中用于创建对象的方法:
方法 | 作用 |
---|---|
newInstance(形参) | 根据指定的构造方法来创建对象 |
setAccessible(boolean flag) | 设置为true,表示取消访问检查(若构造方法是私有的) |
getModifiers() | 获取构造方法的权限,底层会使用,一般不用 |
getParameter() | 获取构造方法的形参,底层会使用,一般不用 |
所以,我们在使用new 类名() 的时候,会弹出一系列的构造方法,但是private的构造方法却不会弹出,这是因为底层用getModifiers()方法做了判定,同理,当我们Ctrl+P时,不会弹出被private修饰符修饰的构造方法里面的参数。
反射获取成员变量
Class类中用于获取成员变量的方法,获取成员变量的对象,只需要指定变量名就好,这么设计是因为变量名肯定是唯一的:
方法 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
Field类中用于创建对象的方法:
方法 | 说明 |
---|---|
void set(Object obj,Object value) | 赋值 |
Object get(Object obj) | 获取值 |
反射获取成员方法
Class类中用于获取成员方法的方法,需要注意,获取成员方法的对象,不仅要指定方法名,还需要指定参数类型(方法的重载):
方法 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组 |
Method[] getDeclaredFields() | 返回所有成员方法对象的数组 |
Method getMethod(String name, Class parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class parameterTypes) | 返回单个成员方法对象 |
Method类中用于创建对象的方法:
Object invoke(Object obj, Object… args):运行方法
参数一:用obj对象调用该方法
参数二:调用方法的传递的参数(若没有则不写)
返回值:方法的返回值(若没有则不写)
invoke配合setAccessible即可调用private修饰的方法
动态代理
动态代理属于是javase中的难点,但是是很有必要攻克的。
学习过设计模式都知道,当我们要给系统增加功能的时候,不应该直接修改源代码,即不应该侵入式修改,这就是开闭原则,动态代理就是用来解决这个问题的。
思想分析
为了不直接修改源代码,我们需要一个代理,可以理解为中介,它可以实现无侵入式的给代码增加额外的功能。
举一个例子,坤坤可以执行唱歌和跳舞两个方法。在唱歌方法中,唱歌前需要准备话筒;在跳舞方法中,跳舞前需要准备场地。事先的准备肯定不是坤坤本人去干的,而是由相关的负责人来进行的,这个负责人就称为代理。
也就是说,对象如果有很多的动作,那么就可以通过代理来转移部分的职责。
这需要负责人本身也拥有唱歌和跳舞两个方法:
唱歌(){
准备话筒
调用坤坤的唱歌函数,找坤坤唱歌
}
跳舞(){
准备场地
调用坤坤的跳舞函数,找坤坤跳舞
}
也就是说,对象有什么方法想被代理,代理就一定要有对应的方法。
但是负责人是怎么知道要调用的是坤坤的唱歌方法或者跳舞方法,而不是坤坤的打篮球方法呢?这就需要一个接口:
public interface Star{
sing方法
dance方法
}
接下来,负责人和坤坤都需要实现这个接口。
也就是说,代理和实现类对象都需要实现公共接口。
总结:
1、为什么需要代理:
代理可以无侵入式的给对象增强其他的功能
2、代理长什么样:代理里面就是对象要被代理的方法
3、java通过什么来保证代理的样子:通过接口保证,后面的对象和代理需要实现同一个接口,接口中就是被代理的所有方法
代码实现
1、定义接口:
public interface Star {
public abstract String sing(String name);
public abstract void dance();
}
2、创建实现类对象:
public class Kunkun {
private String name;
public Kunkun() {
}
public Kunkun(String name) {
this.name = name;
}
//唱歌 跳舞
public String sing(String name){
System.out.println(this.name + "正在唱" + name);
return "一个真正的man";
}
public void dance(String name){
System.out.println(this.name + "正在跳" + name);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
3、定义代理:
/**
* 创建一个代理
*/
public class ProxyUtil {
/**
* 作用:给一个对象创建一个代理
* 形参:被代理的对象
* 返回值:给被代理对象创建的代理
*/
public static Star createProxy(Kunkun kunkun) {
/**
* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法
* public static Object newProxyInstance(ClassLoader loader, Class<>[] interfaces, InvocationHandler h)
* 参数一:用于指定用哪个类加载器,去加载生成的代理类
* 参数二:指定接口,这些接口用于指定生成的代理,包括需要的方法
* 参数三:指定生成的代理对象要做什么事情
*/
Star star = (Star) Proxy.newProxyInstance(
ProxyUtil.class.getClassLoader(),//类加载器,表示"把当前对象(要做动态代理的对象)的字节码文件加载到内存"
new Class[]{Star.class},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/**
* 参数一:代理的对象
* 参数二:要运行的方法
* 参数三:调用方法时,传递的实参
*/
if("sing".equals(method.getName())){
System.out.println("准备话筒");
}else if("dance".equals(method.getName())){
System.out.println("准备场地");
}
//找被代理对象做相应的职责,使用反射
return method.invoke(kunkun, args);
}
}
);
return star;
}
}
4、测试类:
public class Test {
public static void main(String[] args) {
//获取代理对象
Kunkun kunkun = new Kunkun("坤坤");
Star proxy = ProxyUtil.createProxy(kunkun);
//调用唱歌的方法
String result = proxy.sing("鸡你太美");
System.out.println(result);
}
}