回顾总结18

反射

如何根据配置文件re.properties上的信息,创建Cat对象并调用方法hi

classfullpath=com.study.Cat

method=hi

首先可以通过Properties类读取配置文件,获取内容。

然后创建对象。

但是这里我们获取不到类名,无法将一个String类型的数据当成类名。类名是没有类型的,我们无法调用,方法名也是。我们没有一个叫类名的类型来储存类名。

由此引出了反射机制。遵循OCP原则(开闭原则:不修改源码,扩容功能)

//首先,还是通过Properties类,获取配置文件信息,取得classpath和mathod
       Properties properties = new Properties();
       properties.load(new FileInputStream("src\\pb.properties"));
       properties.list(System.out);
       String classpath = properties.getProperty("classpath");
       String method = properties.getProperty("method");
//然后利用反射,获取唯一类Class。和类型是Person类一样,这是Class类
       Class Cat = Class.forName(classpath);
//通过类名,获取一个新实例
       Object o = Cat.newInstance();
//通过类名,获取类中方法
       Method method1 = Cat.getMethod(method);
//反射方式使用hi方法
       method1.invoke(o);

我们可以改变配置文件,来获取项目的具体的类,具体的方法,然后调用。而不用修改源码

  1. 反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如成员变量、构造器、成员方法等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到

  2. 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象)【比如,我第一次new Person类,他首先会加载Person类的信息,然后生成一个对应的Class类对象,再生成一个Person类的对象实例】,这个对象包含了类的完整结构信息,可以通过这个对象得到类的结构。这个Class对象就像一面镜子,透过这个镜子看到类的结构。

程序计算器的三个阶段

编译阶段/代码阶段

类所在的.java文件被javac程序编译成.class字节码文件

这里面有类的信息,属性、构造器、方法

Class类阶段(加载)

.class字节码文件之后会通过类加载器ClassLoader加载类对象(堆)

Runtime运行阶段

生成实例对象,分配空间,调用方法。每个实例对象都会指向它的那个类模板

反射作用

  1. 运行时判断任意一个对象所属的类

  2. 运行时构造任意一个类的对象

  3. 运行时得到任意一个类所具有的成员变量和方法

  4. 运行时调用任意一个对象的成员变量和方法

  5. 生成动态代理

相关类

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的模板对象

  2. java.lang.reflect.Method:代表类的方法

  3. java.lang.reflect.Field:代表类的成员变量

  4. java.lang.reflect.Constructor:代表类的构造方法

优缺点

优点

