这里我单独拎出来总结反射,反射很重要,后面的底层框架源码中非常多使用反射,这里是鄙人浅薄之见,有错误希望大家指出!!!
十、反射
1、何谓反射?
反射就是通过Class类对象获取class的信息的方法
反射赋予了我们在运行是分析类以及执行类中方法成员的能力
通过反射我们可以获取任意一个类的属性和方法,也可以使用这个类的方法和属性。
2、#反射的优缺点?
优点:提高了灵活性和,为各种框架提供开箱即用的方法提供了便利(动态的创建和使用对象)
缺点:降低了安全性,因为它可以避过泛型的类型安全检查(泛型类型安全检查是在编译的阶段,而反射可以在运行阶段,因此可以避过),降低安全性,同时反射的性能相对差一点(解释型语言)。
3、反射的应用场景
反射在各类框架和底层原理中使用的非常多,框架中有需要使用了动态代理的功能,而动态代理机制的实现还需要以来反射,
同时注解也使用了反射。
这是因为可以基于反射去分析类,我们可以获取类上,方法上,属性上的注解。
4、反射详解(Class类和class解析,获取Class的四种方式)
1)Class类和class解析
首先说一下这个Class是什么,Class是类,当jvm加载一个类时,会在内存中创建这个类的Class对象,一个类在堆内存中只有一个对应的Class类对象,而这个类对象包含了这个类的所有信息,我们可以通过这个Class获取这个类的所有信息,因此在操作反射的时候都是先获取类,然后再进行相应的操作。
类加载和反射机制
反射机制是java实现动态语言的关键,也就是使用反射实现类的动态加载
静态加载:指的是在编译期间就加载相关的类,如果不存在相关的类就报错
动态加载:指的是在运行期间加载相关的类,如果运行时未使用到该类也不会报错
类加载的时机:
静态加载时机:
1、使用new新创建一个对象时,该类会被加载
2、当调用类中的静态成员时,会加载该类
3、当子类被加载时,超类也会被加载
动态加载的时机:
1、使用反射时,在程序运行中使用到哪个类,哪个类才会被加载
类加载过程
准备阶段会给静态成员赋默认值,到了初始化阶段会赋初值。
Class类的一些注意点:
1、Class类也是一个类(class),他的名字就叫Class
2、Class类对象时jvm在加载类的时候自动创建的
3、在堆内存中,一个类的Class类对象最多只会有一个,因为类只加载一次
4、每个类实例对象都会知道自己的Class类对象
5、每个Class类对象都知道对应类的所有的信息
6、存放在方法区域中
这里需要注意除了int等基本的数据类型,其他都属于类(包括接口interface)
因此类和接口本质上都是数据类型
而每个class都是动态加载的,当jvm第一次读取到class的时候,才会加载到内存
每加载一种class,此时jvm都会为其创建一个Class类对象,将两者关联起来,注意这里的Class其实是一个名字为Class的类(class)。
public final class Class {
private Class() {}
}
每个Class都指向一个数据类型(class或者接口)
┌───────────────────────────┐ │ Class Instance │──────> String ├───────────────────────────┤ │name = "java.lang.String" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Random ├───────────────────────────┤ │name = "java.util.Random" │ └───────────────────────────┘ ┌───────────────────────────┐ │ Class Instance │──────> Runnable ├───────────────────────────┤ │name = "java.lang.Runnable"│ └───────────────────────────┘
并且每一个Class包含了对应的class的全部信息
因此要使用反射,获取class的全部信息,首先需要获取class对应的Class类
2)获取Class的四种方式
1、对应已知类名的类或者基本数据类型的类,可以直接使用静态成员变量class来获取
Class cls = String.class;// class 是 String 类中的一个静态变量
2、对于类的实例对象,可以使用getClass()来获取Class类对象
String s = "Hello"; Class cls = s.getClass();// 调用 String类对象 s的 getClass() 方法获取
3、如果知道类的路径名,可以使用Class.forName(路径名)来获取Class类
Class cls = Class.forName("java.lang.String");// java.lang.String 是 String 类的完整类名
4、可以通过类的加载器来获取Class类,传入类路径
ClassLoader.getSystemClassLoader().loadClass("cn.javaguide.TargetObject");
5、如何操作类
1)字段:获取字段值、设置字段值
如果需要访问字段有以下四种方法:
Field getField(name):根据字段名获取某个 public 的 field(包括父类)
Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类)
Field[] getFields():获取所有 public 的 field(包括父类)
Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)
一个Field对象包含了以下信息:
1、字段的名称,通过getName()来获取
2、字段的类型,通过getType()来获取
3、字段的修饰符,通过getModifers()来获取
如果要获取字段值,首先需要获取Class类对象,然后通过以上四种之一的方法获取字段Field,然后在通过get(Object object)来设置字段值(object指的是对象,即获取指定对象的字段值),通过set(Object object,Object value)(第一个object是指定对象,第二个object是值,即设置指定对象的指定字段值)来设置值。
注意如果是设置和获取私有字段的值,此时需要先使用Field.setAccessible(True),来暴力反射,获取权限。
2)成员方法
同上,访问方法有以下四种方法:
Method getMethod(name, Class...):获取某个public的Method(包括父类) Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类) Method[] getMethods():获取所有public的Method(包括父类) Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类) 一个Method对象中包含了以下信息:
1、方法的名字,getName()
2、方法的返回值类型,getReturnType()
3、方法的参数类型,getParameterType()
4、方法的修饰符,getmodifiers()
调用方法的话则是使用Method.invoke(Object object,xxx.class),第一个object指的是要在哪个实例对象是调用方法,后面是方法参数的类型,如果是调用静态方法,则object为null,即invoke(null,xxx.class),如果调用的是私有的方法,则需要先使用setAcccessible(True)。
如果在多态中,此时调用的方法,应该是子类重写后的方法,这里其实与多态规则相同。
3)构造器
如果单纯使用newInstance()创建实例对象,调用的是public的无参构造方法,因此有极大的限制
因此可以先获得类的构造器,然后再创建对象。
获取构造器的方法同上:
getConstructor(Class...):获取某个public的Constructor; getDeclaredConstructor(Class...):获取某个Constructor; getConstructors():获取所有public的Constructor; getDeclaredConstructors():获取所有Constructor。 因为构造器不能被重写,因此不存在多态的问题。
此时再使用newInstance(Object... parameters)来创建对象就可以使用私有的无参/有参构造器了。
但是如果使用私有的构造器,同样需要先setAccessible(True)。
4)继承方法
通过Class对象可以获取继承关系:
Class getSuperclass():获取父类类型;
Class[] getInterfaces():获取当前类实现的所有接口。
通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。