Java反射机制

反射

最近通过视频以及一些博客学习了Java反射机制,借此记录下

首先先谈到语言,语言可以分为两类,分别是动态语言和静态语言

动态语言: 是一类在运行时可以改变其结构的语言,例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构

静态语言: 与动态语言相对,运行时不可变的语言就是静态语言。如Java、C、C++。

虽然Java不是动态语言,但是它具有一定的“准动态性”,可以利用反射机制、字节码操作获得类似动态语言的特性。

动态性: 当程序运行时,这时候想调用某个业务,则需要创建某个业务的对象来调用相关方法,但是这时候java程序已经运行了,静态语言无法使用,因此需要反射,动态获得相关类的对象。

什么是反射

首先反射是一种机制,它允许程序在执行的时候借助Reflection(反射)API取得任何类的内部信息,并能操作任意对象的内部属性和方法。

原理: 在程序加载完一个类之后,Java虚拟机会在堆内存的方法区中创建一个Class类型的对象,注意这个Class是大写C开头,并不是关键字,而这个对象包含了整个类的结构信息。就如同一面镜子,可以将一个物品通过反射后,映射到我们眼中,也就是说我们在操作时,实际操作的是这面镜子,通过这面镜子,我们就可以间接的看到相关类的结构。

20210629180617

我们知道,在一个类的外部,我们不能调用这个类的内部私有结构,比如说

public class person(){
    private String name;
    private void show(){
        System.out.println("我在学习反射");
    }
}

在person类的外部就无法调用name, show()方法。
但是通过反射,就可以调用person类的私有结构,比如说私有的构造器、方法、属性。

public void test(){
    Class clazz = person.class;
    //调用私有构造器
    Constructor cons1 = clazz.getDeclaredConstructor(String.class);
    cons1.setAccessible(true);
    person p1 = (person)cons1.newInstance("Jerry");
    System.out.println(p1);

    Field name = clazz.getDeclaredField("name");
    name.setAccessible(true);
    name.set(p1,"hanmeimei");

    Method show = clazz.getDeclaredMethod("show")
    show.setAccessible(true);
    show.invoke(p1);
}

反射的相关API:

  1. java.lang.Class 代表一个类
  2. java.lang.reflect Method 代表类的方法
  3. java.lang.reflect Field 代表类的成员变量
  4. java.lang.reflect Constructor 代表类的构造器

类的加载过程:

20210629193109

  1. 加载: 程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。接着使用java.exe命令对某个字节码文件进行解释运行,就会将该字节码文件加载到内存中。加载到内存中,就是类的加载,加载到内存中的类,就是运行时类,是java.lang.Class对象,也就是Class的一个实例(万物皆对象)。
  2. 链接: 将Java类的二进制代码合并到JVM的运行状态之中的过程。
    • 验证: 确保加载的类信息符合JVM规范,没有安全方面的问题
    • 准备: 正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。不同类型的变量初始值不同。
    • 解析: 虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
  3. 初始化:
    • 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
    • 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
    • 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。
public class ClassLoadingTest {
    public static void main(String[] args )
        System.out.println(m);
    }
}

class A {
    static  {
        m = 300; 
    }
    static int m = 100;
}

第二步:链接结束后 m=0
第三步:初始化后,m的值由<clinit>()方法执行决定
这个 A 的类构造器<clinit>()方法由类变量的赋值和静态代码块中的语句按照顺序合并产生,类似于

<clinit>(){  
    m = 300;  
    m = 100;  
}

反射有什么用

反射可以提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法

我们要注意,反射的调用是在程序运行时发生的,而在程序运行前,我们并不知道究竟要创建什么对象,比如说一个管理页面,首先有的一个功能是管理员的登录功能,我们在运行程序时,并不知道使用者是否要登陆。而如果通过反射,那么可以在用户发送登录login请求时,我们接收到这个请求,利用反射去创建实现该请求的方法对象实例,从而实现该功能。

因此反射对于框架开发者的作用非常大,因为许多底层的方法都是通过反射来调用,比如经常能看见的invoke()。

反射怎么用

最主要的是获取Class实例的方法
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方法获得该类

//获得class办法一:通过对象获得
Class clazz1 = person.getClass();
//获得class办法二:通过字符串获得(包名+类名)
Class clazz2 = Class.forName("com.reflection.person");
//获得class办法三:通过类的静态成员class获得
Class clazz3 = Person.class;
//方法四:使用类的加载器:ClassLoader
ClassLoader classLoader = ReflectionTest.class.getClassLoader();
classLoader.loadClass("com.reflection.person")

无论通过哪个方法,获取的为同一个运行时类。 在实际开发中,我们经常能看见通过类加载器来获得相应的类,比如在使用JDBC时,我们就是利用类加载器来获得相应的配置文件的内容。

InputStream in = this.getClass getClassLoader getResourceAsStream ("jdbc.properties)

类加载器的作用:

  1. 类加载的作用:将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
  2. 类缓存: 标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过JVM垃圾回收机制可以回收这些Class对象

20210629201206

也就是说只有通过类加载器,才能进行之后的相关操作。

20210629202227

// 获取一个系统类加载器
ClassLoader classloader = ClassLoader.getSystemClassLoader
System.out.println classloader
// 获取系统类加载器的父类加载器,即扩展类加载器
classloader = classloader.getParent
System.out.println classloader
// 获取扩展类加载器的父类加载器,即引导类加载器
classloader = classloader.getParent
System.out.println classloader//为null,无法获取。

//读取配置文件(一般在jdbc中常见)
InputStream in = null;
in = this.getClass getClassLoader getResourceAsStream ("exer2 test.properties);
//一般来说getResourceAsStream("")路径默认是src下

另外还有许多其它用处

  1. 利用newInstance()来创建类实例。
  2. 利用getMethod()等类似方法来获得类中的方法
  3. 利用getFiled()等来获得成员变量
  4. 通过invoke()来调用方法
Object invoke(Object obj , Object … args)
  • Object对应原方法的返回值,若原方法无返回值,此时返回 null
  • 若原方法若为静态方法,此时形参 Object obj 可为 null
  • 若原方法形参列表为空,则 Object[] args 为 null
  • 若原方法声明为 private, 则需要在调用此 invoke()方法前,显式调用方法对象的setAccessible()方法(设为true),将可访问private的方法。

关于setAccessible()方法,该方法是用来启动和禁用访问安全检查的开关,要注意的事,设为true表示放弃java语言访问的检查,反之则开启。

其他许多用法参考尚硅谷视频以及末尾的参考文章

使用反射注意事项

  1. 由于反射的运用一般是在程序运行时,JVM无法对其进行优化,因此操作效率会比非反射的操作效率低,因此在使用反射时应该考虑效率问题。
  2. 上面我们提到,使用反射并且通过setAccessible()方法,可以获得内部封装的属性或方法,因此过多的运用放射会导致安全问题。

参考文献:

  1. 尚硅谷反射基础
  2. 深入解析Java反射(1) - 基础
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值