类加载
当程序使用某个类时,如果该类没有被装载进内存,则系统会通过加载、连接、初始化三个步骤来对类进行初始化。类加载就是类加载器将.class文件加载进JVM虚拟机中,并在虚拟机内存中产生一个Class对象。使用任何一个类虚拟机都会创建一个java.lang.Class类对象。
类加载使用的类加载器是JVM提供,采用双亲委派机制。
类加载器分为四种
- 启动类加载起 BootStrapClassLoader 用来加载核心类库 扩展类加载器 extensionsClassLoader
- 用来加载JRE的扩展目录 Javax. 系统类加载器 SystemClassLoader
- 用来加载来自java命令的classpath指定的JAR包和类路径。
- 自定义类加载器
双亲委派机制
每个类在JVM中的为一性都有类加载器和类本身决定,即使同一个类但是通过不同的类加载器加载也不是同一个类。
一个类的加载时,通过由底向上的顺序判断该类是否被该类加载器加载过,如果都没有,则自顶向下的顺序尝试进行加载。
这样做的目的是防止一个类被多份加载,可以节省空间。
类加载方式
- 隐式加载 new
- 显示加载 反射
类装载的过程
- 加载
加载指的是将类的.class字节码文件,读入到内存中,并为之创建一个java.lang.Class对象。任何类使用之前都会为之创建一个Class对象。类加载有类加载器完成。 - 链接
链接负责把类的二进制数据合并到JRE中。
链接又分为三个阶段:
1、验证:验证阶段用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
2、准备:准备阶段负责为类的静态变量分配内存,并设置默认的初始值。
3、解析:将类的二进制数据中的符号引用替换成直接引用。 - 初始化
为类的静态变量赋予正确的初始值。
反射
反射就是在运行状态中,可以获取类的
- 名称、package、属性、方法、注解、类型、类加载器
- 获取任意对象的属性,并改变对象的属性
- 调用任意对象的方法
- 判断任意对象所属的类
- 实例化任意一个类的对象
package user;
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "user.User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
User user = new User();
- 获取类对象的方法
1)已知实例创建获取类对象
Class u = user.getClass();
2) 已知类的全路径名获取类对象
Class u = Class.forName("user.User");
3)已知类名获取类对象
Class u = User.class
2、通过类获取父类
Class fu = u.getSuperclass();
3、通过对象获取包名+类名
String name = u.getName();
4、获取类名
String name = u.getSimpleName();
5、通过类对象获取类的属性
1)只能获取public属性
Field[] fields = u.getFields();
2)获取全部属性
Field[] fields = u.getDeclaredFields();
3)获取指定属性值
Filed name = u.getField("属性名");
6、获取类的所有方法
!) 获取本类及其父类的所有public方法
Method[] methods = c1.getMethods();
2)获取本类的所有方法
Field[] declaredFields = c1.getDeclaredFields();
3)获得指定的方法, 参数为方法名和方法的参数类型(“Name”, arg1.class, arg2.class, ......)
Method setName = c1.getMethod("setName", String.class);
7、获取构造器
!) 获取本类父类的public构造器
Constructor[] constructors = c1.getConstructors();
2)获取本类的所以构造器
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
3)获取指定构造器,参数为构造方法的参数列表类型
Constructor declaredConstructor = c1.getDeclaredConstructor( String.class, Integer.class);
通过反射获取对象
通过Class对象获取实例
Class c1 = Class.forName("user.Use");
本质是调用类的无参构造来创建实例,如果没有无参构造就会报错
Use u1 = (Use) c1.newInstance();
通过构造器创建对象
1)无参构造器
Constructor declaredConstructor1 = c1.getDeclaredConstructor();
Use u2 = (Use) declaredConstructor1.newInstance();
2)有参构造器
Constructor declaredConstructor = c1.getDeclaredConstructor( String.class, Integer.class);
Use u2 = (Use) declaredConstructor.newInstance("a", "1");
通过反射调用方法
Class c1 = Class.forName("user.Use");
User user = new User();
Method setAge = c1.getDeclaredMethod("setAge", Integer.class);
//对象名,参数
getAge.invoke("user", "a");
反射操作属性
Class c1 = Class.forName("user.Use");
User user = (User) c1.newInstance();
Field field = c1.getDeclaredField("name");
如果这个属性是私有属性那么在操作之前需要加上一句 name.setAccessible(true) 先关闭安全检测
name.set(user, "a");
对于反射来说操作类对象效率比较低
普通方式调用 > 关闭检测的反射 > 不关闭检测的反射