一、类的加载
(一) 定义及过程:
当程序需要使用某个类的时候,如果这个类还没有被加载到内存中,则系统会通过加载、连接、初始化三个步骤对这个类进行初始化;
-
加载:
1. 将class文件
读入内存,并为之创建一个Class对象
;
2. 任何类被使用时,系统都会建立一个Class对象
; -
连接:
1. 验证:是否有正确的内部结构,并和其他类协调一致;
2. 准备:负责为类的静态成员分配内存,并设置默认初始化值;(原来静态成员在这里加载)
3. 解析:将类的二进制数据中的引用替换为直接引用; - 初始化:即对对象的初始化;
(二)类初始化时机
- 创建类的实例;
- 访问类的静态变量,或者为静态变量赋值;
- 调用类的静态方法;
- 使用反射方式来强制创建某个类或者接口对应的
Class
对象; - 初始化某个类的子类;
- 直接使用java.exe命令运行某个主类的时候;
(三)类加载器
- 作用:
- 负责将
class
文件加载到内存中, - 为之生成对应的
Class对象
;
- 负责将
- 分类
- 根类加载器(引导类加载器)
Bootstrap ClassLoader
,(即加载系统的东西)- 负责
java
核心类的加载(如String类),在jdk/jre/rt.jar
文件中;
- 负责
- 扩展类加载器
Extension ClassLoader
(即加载扩展类的东西)- 负责jre的扩展目录中jar包的加载,在
jdk/jre/lib/ext
目录下;
- 负责jre的扩展目录中jar包的加载,在
- 系统类加载器
System ClassLoader
(即加载程序员写的类)- 负责在
JVM
启动时加载来自java
命令的class
文件, -
classpath
环境变量所指向的jar包
和类路劲
;
- 负责在
- 根类加载器(引导类加载器)
二、反射
(一) 定义、作用及特点###
- 作用:通过
class
文件来使用类的成员变量、成员方法、构造方法; - 特点:运行状态下,动态获取信息以及动态调用对象方法;(因为是在运行时才做的操作,因此反射实际上有效率损失,会延长代码运行时间)
- 对于任何类,都可以知道这个类的所有属性和方法;
- 对于任何对象,都可以调用他的任意一个方法和属性;
- 原理:要想解剖一个类,必须先要获取一个类的字节码文件对象(即
class
文件)。而解剖使用的就是Class
类中的方法,所以先要获取到每一个字节码文件对应的Class
类型的对象;- 正常情况下:java文件-->class文件-->给
JVM
调用; - 反射:Class文件对象--->class文件对象-->给
JVM
调用;
- 正常情况下:java文件-->class文件-->给
- 因此我们要首先获取
Class文件对象
,然后构造出class文件对象
,这里实际上将所有的class文件
看成对象,这个对象的类就是Class
,成员变量就是成员变量
、成员方法
、构造方法
;
(二)反射的代码实现
要想使用反射,就必须首先得到class文件对象
,也即Class类
的对象,
因为class文件对象
只有一个,所以不管通过什么方式得到Class类
的对象,都是同一个对象;
1. 获取class文件
对象的方式
-
Object.getClass();
Person p1= new Person(); Class<? extends Person> clazz1 = p1.getClass(); Person p2= new Person(); Class clazz2 = p2.getClass(); //true //因为`class文件对象`只有一个,所以不管通过什么方式得到`Class类`的对象,都是同一个对象; System.out.println(clazz1 == clazz2);
-
数据类型的静态属性class;
Class p3 = Person.class; System.out.println(clazz1 == clazz3);
-
Class
类中的静态方法public static Class forName(String className)
;Class clazz4 = Class.forName("com.wvbx.java.Person"); System.out.println(clazz1 == clazz4);
2. 通过反射获取构造方法
并使用
(1)常用API 说明及举例
-
clazz.getConstructors();
获取所有public修饰的构造方法String name = Person.class.getName(); try { Class<?> clazz = Class.forName(name); //获取所有public修饰的构造方法 Constructor<?>[] constructors = clazz.getConstructors(); for (Constructor<?> constructor : constructors) { Log.i(TAG, "onCreate: " + constructor); } } catch (ClassNotFoundException e) { e.printStackTrace(); }
-
clazz.getDeclaredConstructors();
获取所有的构造方法,即使是private
修饰的; -
clazz.getDeclaredConstructor(Class<?> paramsTypes)
获取指定参数列表的构造方法;这里需要传入的是数据类型的静态属性,Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(int.class, String.class); Log.i(TAG, "onCreate: " + declaredConstructor);
输出结果:
public com.wvbx.java.Person(int,java.lang.String)
注意:在所有的方法里面如果有Declared
这个单词,就表示可以获取任意权限的构造方法,包括private
修饰的构造方法;如果不包含Declared
这个单词,就只能获取public
修饰的构造方法,这一点在获取成员方法,成员变量时一样适用;
(2)通过调用上述方法,我们可以得到指定参数列表的构造方法对象,通过这个构造方法对象来创建对应参数列表的对象实例;
-
通过调用
constructor.newInstance(...)
方法来创建对象,这里的参数是对被创建对象的数据初始化;Object newInstance = declaredConstructor.newInstance(18, "张三"); Log.i(TAG, "onCreate: "+newInstance);
输出结果
com.wvbx.java.Person@f648ccf
可以看到,虽然我们用Object类来接受,但实际上就是我们创建的Person
类;因此把对象强制转换成Person
类也不会报错,而且初始化成功;
declaredConstructor.setAccessible(true);
Person newInstance = (Person) declaredConstructor.newInstance(18, "张三");
Log.i(TAG, "onCreate: "+newInstance.getAge()+"---"+newInstance.getName());
输出结果
onCreate: 18---张三
declaredConstructor.setAccessible(true);
在给指定的构造方法的参数赋值时,如果构造方法是private
修饰的,就会赋值失败,必须加上这一句话才会成功。赋值为true
时,指示反射的对象在使用时应该取消java
语言访问检查;
这样我们就可以使用newInstance
这个对象的所有成员变量和成员方法了;
注意:调用clazz.getDeclaredConstructor();
时,这里需要传的参数有几个,在Object newInstance = con.newInstance("张三",18,"北京");
就需要传入同样个数的参数,并且对应的数据类型不能出错,这样才能反射调用成功。因为通过反射创建对象时,是通过特定的构造方法创建对象,比如通过反射三个参数的构造方法来创建对象,如果con.newInstance()一个参数也没有,就相当于通过无参构造方法创建对象,自然报错。可以如此理解:Person p = new Person("张三",18,"北京")
这样一步在反射中是通过多步来完成的;
3. 通过反射获取成员变量
并使用
(1)常用API 说明及举例
-
Field[] fields = clazz.getFields();
获取所有public修饰的成员变量; -
Field[] fields = clazz.getDeclaredFields();
获取所有的成员变量,即使是private
修饰的成员变量; -
Field fields = clazz.getDeclaredField("address");
获取指定名字的成员变量的对象; -
fields.set(Object obj,Object value);
给指定对象的该字段赋值,因此这里首先要有个对象,因为这里字段就是通过反射来获取的,因此这里的对象自然也要通过反射创建;String name = Person.class.getName(); try { Class<?> clazz = Class.forName(name); Constructor<?> con = clazz.getDeclaredConstructor(); con.setAccessible(true); //创建对象 Object newInstance = con.newInstance(); //获取指定字段信息 Field fields = clazz.getDeclaredField("address"); fields.setAccessible(true); //给指定对象的该字段赋值 fields.set(newInstance, "上海"); Log.i(TAG, "onCreate: " + newInstance); } catch (Exception e) { e.printStackTrace(); }
输出结果
Person{address='上海', name='null', age=0}
注意:这里的参数类型必须和类中的规定的参数类型一样,否则会报错;
4. 通过反射获取成员方法
并使用
(1)常用API 说明及举例
-
Method[] methods = clazz.getMethods();
获取自己以及父类的所有public
修饰的方法对象, -
Method[] methods = clazz.getDeclaredMethods();
获取自己所有的成员方法,包括private
修饰的方法对象; -
Method method1 = clazz.getMethod(String name,Class<?>... paramsType);
通过方法名
和方法参数列表的Class类型列表
获取方法对象; -
public Object invoke(Object obj,Object...args)
第一个参数表示对象是谁,第二个参数表示调用该方法的实际参数,返回值就是该方法的实际返回值(比如Person类中某个方法有返回值,这里的Object就是接受这个返回值的);
(2)通过获取的方法对象调用该对象的方法
Class<?> clazz = Class.forName(name);
Constructor<?> con = clazz.getDeclaredConstructor(String.class, int.class, String.class);
Object newInstance = con.newInstance("张三",18,"北京");
Log.i(TAG, "onCreate: " + newInstance);
Method method1 = clazz.getMethod("setName",String.class);
method1.setAccessible(true);
method1.invoke(newInstance, "李四");
Log.i(TAG, "onCreate: " + newInstance);
输出结果
Person{address='北京', name='张三', age=18}
Person{address='北京', name='李四', age=18}
可以看到我们通过反射创建对象并初始化时,name=张三
,然后通过反射调用setName
方法并重新赋值为李四
,赋值成功,说明通过method.invoke(Object obj,Object args)
可以调用obj
的指定方法;
注意:clazz.getMethod("setName",String.class);
的参数列表和method1.invoke(newInstance, "李四");
的参数列表必须相同,而且一一对应,否则也会报错,理由和构造方法相同,尤其是在有方法重载时要特别注意这个问题。
三、反射的练习
-
给一个
ArrayList<Integer>
的一个集合,需要如何做才能往这个集合中添加字符串数据?-
分析:通过
list.add()
方法肯定不可以,因为编译无法通过。但是我们可以根据泛型的特点来解决,泛型只是在编译时起数据类型检查作用,生成class文件后泛型就不存在,因此如果我们可以在class文件之后向集合中添加数据,就可以绕过泛型检查,像集合中添加数据。而反射实在class文件运行时才执行的,因此满足条件,因此可以通过反射实现这个需求。 -
代码实现
ArrayList<Integer> list = new ArrayList<>(); //只能在运行的时候通过反射获取add方法,然后向里面添加元素 try { Class<?> klass = list.getClass(); Method add = klass.getDeclaredMethod("add", Object.class); add.setAccessible(true); add.invoke(list,"sdlfjld"); add.invoke(list,"dfd"); add.invoke(list,"kuk"); add.invoke(list,"cbc"); for (Object integer : list) { Log.i(TAG, "onCreate: " + integer); } } catch (Exception e) { e.printStackTrace(); }
-
运行结果
onCreate: sdlfjld onCreate: dfd onCreate: kuk onCreate: cbc
-
-
由于反射可以不用知道具体的对象,就可以创建对象,因此反射经常和泛型配合写框架;
网友评论