目录
框架:开发中的半成品软件,程序员可以在框架的基础上进行软件开发,简化编码,可以让程序员集中注意力在代码逻辑上。
在了解反射之前,我们先来看看一个java程序从编写到运行过程中在计算机中经历的三个阶段。
阶段一:Source源代码阶段
java文件:当我们编写完java类后,基本的*.java文件就形成了,经过javac命令编译Java文件,在编译过程没有问题的情况下,硬盘上会生成一个文件叫字节码文件即*.class文件。
字节码文件:字节码文件放入的是各个模块的数据即成员变量信息,构造方法数据,成员方法数据及类名等等。
注意:此时这两个文件都存放在硬盘中,需要加载到内存中执行。
类加载器:通过ClassLoader对象(类加载器)将字节码文件加载到内存当中去。
阶段二:Class类对象阶段
Class类对象:用于描述java程序中所有字节码文件的共同特征和行为。
Class类对象将字节码中的成员变量们封装成了一个File对象数组,来存储这些变量,将字节码中的构造方法们封装成了一个Constructor对象数组,将成员方法们封装成了一个Method对象数组。
之后我们可以通过Class类对象的行为就可以来创建对象。
阶段三:Runtime运行时阶段
通过new 构造方法创建对象,通过对象调用一系列的方法。
反射:将类的各个组成部分封装为其他对象,这就是反射机制。
反射的作用
1.可以在程序运行过程中,操作这些对象。
例如:
IDEA中,当我们要使用方法时常常会有非常贴心的提示。
整个IDEA运行过程中,已经将对象的方法封装成了一个类,这里是将类中的成员方法放在列表中供我们选择,这就是一个反射的应用。
2.可以解耦,提高程序的可扩展性。
获取字节码Class对象
我们以我们自己编写的person类为例。
想要实现第一个作用——操作对象,那我们就得先学会怎么获取字节码Class对象。
获取class对象的方式:
1.Source源代码阶段:通过Class.forName("全类名"):将字节码文件加载到内存,返回Class对象。
2.Class类对象阶段:当你已经将字节码文件加载进内存了,接下来就不需要加载,只需要获取对象。
通过类名person的属性Class即Person.Class来获取。
3.Runtime运行时阶段:当你已经创建Person对象,利用Person对象的getClass方法。
注意:getClass方法是被封装在object类中的,它被所有的对象继承。
我们来用代码演示这三种方法:
我们先定义一个Person类,利用Person类来演示方法。
注意:这两个方法是定义在同一个包下的。
package reflect;
public class Person {
private String name;
public int age;
public String sex;
public Person() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
package reflect;
public class Reflection01 {
/*
获取Class对象的方式:
1.通过Class.forName("全类名"):将字节码文件加载到内存,返回Class对象。
2.通过类名Person的属性class即Person.class来获取。
3.利用Person对象的getClass方法。
*/
public static void main(String[] args) throws Exception {
//方法1
Class cls1 = Class.forName("reflect.Person");
//方法2
Class cls2 = Person.class;
//方法3
Person person = new Person();
Class cls3 = person.getClass();
//利用"=="比较三个对象是否是相等的
System.out.println(cls1 == cls2);
System.out.println(cls1 == cls3);
}
}
这是控制台最终输出的最终结果。
注意事项:
0.用"=="比较对象,比较的是对象的物理地址,物理地址相同,说明是同一个对象。
1.第一个方法Class.forName("全类名“):全类名指的是也要包名写入。
2.第二个方法中class是属性,不是成员方法。
3.第三个方法中,getClass()方法是静态方法,所以可以直接调用。
查询api文档可见下图:
最终结论:
同一个字节码文件在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获得的Class对象都是同一个。
总结:
1.方法一多用于配置文件,将类名定义在配置文件中,读取文件,加载类。
2.方法二多用于参数的传递。
3.方法三多用于对象的获取字节码的方式。
使用Class对象
现在我们已经知道如何获取Class对象,现在就来了解如何使用Class对象。首先我们得了解它有哪些功能以及如何使用。这里我们以Person类获取的Class对象为例。
获取成员变量(成员变量往往不止有一个哦)
Field[] getFields():获取所有public修饰的成员变量。
Field getField(String name):获取指定名称的public 修饰的成员变量
package reflect;
import java.lang.reflect.Field;
public class Reflection02 {
public static void main(String[] args) throws Exception {
//获取Class对象
Class cls1 = Person.class;
//利用getFields()方法获取所有public 修饰的成员变量
Field[] fields = cls1.getFields();
//利用foreach遍历
for (Field field:fields) {
System.out.println(field);
//输出:public int reflect.Person.age
//public java.lang.String reflect.Person.sex
}
//利用Filed getField(String name)方法获取指定名称的public 修饰的成员变量
Field field1 = cls1.getField("sex");
System.out.println(field1);
//会打印public java.lang.String reflect.Person.sex
}
}
Field[] getDeclaredFields():获取所有的成员变量。
Field getDeclaredField(String name):获取指定名称的成员变量。
// Field[] getDeclaredFields():获取所有的成员变量。
Field[] field2 = cls1.getDeclaredFields();
for (Field field: field2) {
System.out.println(field);
}
// Field getDeclaredField(String name):获取指定名称的成员变量。
Field field3 = cls1.getDeclaredField("name");
使用成员变量:
注意:
1.上文获取的成员变量是被封装了之后的Field对象,我们需要通过调用对象的方法来获取成员变量的值和设置成员变量的值。
2.由于Field[] getDeclaredFields()可以获得所有成员变量,则包括私有变量。但是当我们通过get,set方法使用该私有变量时会报错,我们需要进行忽略访问权限修饰符的安全检查即暴力反射。
暴力反射:通过Field对象.setAccessible(true)可以实现。
1.设置值:void set(Object obj,Object value)
2.获取值:get(Object obj)
Person person = new Person();
field3.setAccessible(true);
//注意get方法中需要传入person来指明具体的对象,因为Class对象里可能有多个类的成员变量。
field3.set(person,"张三");
Object value1 = field3.get(person);
System.out.println(value1);
获取构造方法
Constructor<?>[ ] getConstructors()
Constructor<T> getConstructor(类<?> ...parameterTypes)
注意:这个getConstructor获取构造方法的参数比较特别,让我们来看看API文档吧
举个例子来看看~
package reflect;
import java.lang.reflect.Constructor;
public class Reflection03 {
public static void main(String[] args) throws Exception {
Class cls1 = Person.class;
//获取构造方法
//Constructor<T> getConstructor(类<?> ...parameterTypes)
//这个方法的参数是不同类型的class对象
Constructor constructor = cls1.getConstructor(String.class,int.class,String.class);
System.out.println(constructor);
//控制台打印结果:public reflect.Person(java.lang.String,int,java.lang.String)
//表名已获取构造器对象。
}
}
Constructor<T> getDeclaredConstructor(类<?>... parameterTypes)
Constructor<?> [ ] getDeclatedConstructors()
注:这两个获取构造器的方法同理。
使用构造方法
//使用有参的方法
Object person = constructor.newInstance("张三",34,"男");
System.out.println(person);
//使用空参的方法
Object person2 = cls1.newInstance();
System.out.println(person2);
获取成员方法
Method[] getMethods()
Method getMethod(String name,类<?>...parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name,类<?>...parameterTypes)
package reflect;
import java.lang.reflect.Method;
public class Reflection04 {
public static void main(String[] args) throws Exception {
Class cls1 = Person.class;
//获取指定名称的方法
//确定一个方法有两个要素:方法名称,参数列表
Method method = cls1.getMethod("eat");
Person person = new Person();
//空参方法的使用:执行方法
method.invoke(person);
//有参方法的使用
//第一个参数是方法名称,第二个是方法参数类型的Class对象
Method method1 = cls1.getMethod("eat",String.class);
Person person1 = new Person();
//第一个参数是类名,第二个参数是方法中的你需要传递的具体值
method1.invoke(person1,"早餐");
//获取方法的名称
String name = method1.getName();
System.out.println(name);
//获取类名的方法
String classname = cls1.getName();
System.out.println(classname);
}
}
如有错误,请指正~