Java 反射及原理

反射,指的是对于任意一个类,都可以动态的获得它的所有属性和方法,对于任意一个对象都能调用的它的所有属性和方法,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

想要理解反射首先我们要知道JVM也就是java的虚拟机,java能够跨平台也是因为它,JVM说白了也就是一个进程,只不过是用来跑你的代码的。
Java 反射及原理_第1张图片
上图是java的内存模型,我们关注的点,一个方法区,一个栈,一个堆,初学的时候老师不深入的话只告诉你java的内存分为堆和栈,易懂点吧!

加载一个类主要有三个阶段:编译加载、连接、初始化
假如你写了一段代码 Object o=new Object()
首先你的代码会被编译成一个.class字节码文件,然后这个字节码文件会被类加载器也就是ClassLoader加载到内存中,并且创建一个class对象(每一个类都会有一个且只有一个class对象)。加载器 ClassLoader 加载class文件时,会把类里的一些数值常量、方法、类信息等加载到内存中,称之为类的元数据,最终目的是为了生成一个Class对象用来描述类,这个对象会被保存在.class文件里。

一个类的元数据会被转换成方法区中的运行时数据
成员变量---->Field类的对象,可以有多个,所以Field[]
构造器---->Constructor类的对象,可以有多个,所以Constructor[]
方法---->Method类的对象,可以有多个,所以Method[]
Java 反射及原理_第2张图片
编译完成之后会来到连接阶段,将java类的二进制代码合并到JVM的运行状态中的过程,它会做这几件事

  • 验证:验证你的代码是否有问题,是否有安全方面的问题
  • 准备:将类变量进行分配内存并初始化,这些内存是在方法区中分配的
  • 解析:将常量池中的符号引用替换成直接引用,在编译的时候每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,所以就用符号引用来代替,而在解析阶段就是为了把这个符号引用转化成真正的地址的阶段。

接下来就是初始化阶段了

  • 执行类构造器clinit,类构造器是由编译阶段自动收集所有类变量的复制动作和静态代码块中的动作语句合并产生的(类构造器是构造类信息的,不是该类的构造器)
  • 初始化一个类的时候会首先去检查父类是否初始化,如果没有会首先初始化父类
  • jvm会保证一个类的clinit方法在多线程环境下被争取的加锁和同步

那么什么时候会发生类的初始化呢?

类的主动引用(一定会发生类的初始化)

  • 当虚拟机启动,先初始化main方法所在的类
  • new一个类的对象
  • 调用类的静态成员(除了final常量),和静态方法
  • 使用java.lang.reflect包的方法对类进行反射调用
  • 当初始化一个类,如果其父类没有初始化则先初始化父类

类的被动引用(不会发生类的初始化)

  • 当访问一个静态域时,只有真正声明这个域的类才会被初始化,如:当通过子类引用父类的静态变量时,不会导致子类初始化
  • 通过数组定义类引用,不会触发此类的初始化,如 int[] arr=new int[]
  • 引用常量不会发生此类的初始化,常量在编译阶段就存入常量池了

类加载器
类加载器的作用:将class字节码文件加载到内存中,并将这些静态数据装换成方法区的运行时数据,然后再堆中生成一个代表这个类的java.lang.Class对象,作为方法区中数据的访问入口

类缓存:标准的JavaSE类加载器可以按照要求查找类,但一旦某一个类被加载到类加载器中,它将维持加载(缓存)一段时间,不过GC垃圾回收机制可以回收这些Class对象

JVM规范定义了如下类型的类加载器

  • 类加载器Bootstrap classLoader:由C++编写,是JVM自带的类加载器,负责java平台核心库,用来装载核心类库,该加载器无法直接获取
  • 扩展加载类加载器ExtClassLoader:负责jre/lib/ext目录下的jar包或-D java.ext.dirs指定目录下的jar包装入工作库
  • 系统加载器AppClassLoader:负责java-classpath或-D java.class.path所指的目录下的类于jar包装入工作,是最常用的加载器

