18-反射

nobility 发布于 2021-04-05 247 次阅读


反射

正常情况下,需要import语句引入一个类,再通过new关键字创建实例化对象。而通过实例化对象,来寻找其类的定义这就是反射机制

反射类的体系结构

反射体系结构图

类加载器

Java程序执行流程

Java程序执行流程

类加载器所做的事情

  1. 类加载:读取class文件到内存,并创建一个Class对象
  2. 类连接
    1. 验证阶段:检查类的内部结构是否正确
    2. 准备阶段:为类变量分配内存,设置初始化参数(不同形式的0)
    3. 解析阶段:将类的二进制数据中的符号引用替换为直接引用(将变量变为地址信息)
  3. 类的初始化
    1. 该类的直接父类未被加载,先加载父类并初始化
    2. 执行类变量的显示赋值

类的加载时机

  • 创建类的实例
  • 调用类的静态方法
  • 访问类或接口的静态变量
  • 反射方式强制创建该类的Class对象
  • 使用其子类
  • 使用java命令运行该类的主方法

类加载机制

  • 全盘负责:加载某个类时,该类的依赖和引用都由该加载器负责
  • 父类委托:加载某个类时,先让父加载器加载,父加载器无法加载自己才加载
  • 缓存机制:加载过的类会缓存,只有缓存中不存在要加载的类时才会读取相对应的class文件到缓存中

类加载器查看方式

System.out.println(String.class.getClassLoader());	//查看指定类使用的类加载器,String使用根加载器,null

System.out.println(ClassLoader.getSystemClassLoader());	//返回用户加载器,AppClassLoader
System.out.println(ClassLoader.getSystemClassLoader().getParent());	//返回父加载器,PlatformClassLoader
System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());	//返回父类的父类加载器,null,根加载器返回null

动态获取文件路径

System.out.println(Test.class.getClassLoader().getResource("/"));
//null,因为是从classPath下寻找资源文件
System.out.println(Test.class.getClassLoader().getResource(""));
//返回项目路径URL
System.out.println(Test.class.getClassLoader().getResource("file"));
//文件存在返回文件路径URL,不存在返回null
System.out.println(Test.class.getResource("/"));
//返回类所在路径URL
System.out.println(Test.class.getResource(""));
//返回类所在路径URL
System.out.println(Test.class.getResource("file"));
//文件存在返回文件路径URL,不存在返回null

InputStream is = Test.class.getClassLoader().getResourceAsStream("file");
//classPath下找file,若存在返回输入流,不存在返回null
InputStream is = Test.class.getResourceAsStream("file");
//该类所在路径下找file,若存在返回输入流,不存在返回null

自定义类加载器

//该class源文件如下
public class Test{
  public static void test(){
    System.out.println("test!!!");
  }
}

继承Classloader,读取class文件,返回父类的defineClass方法即可

public class Main{
  public static void main(String[] args) throws Exception {
    TestClassLoader testClassLoader = new TestClassLoader();
    Class<?> test = testClassLoader.loader("Test");
    Method testFun = test.getMethod("test");
    testFun.invoke(test);
    //反射方式调用方法
    System.out.println(test.getClassLoader());  //返回使用自定义的加载器TestClassLoader
    System.out.println(test.getClassLoader().getParent());  //返回用户类加载器AppClassLoader
    System.out.println(test.getClassLoader().getParent().getParent());  //返回平台类加载器PlatformClassLoader
    System.out.println(test.getClassLoader().getParent().getParent().getParent());  //返回根加载器null
  }
}
class TestClassLoader extends ClassLoader {
  public Class<?> loader(String name) throws Exception {
    //传入加载的类名称
    BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(new File("Test.class")));
    byte[] bytes = bufferedInputStream.readAllBytes();
    //读取class文件
    bufferedInputStream.close();
    //始放资源
    return super.defineClass(name,bytes,0,bytes.length);
    //该方法传入类名,保证对class文件中的那个类进行加载
    //返回符类解析这个class文件二进制数据
  }
}

获取反射的三种方式

