Java反射机制
JAVA反射机制
JVM退出情况:
1):程序正常执行结束
2):使用 System.exit(0)方法
3):出现异常时,没有捕获异常
4):平台强制结束JVM进程
JVM进程一旦结束,该进程内存中的数据将会丢失
当程序主动使用到某个类时,如果该类还未被加载进内存中,则系统会通过 加载,连接,初始化三个步骤来对类进行初始化操作
加载(Loading)--->连接{验证(Verification)-->准备(Preparation)-->解析(Resolution)}-->初始化(initialization)-->使用(Using)-->卸载(UnLoading)
1):类的加载
类加载值将类的class文件(字节码文件)载入内存中,并为之创建一个Java.lang.Class对象,我们称之为字节码对象
类的加载过程由类加载器(ClassLoader)完成,类加载器通常有JVM提供,称之为系统类加载器
不同的类加载器可以实现加载本地字节码文件,jar包中的字节码,通过网络加载字节码等
2):类的连接
当类被加载进内存之后,系统为之生成一个对应的Class对象,接着把类的二进制数据合并到JRE中
1>验证:检测被加载的类是否有正确的内部结构
2>准备:负责为类的static变量分配内存,并设置默认值
3>解析:把类的二进制数据中的符号引用替换为直接引用(深入分析JVM 书籍)
3):类的初始化
在此阶段,JVM负责对类进行初始化,主要就是对static变量进行初始化
步骤:
1>如果该类还未被加载和连接,则程序先加载并连接该类
2>如果该类的直接父类还未被初始化,则先初始化其父类
3>如果类中有初始化语句(静态代码块),则系统依次执行这些初始化语句
符号引用:
符号引用是一个字符串,它给出了被引用的内容的名字并且可能会包含一些其他关于这个被引用项的信息
编译类和运行类型
Object obj = new java.util.Date();
编译类型:Object
运行类型:java.util.Date()
元数据:描述数据的描述数据
反射:得到类的元数据的过程,在运行时期,动态的去获得某一个类中的成员信息(构造器,方法,字段,内部类,接口,父类等等),并且把类中的每一种成员,都描述成一个新的类
Class:表示所有的类
Constructor:表示所有的构造器
Method:表示所有的方法
Field:表示所有的字段
Class类:用来描述类或者接口的类型
Class类的实例:在JVM中的一份份字节码,Class实例表示在JVM中的类或者接口,枚举是一种特殊的类,注解是一种特殊的接口
Class 类的实例表示正在运行的 Java 应用程序中的类和接口。枚举是一种类,注释是一种接口。每个数组属于被映射为 Class 对象的一个类,
所有具有相同元素类型和维数的数组都共享该 Class 对象。基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)
和关键字 void 也表示为 Class 对象。
java.lang.Class<T>
T - 由此 Class 对象建模的类的类型。例如,String.class 的类型是 Class<String>。如果将被建模的类未知,则使用 Class<?>。
1 获取字节码对象:Class对象,以java.util.Date为例 2 //方式一:使用class属性 3 Class<java.util.Date> claz = java.util.Date.class; 4 //方式二:通过对象的getClass方法来获取,getClass是Object类的方法 5 java.util.Date date = new java.util.Date(); 6 Class<?> calz = date.getClass(); 7 //方式三:通过Class类中的静态方法forName(String className) 8 Class<?> clz = Class.forName("java.util.Date"); 9 >>>方式三使用最多,在框架中大量使用
基本数据类型不能表示为对象,也就不能使用getClass的方式,基本数据类型没有类名的概念,也不能使用Class.forName的方式,如何表示其字节码对象?
所有的数据类型都有Class属性
Class clz = 数据类型.class;
九大内置Class实例
byte,short,int,long,double,float,char,boolean,void
byte.class short.class ... void.class
==>Integer.class 和 int.class 不同,其是两个不同的数据类型
在8大基本数据类型的包装类中,都有一个常量:TYPE,用于返回该包装类对应基本数据类型的字节码对象
Integer.TYPE == int.class ; //true
数组的Class实例
方式一: 数组类型.class; 如:int[].class
方式二: 数组对象.getClass();
注意:所有具有相同的维数和数据类型的数组共享同一份字节码对象,和元素没有关系
Class: 描述所有的类型,所以Class类中应该有所有类型的相同的方法
Object: 描述所有的对象,所以在Object类中应该具有所有对象的共同方法
通过反射来获取某一个类的构造器:
1):获取该类的字节码对象
2):从该字节码对象中去找需要获取的构造器
1 //获取所有构造器 2 //1,获取所有的构造器 3 Class<User> clz = User.class; 4 //2,获取对象中所有的构造器 5 //Constructor<?>[] getConstructors()只能获取public修饰的构造器 6 Constructor<?>[] cs = clz.getConstructors(); 7 for (Constructor<?> c:cs){ 8 System.out.println(c); 9 } 10 //Constructor<?>[] getDeclaredConstructors获得所有的构造器 11 cs = clz.getDeclaredConstructors(); 12 for (Constructor<?> c:cs){ 13 System.out.println(c); 14 } 15 16 //找到指定构造器 17 //获取User() 18 Constructor<User> conn = clz.getConstructor(); 19 System.out.println(conn); 20 //获取User(java.lang.String) 21 conn = clz.getConstructor(String.class); 22 System.out.println(conn); 23 //获取User(java.lang.String,int) 24 conn = clz.getDeclaredConstructor(String.class, int.class); 25 System.out.println(conn);
----------------------------------------------------------------------------------------------
构造器:最大作用创建对象
为什么使用构造器,不new
在框架中,提供给我们的都是字符串
使用反射创建对象
1):找到构造器所在类的字节码文件
2):获取构造器
3):使用反射,创建对象
Constructor<T>类:表示类中构造器的类型,Constructor的实例就是某一个类中的某一个构造器
常用方法:
public T newInstance(Object...initargs):如果调用带参数的构造器,只能使用该方式
参数:initargs 表示调用构造器的实际参数
返回: 创建的实例
如果一个类中的构造器是外界可以直接访问,同时没有参数,那么可以直接使用Class类中的newInstance方法创建对象
public Object newInstance() 相当于 new 类名()
调用私有的构造器:
//设置当前构造器可以访问
对象.setAccessible(true);
通过反射来获取类中的方法
1):获取方法所在类的字节码对象
2):获取方法
Class类中常用方法
public Method[] getMethods() :获取包括自身和继承过来的所有的public方法
public Method[] getDeclaredMethods() : 获取自身类中所有的方法(不包括继承的,和访问权限无关)
public Method[] getMethod(String methodName, Class<?>... parameterTypes):表示调用指定的一个公共的方法(包括继承的)
参数:methodName 表示调用方法的名字
parameterTypes :表示被调用方法的参数的Class类型如String.class
public Method[] getDeclaredMethod(String name, Class<?>... parameterTypes) :表示调用指定的一个本类中的方法(不包括继承的)
参数:methodName 表示调用方法的名字
parameterTypes :表示被调用方法的参数的Class类型如String.class
1 //获取doWork()方法 2 Class clz = Student.class; 3 Method m = clz.getMethod("doWork"); 4 System.out.println(m); 5 //获取doWork(String name)方法 6 m = clz.getMethod("doWork", String.class); 7 System.out.println(m);
---------------------------------------------------------------------
>>>>调用方法
1 //获取doWork()方法 2 Class clz = Student.class; 3 Method m = clz.getMethod("doWork"); 4 System.out.println(m); 5 //调用doWork()方法 6 Object obj = m.invoke(clz.newInstance()); 7 System.out.println(obj); 8 //获取doWork(String name)方法 9 m = clz.getMethod("doWork", String.class); 10 System.out.println(m); 11 obj = m.invoke(clz.newInstance(),"格格"); 12 System.out.println(obj); 13 //调用private String sayHello(String name, int age)方法 14 m = clz.getDeclaredMethod("sayHello", String.class, int.class); 15 System.out.println(m); 16 //设置可访问私有成员 17 m.setAccessible(true); 18 obj = m.invoke(clz.newInstance(),"格格",12); 19 System.out.println(obj);
-------------------------------------------------------------------------------
>>>>使用反射调用静态方法
静态方法不属于任何对象,静态方法属于类本身
此时把invoke方法的第一个参数设置为null即可
obj = m.invoke(null,"格格");
使用反射调用数组参数(参数可变) class Employee{ public static void doWork1(int...arr) { System.out.println("doWork1被调用,"+Arrays.toString(arr)); } public static void doWork2(String...arr) { System.out.println("doWork2被调用,"+Arrays.toString(arr)); } } public class MethodArrDemo { public static void main(String[] args) throws SecurityException, NoSuchMethodException, IllegalArgumentException,
IllegalAccessException, InvocationTargetException { Class clz = Employee.class; //1,数组的元素类型是基本类型 Method method = clz.getMethod("doWork1", int[].class); //method.invoke(null, 1,2,3,4,5);//error method.invoke(null, new int[]{1,2,3,4,5,}); //2,数组的元素类型是引用类型 method = clz.getMethod("doWork2", String[].class); //method.invoke(null, "a","b","c");//error //method.invoke(null, new String[]{"a","b","c"});//error method.invoke(null, new Object[]{new String[]{"a","b","c"}}); } }
-->调用方法的时候把实际参数统统作为Object数组的元素即可
Method对象.invoke(方法底层所属对象, new Object[]{说有实参});
--------------------------------------------------------------------------------------
加载资源文件
db.properties
注意:properties只能用load()方法
方式一:使用绝对路径的方式加载,该方式不可行
方式二:使用相对路径,相对于classpath的根路径(字节码输出目录) 资源放于resources文件夹
此时得使用 ClassLoader(类加载器),类加载器默认就是从classpath根路径去寻找文件的
1 Properties p = new Properties(); 2 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 3 InputStream is = loader.getResourceAsStream("db.properties"); 4 p.load(is); 5 System.out.println(p);
方式三:使用相对路径,相对于当前加载资源文件的字节码路径 资源和类放于一起
1 Properties p = new Properties(); 2 InputStream is = TestPro(当前类).class.getResourceAsStream("db.properties"); 3 p.load(is); 4 System.out.println(is);
当前类.class.getClassLoader().getResourceAsStream("db.properties")