可以动态的创建和使用对象(框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支持

缺点

使用反射基本是解释执行,对执行速度有影响

优化

hi.setAccessible(true)

取消掉反射的安全检查,可以调优(可以调,只能调一点点)

Class类

  1. Class类也是类,因此也继承Object类

  2. Class类对象不是new出来的,而是系统创建的

  3. 对于某个类的Class对象,在内存中只有一份,因为类只加载一次

  4. 每个类的实例都会记得自己是由哪个Class类所生成

  5. 通过反射,Class对象可以完整地得到一个类的完整结构

  6. Class对象是存放在堆的

  7. 类的字节码二进制数据,是放在方法区的,有的地方成为类的元数据(包括方法代码,变量名,方法名,访问权限等) https://www.zhihu.com/question/38496907

常用方法

  1. Class forName(String name):返回指定类名name的Class对象

  2. Object newInstance():调用无参构造函数,返回该Class对象的一个实例

  3. getName():返回此Class对象所表示的实体(类、接口、数组类、基本类型等)名称

  4. Class getSuperClass():返回当前Class对象的父类的Class对象

  5. Class[] getInterfaces():返回当前Class对象的接口

  6. ClassLoader getClassLoader():返回该类的加载器

  7. Constructor[] getConstructors():返回一个包含某些Constructor对象的数组

  8. Field[] getDeclaredFields():返回Field对象的一个数组

  9. Method getMethod(String name,...param Types):返回一个Method对象,此对象的形参类型为paramType

java.lang.Class类方法

补:Class cls = Class.forName("com.study.reflection.Cat")

  1. cls.getName:获取全类名

  2. cls.getSimpleName:获取简单类名

  3. cls.getFields:获取所有public修饰的属性,包含本类和父类(所有的)

  4. cls.getDeclareFields:获取所有本类中该类特有的属性(包括私有的),不能获取父类的属性

  5. cls.getMethods::获取所有public修饰的方法,包含本类和所有父类

  6. cls.getDeclareMethods:本类所有方法(不包含继承父类的)

  7. cls.getConstructors:本类public构造器,不包含父类(有点不一样)

  8. cls.getDeclareConstructors:获取本类的所有构造器

  9. cls.getPackage:获取包信息

  10. cls.getSuperClass:返回父类Class对象

  11. cls.getInterfaces:返回所有接口的数组形式

  12. cls.getAnnotations:返回所有注解的数组形式

java.lang.reflect.Field类方法

Field[] fields = cls.getDeclareFields();

  1. fields.getModifiers:以int形式返回修饰符【默认修饰符是0,public是1,private是2,protected是4,static是8,final是16】,如果有public static就是1+8 = 9

  2. fields.getType:以Class形式返回属性对应类型

  3. fields.getName:返回属性名

java.lang.reflect.Method类方法

Method[] methods = cls.getDeclaredMethods();

  1. methods.getModifiers:以int形式返回修饰符,同上

  2. methods.getReturnType:以Class形式获取返回值类型

  3. methods.getName:返回方法名

  4. methods.getParameterTypes:以Class[] 返回参数类型数组

java.lang.reflect.Constructor类方法

Constructor[] constructors = cls.getDeclareConstructors();

  1. constructors.getModifiers:以int形式返回修饰符

  2. constructors.getName:返回构造器名(全类名)

  3. constructors.getParameterTypes:以Class[] 返回参数类型数组

获取Class类对象

  1. 已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassotFoundException 实例:Class cls = Class.forName("java.lang.Cat") 应用场景:多用于配置文件,读取全类名,加载类

  2. 已知具体的类,通过类的Class获取,该方式最为安全可靠,程序性能最高 实例:Class cls = Cat.class; 应用场景:多用于参数传递,比如通过反射得到对应构造器对象

  3. 已知某个类的实例,调用该实例的getClass方法获取Class对象 实例:Class cls = 对象实例.getClass() 应用场景:通过创建好的实例对象,获取Class类对象

  4. 其他方式:已知类的全类名和实例对象,通过类加载器 实例:ClassLoader clsLoader = 对象实例.getClassLoader(); Class cls = clsLoader.loadClass("类的全类名")

  5. 基本数据类型 实例:Class cls = 基本数据类型.class

  6. 基本数据类型的包装类,通过.TYPE得到Class类对象 实例:Class cls = 包装类.TYPE

Class对象的类型

那些类型有Class对象

  1. 类:外部类、成员内部类、静态内部类、局部内部类、匿名内部类

  2. 接口:interface

  3. 数组

  4. 枚举:enmu

  5. 注解:annotation

  6. 基本数据类型

  7. void

反射创建对象

  1. 方式一:调用类中的public修饰的无参构造器

  2. 方式二:调用类中的指定构造器

  3. Class类相关方法

    • newInstance:调用类中的public的无参构造器,获取对应类的对象

    • getConstructor(Class ...):根据参数列表,获取对应的public有参构造器的对象

    • getDeclaredConstructor(Class...):根据参数列表,获取对应的有参构造器对象

  4. Constructor类相关方法

    • setAccessible:暴破(访问私有成员必备)

    • newInstance(Object ...obj):调用构造器

反射访问成员

属性

  1. 根据属性名获取Field对象

    Field f = class对象.getDeclaredField(属性名);

  2. 暴破:f.setAccessible(true);破除私有属性的访问权限

  3. 访问

    f.set(o,值);//o表示对象实例

  4. 如果是静态属性,则set和get中的参数o,可以写成null

方法

  1. 根据方法名和参数列表获取Method方法对象

    Method m = class.getDeclaredMethod(方法名,XX.class)

  2. 获取对象:Object o = class.newInstance()

  3. 暴破:m.stAccessible(true)

  4. 访问:Object returnValue = m.invoke(o,实参列表)

  5. 如果是静态方法,invoke的参数o,可以写作null

类加载

反射机制是Java实现动态语言的关键,也就是通过反射实现类动态加载

静态加载

静态加载:编译时加载相关的类,如果没有则报错,依赖性太强。

IDEA所检查的,都是编译期错误,即静态加载

new Cat()是静态加载,编译的时候,必须要有Cat类,才能通过

即使是选择的一个分支,不一定会走这行代码

动态加载

动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性

编译时期不会检查,只有当代码走到这的时候(动态加载到这),才会报错

类加载时机

  1. 创建对象时new:静态加载

  2. 子类被加载时,其类和父类都被加载:静态加载

  3. 调用类中的静态成员时:静态加载

  4. 通过反射:动态加载

类加载流程

首先将.java文件编译成.class二进制文件

然后经过java运行,进入下一阶段

Loading加载阶段(JVM)

将类的class文件读入内存(方法区的字节码class二进制数据),并为之创建一个java.lang.Class对象。此过程由类的加载器完成

Linking连接阶段(JVM)

将类的二进制数据合并到JRE中

验证Verification

确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全

包括:文件格式验证(是否以魔数0xcafebabe开头)、元数据验证、字节码验证和符号引用验证

可以考虑关闭大部分的类验证措施,缩短虚拟机类加载时间。使用-Xverify:none

准备Preparation

JVM会在该阶段对静态变量,分配内存并默认初始化(final修饰的属性,是不可更改的,所以不会有默认初始化,会直接在类阶段赋值给初始化)。这些变量所使用的内存都将在方法区中进行分配

解析Resolution

虚拟机将常量池内的符号引用替换为直接引用的过程。一开始的各种引用,由于没有进入内存,也就没有地址,引用是靠符号引用来确定的。当进入内存后,需要将符号引用替换为直接引用(地址)

initialization初始化阶段(程序员)

JVM负责对类进行初始化,这是类加载时期,初始化的都是静态成员

  1. 此阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>()方法的过程

  2. <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并

  3. 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确的加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,知道活动线程执行<clinit>()方法完毕

  4.  

posted @ 2021-08-25 00:07  灰线  阅读(43)  评论(0)    收藏  举报