方式 方法 描述
通过对象获取 对象.getClass() 该方法是Object继承的,final修饰无法被重写
通过类似于类静态属性获取 类名.class 其实是Java原生代码支持,class本身就是关键字
通过Class类的静态方法来获取 Class<?> forName(String className) 参数是类的全类名,即包名+类名的字符串形式

Class类

获取结构信息方法

方法名 描述
String getName() 获取类的全类名
String getSimpleName() 获取类的简单类名
Package getPackage() 获取类的包对象
Class<?>[] getInterfaces() 获取类实现的全部接口的反射
Class<? super T> getSuperclass() 获取类的父类的反射
boolean isArray() 判断该反射是否是数组
包类
方法名 描述
String getName() 获取包名

获取构造方法

方法名 描述
Constructor<?>[] getConstructors() 获取所有public修饰的构造方法
Constructor<T> getConstructor(Class<?>... parameterTypes) 根据指定参数列表获取指定单个public修饰的构造方法,参数是数据类型的反射对象
Constructor<?>[] getDeclaredConstructors() 获取所有构造方法
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) 根据指定参数列表获取单个构造方法,参数是数据类型的反射对象
构造方法类
方法名 描述
T newInstance(Object ... initargs) 创建实例化对象,参数对应构造方法的参数

获取普通方法

方法名 描述
Method[] getMethods() 获取所有public修饰的方法,包括继承的
Method getMethod(String name, Class<?>... parameterTypes) 根据方法名和参数列表获取public修饰的方法,参数是数据类型的反射对象
Method[] getDeclaredMethods() 获取所有方法,不包括继承的
Method getDeclaredMethod(String name, Class<?>... parameterTypes) 根据方法名和参数列表获取方法,参数是数据类型的反射对象
方法类
方法名 描述
Object invoke(Object obj, Object... args) 调用obj的该方法,args参数列表,返回值类型是Object
int getModifiers() 获取方法修饰符,若想查看字面上的修饰符则使用Modifier.toString(int mod)转换
Class<?> getReturnType() 获取方法返回值数据类型的反射对象
String getName() 获取方法名称
Class<?>[] getParameterTypes() 获取方法的全部参数数据类型的反射对象
Class<?>[] getExceptionTypes() 获取方法的所有异常反射对象

获取属性

方法名 描述
Field[] getFields() 获取所有public修饰的属性,包括继承的
Field getField(String name) 根据name获取单个public修饰的属性
Field[] getDeclaredFields() 获取全部的属性,不包括继承的
Field getDeclaredField(String name) 根据name获取的属性
属性类
方法名 描述
Class<?> getType() 获取属性类型的反射对象
Object get(Object obj) 获取obj对象的该属性内容
void set(Object obj, Object value) 为obj对象的该属性赋值为value

获取注解

方法名 描述
Annotation[] getAnnotations() 获取全部注解
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 根据注解的反射对象来判断该注解是否存在
<T extends Annotation> T getAnnotation(Class<T> annotationClass) 根据注解的反射对象来获取指定的注解
注解类

得到具体的注解类对象,直接调用注解.属性()就可以返回注解的属性值,这样看来注解中的属性其实算是方法

Unsafe类

private Unsafe() {};
private static final Unsafe theUnsafe = new Unsafe();
//构造方法和静态获取改单例的方法都被私有化

只能通过反射机制来获取其封装的属性,该类可以实现绕过创建对象的步骤,直接调用该类的普通方法

class Test{
  private Test(){
    System.out.println("构造方法被调用");	//为被调用
  }
  public void method(){
    System.out.println("普通方法别调用");	//被调用了
  }
}
public class Main{
  public static void main(String[] args) throws Exception {
    Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); //反射获取该类中单例属性
    theUnsafe.setAccessible(true);  //忽略访问修饰符
    Unsafe unsafe = (Unsafe) theUnsafe.get(null); //获取属性内容强转为Unsafe,若没有对象传入null即可
    Test test = (Test)unsafe.allocateInstance(Test.class);  //获取Test对象
    test.method();  //执行对象中的方法
  }
}
此作者没有提供个人介绍
最后更新于 2021-04-05