引言:
假如你写了一段代码:Object o=new Object();运行了起来!首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。
上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。
相当于这些代码都是写 “死” 的,但是在运行时要使用则需要利用反射通过包名灵活地加载某些类。
====================================================================
-
Java程序的运行过程
源文件(java)–》javac 编译器编译为class字节码文件–》类加载器加载到内存中(方法区中code segment)–》字节码校验器进行校验–》解释器进行解释–》操作系统执行
-
被类加载器加载到内存的字节码文件其实就是一个又一个的Class(Filed,Mehtod,Constructor)的对象,把这些对象放大来看,里面的成员变量和方法都是对象
什么是反射?
在运行过程中,动态的获取类的信息以及对类进行操作的机制,就被称为反射。
为什么需要用到反射?
反射给程序设计提供了很大的灵活性,解决了很多死的东西。可以根据动态需求,产生动态响应。常用框架中基本都用到了反射,尤其在hibernate这种持久层框架中,用反射生成sql语句。反射是框架的灵魂;可以解耦,提高程序的可扩展性
反射的简单应用
- Field:代表类的成员变量(属性);
- Method:代表类的方法;
- Constructor:代表构造器(构造函数、构造方法);
获取Class类对象的三种方式
创建Student类
public class Student{
private String name;
public Student() {
}
public Student(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
'}';
}
}
创建测试类ReflectDemo
获取Class类对象的三种方式
Class.forName(“类的全路径”); 包名+类名 最常用
//获取Class类对象的三种方式
//1. 类名.class
Class<Student> studentClass = Student.class;
//2. 对象.getClass
Student student = new Student();
Class<? extends Student> aClass1 = student.getClass();
//3. Class.forName("类的全路径"); 包名+类名 最常用
Class<?> aClass = Class.forName("com.codeyancy.test.Student");
getMethods()和getDeclaredMethods()
- getMethods() 获取所有public的方法,包括继承下来的
- getDeclaredMethods() 获取本类中声明的所有方法,不包含继承的
//getMethods() 获取所有public的方法,包括继承下来的
Method[] methods = aClass.getMethods();
for (Method method : methods) {
System.out.println(method.getName());
}
System.out.println("======");
//getDeclaredMethods() 获取本类中声明的所有方法,不包含继承的
Method[] declaredMethods = aClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod.getName());
}
结果:
这时候,我们把Student类中的getName()方法改为private修饰,再进行观察:
Student
private String getName() {
return name;
}
我们再次运行getMethods()和getDeclaredMethods()方法观察可以发现:getMethods() 获取所有public的方法,包括继承下来的(而不包括private),getDeclaredMethods() 则可以获取本类中声明的所有方法!
getFields()和getDeclaredFields()
- getFields() 获取所有public的成员变量 包括继承下来的
- getDeclaredFields() 获取本类中声明的所有成员变量,不包含继承的
//getFields() 获取所有public的成员变量 包括继承下来的
Field[] fields = aClass.getFields();
for (Field field : fields) {
System.out.println(field.getName());
}
System.out.println("++++++++");
//getDeclaredFields() 获取本类中声明的所有成员变量,不包含继承的
Field[] declaredFields = aClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField.getName());
}
getConstructors()和getDeclaredConstructors()
public static void getConstructor() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName("com.codeyancy.test.Student");
//获取构造器
Constructor<?>[] constructors = aClass.getConstructors();
Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println(declaredConstructor.getName());
}
//获取的是指定的无参构造器
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
Object o = declaredConstructor.newInstance();
System.out.println(o);
//获取的是指定的有参构造器
Constructor<?> declaredConstructor1 = aClass.getDeclaredConstructor(String.class);
Object tom = declaredConstructor1.newInstance("tom");
System.out.println(tom);
}
运行结果:
获取Studdent类的无参构造
Class<?> aClass = Class.forName("com.codeyancy.test.Student");
//调用的是无参构造,如果Student类中没有无参构造就会报错
Object o = aClass.newInstance();
System.out.println(o);
结果:
Student类进行修改:
private void study(String a, int score) {
System.out.println(a +"正在学习,考了" + score);
}
public String getName() {
System.out.println("getName被调用了");
return name;
}
测试类:
Class<?> aClass = Class.forName("com.codeyancy.test.Student");
//调用的是无参构造,如果Student类中没有无参构造就会报错
Object o = aClass.newInstance();
//获取指定方法 无参
Method declaredMethod = aClass.getDeclaredMethod("getName");
//通过反射进行方法调用
declaredMethod.invoke(o);
Method study = aClass.getDeclaredMethod("study",String.class, int.class);
//setAccessible(true) 修改为可访问的权限;如果没有则不能访问private修饰的方法
study.setAccessible(true);
study.invoke(o,"tom",80);
结果:
关于反射的代码案例演示:
在Student类的基础上增加一个属性:
private Computer computer;
定义一个抽象电脑类和他的两个子类:
Computer类:
public abstract class Computer {
private String name;
public Computer() {
}
public Computer(String name) {
this.name = name;
}
getter/setter
toString
DellComputer类:
public class DellComputer extends Computer {
public DellComputer() {
}
public DellComputer(String name) {
super(name);
}
}
AppleComputer类:
public class AppleComputer extends Computer {
public AppleComputer() {
}
public AppleComputer(String name) {
super(name);
}
}
测试类:
public static void test() throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
Class<?> aClass = Class.forName("com.codeyancy.test.Student");
//获取学生构造器
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor(String.class,Computer.class);
//获取电脑的class类对象
Class<?> aClass1 = Class.forName("com.codeyancy.test.DellComputer");
//获取电脑的指定构造器
Constructor<?> declaredConstructor1 = aClass1.getDeclaredConstructor(String.class);
//实例化一个电脑实例
Object computer = declaredConstructor1.newInstance("dell");
//实例化学生对象
Object stu = declaredConstructor.newInstance("tom", computer);
System.out.println(stu);
}
运行结果:
修改测试类代码:
运行结果:
好处:
不需要修改源代码即可进行改动,降低了程序的耦合性。
类的加载过程
动态的加载;按需加载,用的时候才会加载这个类的字节码文件
类加载器有哪些?
-
核心加载器(bootstrap class loader)
加载最核心的类;是本地语言写的,没有名字
-
扩展类加载器
加载的是jre/lib/ext包下面的类
-
应用类加载器
加载的是用户自定义的类
-
其他加载器
双亲委派机制
为什么需要双亲委派机制?
- 防止字节码文件被重复加载
当类加载器收到一个加载类的请求的时候,会把请求先委派为父类,父类也会继续把请求往上委派,最终请求都会到达核心类加载器,如果在核心类加载器的加载范围就会去加载否则是加载失败继续往回追;如果父级都加载失败了才会由当前的类加载器进行加载