双亲委派机制
当我们需要加载一个类的时候,首先AppClassLoader会检查自己是否加载过,如果加载过则无需加载,否则就会拿到父加载器,父加载器同样会进行检查,一直到Bootstrap classLoader,而不是先看自己是否能加载,到了Bootstrap classLoader之后,如果Bootstrap classLoader不能加载,则会下沉到子加载器,如果可以则加载,如果不可以则继续下沉,一直到AppClassLoader,如果还是无法加载,则抛出异常ClassNotFoundException

我们能够获取的运行时的class类的完整结构结构

  • 所有的属性Field
  • 所有的构造器Constructor
  • 所有的方法Method
  • 所继承的父类SuperClass
  • 实现的全部接口Interface
  • 注解Annotation
  • 名字Name

有两种类型获取,当然前提是你有这个类

  • get+你要获取的东西,例如:获取属性为getField()、获取方法为getMethod()
  • get+Declared+你要获取的东西,例如:获取属性为getDeclaredField()、获取方法为geDeclaredtMethod()

那么这两种方式有什么区别了,区别在于第一种方法只能获取公有的东西,而第二种是获取全部
需要注意的是获取方法的时候,getMethod可以获取到本类及其父类的方法,而getDeclaredMethod只能获取到本类

如何去调用方法和修改属性
方法.invoke(类对象)

Class c1 = Class.forName("experiment.B");
B b = (B) c1.getConstructor(null).newInstance();
Method method=c1.getDeclaredMethod("A",null);
method.invoke(b);

属性.set(类对象,设置成什么值)

Class c1 = Class.forName("experiment.B");
B b = (B) c1.getConstructor(null).newInstance();
Field field=c1.getDeclaredField("name");
field.set(b,"orange");
System.out.println(field.get(b));

newInstance()和new()
JAVA9之后的版本请使用构造器创建对象,class.getDeclaredConstructor().newInstance()

  • 1、两者创建对象的方式不同,前者是实用类的加载机制,后者则是直接创建一个类:
  • 2、newInstance创建类是这个类必须已经加载过且已经连接,new创建类是则不需要这个类加载过
  • 3、newInstance: 弱类型(GC是回收对象的限制条件很低,容易被回收)、低效率、只能调用无参构造,new 强类型(GC不会自动回收,只有所有的指向对象的引用被移除是才会被回收,若对象生命周期已经结束,但引用没有被移除,经常会出现内存溢出)

需要注意的是使用newInstance()必须满足两个条件:1.这个这个类已经加载了。2.这个类已经链接

下面说说如何获取泛型信息
getGenericParameterTypes 获取所有泛型参数信息
getGenericReturnType 获取泛型返回值信息(返回值只有一个)
getGenericExceptionTypes 获取所有泛型错误信息

ParameterizedType 真实参数

public class B {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class c1 = Class.forName("my.genericity");

        Method method = c1.getMethod("aaa", Map.class);
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (Type genericParameterType : genericParameterTypes) {
            System.out.println(genericParameterType);
            if(genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }
    }
}

泛型还可以越过泛型检查,请看下面

public class B {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        ArrayList<Integer> arr=new ArrayList<>();

        Class<? extends ArrayList> aClass = arr.getClass();
        Method add = aClass.getMethod("add", Object.class);
        add.invoke(arr,"hello");
        add.invoke(arr,"world");
        add.invoke(arr,"orange");

        System.out.println(arr);
    }
}

运行结果:
在这里插入图片描述
我们定义了一个Integer类型的集合,但是我们居然可以存String类型的数据,这就是反射牛逼之处的体现

通过配置文件来指定内容

public class B {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InstantiationException {
        Properties prop=new Properties();
        FileReader fr=new FileReader("data.txt");
        prop.load(fr);
        fr.close();


        String className = prop.getProperty("ClassName");
        String methodName = prop.getProperty("MethodName");

        Class<?> aClass = Class.forName(className);
        Constructor<?> con1 = aClass.getConstructor(String.class);
        Object o = con1.newInstance("libo");

        Method method = aClass.getMethod(methodName);
        System.out.println(method.invoke(o));
    }
}

如有不对欢迎联系改正!!!感谢阅读

你可能感兴趣的:(java,开发语言,后端)