一、反射机制
Java反射机制_Benett-Chen的博客-CSDN博客
前言
反射机制是学习编程过程中必须掌握的一项本领,用到反射机制最多的地方就是框架,它能够提高代码的灵活性和可扩展性,像 Spring/Spring Boot、MyBatis 等框架中大量使用了动态代理,而动态代理的实现依赖反射。比如在用eclipse编程时,当你按下快捷键时,出现的提示就是通过反射来实现的。
什么是反射呢
Java反射机制可以在运行时动态地获取类的信息并操作类的成员,实现动态加载类(编译时刻加载类是静态加载、运行时刻加载类是动态加载)、创建实例、调用方法以及访问字段、动态代理等功能。在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。
反射有哪些作用呢
反射的作用和使用场景
这么说,通过反射可以获取一个类的所有信息,比如成员变量,成员函数,构造方法以及父类的泛型,生成动态代理等。
AOP、拦截器(拦截器是AOP思想的具体实现)、IOC、注解的实现、tomcat调用具体的servlet、HandlerAdapter调用具体的处理器Handler(Handler,也叫后端控制器即Controller)、子类获取父类的泛型、等都是基于反射。
在单元测试中,我们可以使用反射技术来访问私有或受保护的类成员,使测试更加全面。
许多Java序列化和反序列化工具都是基于Java反射机制实现的,例如ObjectInputStream和ObjectOutputStream,ObjectInputStream类的readObject()方法是一个私有方法,在反序列化过程中,Java虚拟机会通过反射机制调用该方法,反序列化不会调用构造函数,反射会调用构造函数。
Spring框架中,IOC也是使用了反射机制实现的。当我们在配置文件中定义了一个Bean时,Spring就会根据该Bean的配置信息,利用反射机制创建出该Bean的实例,并将其注入到需要该Bean的地方。
通常工厂设计模式都会用到反射,工厂(
定义一个工厂类,用于根据传入的名称生成相应的 bean 实例,
在工厂类中,使用 Java 的反射机制获取指定名称的类,并创建相应实例作为 bean 返回。)
在使用反射时,通常结合配置文件会使程序更加灵活,比如通过配置文件来读取数据库连接信息,反射+泛型+配置文件,会使得我们所编写的代码更加灵活,修改起来十分容易。
动态代理(Dynamic Proxy)
是一种在运行时动态生成代理对象的技术。它是一种设计模式,用于在不修改原始对象的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。动态代理通常用于实现横切关注点(cross-cutting concerns),如日志记录、性能监控、事务管理等。通过反射,可以动态地创建代理对象,并在代理对象上调用方法,从而实现动态代理的功能。
获取父类的泛型
前言
1.通配符限定
<? extends T>
:即传入实际的类必须是T或者T的子类,
<? super T>
:即传入实际的类必须是T或者T的父类。
2.类型擦除
类型擦除指的是,你在给类型参数
<T>
赋值时,编译器会将实参类型擦除为Object(这里假设没有限定符,限定符下面会讲到)所以这里我们要明白一个东西:虚拟机中没有泛型类型对象的概念,在它眼里所有对象都是普通对象。比如下面的代码:
获取父类的泛型
获取父类的泛型 指的是 获取(子类继承父类时指定的)父类的泛型,即
class Son extends Father<String,Integer>{
,而不是泛型类的类型
class Son <T extends Father> {
package com.genertech.plm.aia.portaladmin.entity;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
public class TestGeneric {
//泛型形参:<T,U>
class Father<T,U>{
}
//子类继承父类并指定泛型实参:<String,Integer>
class Son extends Father<String,Integer>{
}
public static void main(String[] args) {
//需求:在运行时,获取Son类型的泛型父类的泛型形参<String,Integer>
//(1)还是先获取Class对象
Class clazz = Son.class;//四种形式任意一种都可以
//(2)获取泛型父类 -- getSuperclass()只能得到父类名,无法得到父类的泛型形参列表
// Class superclass = clazz.getSuperclass();
Type type = clazz.getGenericSuperclass();
// 泛型父类Father<String,Integer>属于ParameterizedType
ParameterizedType pt = (ParameterizedType) type;
//(3)获取泛型父类Father<String,Integer>的泛型形参列表
Type[] typeArray = pt.getActualTypeArguments();
for (Type type2 : typeArray) {
System.out.println(((Class)type2).getName());
//【输出(子类指定的)父类的泛型】:
//java.lang.String
//java.lang.Integer
}
}
}
1.反射机制动态加载获取类有4种方法
User user = new User();
//第一种方式:
Class c1 = User.class;
System.out.println("c1-->"+c1);
//第二种方式:
Class c2 = user.getClass();
System.out.println("c2-->"+c2);
//第三种方式:
Class c3 = Class.forName("cn.benett.reflection.User");
System.out.println("c3-->"+c3);
//可以通过类的类型创建该类的实例对象
User u = (User)c3.newInstance();
System.out.println("u-->"+u);
//第四种方式:通过通过类加载器得到,ClassLoader.getSystemClassLoader()
//用得很少,基本只有底层加载类时会用到,我们一般不用,只做了解。
Class c4 = ClassLoader.getSystemclassLoader().loadclass("java.util.ArrayList");
运行结果:
如何获取Class对象?
- 使用Class.forName("全类名")Class c3 = Class.forName("cn.benett.reflection.User");。
- 通过对象的getClass()方法Class c2 = user.getClass();。
- 调用类的class属性Class c1 = User.class;。
- 通过类加载器Class c4 = ClassLoader.getSystemclassLoader().loadclass("java.util.ArrayList");。
通过反射创建对象的方法?
- 1.通过类对象的.newInstance方法。【使用Class对象的newInstance()方法来创建类的实例,这种方式要求该Class对象的对应类有默认构造器即无参构造器,执行newInstance()实际上是利用默认构造器来创建该类的实例。】
- 2.通过类对象的构造器对象的.newInstance方法。【先使用Class对象获取到Constructor对象,再调用Constructor对象的newInstance()方法来创建对应Class对象对应类的实例。采用这种方法可以指定构造函数来创建。】
如何通过反射获取和设置对象私有字段的值?
- 先通过getDeclaredFields();方法获取属性列表。
- 再通过field.setAccessible(true);打开访问权限。
- 再通过field.set(obj, value);将值填入。
反射中,Class.forName和classLoader.loadClass的区别?
- Class.forName除了将类的.class文件加载到jvm中之外,还会执行类中的static块。Class.forName(className)方法,内部实际调用的方法是Class.forName(className,true,classloader);第2个boolean参数表示类是否需要初始化,Class.forName(className)默认是需要初始化。一旦初始化,就会触发目标对象的static块代码执行,static参数也会被赋值。
- ClassLoader.loadClass是将.class文件加载到jvm中,不会执行static块中的内容,只有在newInstance时才会去执行static块。ClassLoader.loadClass(className)方法,内部实际调用的方法是ClassLoader.loadClass(className,false);第2个 boolean参数,表示目标对象是否进行链接,false表示不进行链接。根据jvm类加载过程可知,不进行链接意味着不进行包括初始化等一些列步骤,那么静态块就不会得到执行,static参数也不会被赋值。
- ClassLoader就是遵循双亲委派模型最终调用启动类加载器的类加载器,实现的功能是“通过一个类的全限定名来获取描述此类的二进制字节流”,获取到二进制流后放到JVM中。Class.forName()方法实际上也是调用CLassLoader来实现的。
2.反射机制获取成员方法、构造方法、父类父接口信息:
Class c=object.getClass();
System.out.println("类的名称:"+c.getName());
Method[] methods=c.getMethods();//获取方法
//获取所有的public方法,包括从父类继承的public方法
for(int i=0;i<methods.length;i++){
//得到方法的返回类型
Class returnType=methods[i].getReturnType();
System.out.print(returnType.getName());
//得到方法名:
System.out.print(methods[i].getName()+"(");
//获取参数名:
Class[] parameterTypes=methods[i].getParameterTypes();
for(Class class1:parameterTypes){
System.out.print(class1.getName()+",");
}
System.out.println(")");
}
运行结果:
getMethods()获取所有的public方法,包括从父类继承的public方法,
getDeclaredMethods()获取该类所有的方法,包括private ,但不包括继承的方法。
3.反射机制获取成员变量信息:
Class c=o.getClass();
//获取成员变量
Field[] fileds=c.getDeclaredFields();
for(Field f:fileds){
//获取成员变量的类型
Class filedType=f.getType();
System.out.println(filedType.getName()+" "+f.getName());
}
getFileds()获取public,getDeclaredFields()获取所有,
运行结果:
4.反射机制调用方法的操作
public class User implements Serializable{
......
public void add(int a,int b){
System.out.print(a+b);
}
.......
}
User user = new User();
Class c = user.getClass();
Method method = c.getMethod("add", int.class,int.class);
//invoke()调用方法
method.invoke(user, 10,10);
//运行结果:20
5.缺点
性能:反射会带来一定的性能损失,因为反射需要在运行时动态地获取类的信息,并且需要进行类型转换和方法调用等操作,这些都会增加程序的运行时间和内存开销。
安全性:反射可以破坏封装性和安全性,让程序访问和修改类的私有属性和方法。
可读性:反射代码通常比普通代码更难理解和维护,增加代码的复杂性,影响程序的可读性和可维护性。
二、3次握手4次挥手
【超详细分析】关于三次握手与四次挥手面试官想考我们什么?_帅地的博客-CSDN博客