1. 反射概述
- 反射(Reflection)被视为动态语言的关键,反射机制允许程序在运行时借助于Reflection API获取任何类的内部信息,并能直接操作任意对象的内部属性及方法
- 框架 = 反射 + 注解 + 设计模式
- 反射是框架设计的灵魂
2.为什么需要反射?
2.1创建对象的过程
要理解为什么需要反射,首先看一下运行A a=new A()
;时发生了什么:
- 编写好的代码会编译成一个.class文件(字节码文件),然后被类加载器加载进JVM内存中的方法区中,并创建了类A的Class对象到堆中【不是new出来的对象,而是类的类型对象,同一个类只会有一个Class对象,作为方法区类的数据结构的接口】。
- jvm创建对象前,会先检查该类是否加载,寻找类对应的Class对象,若已加载,则为对象分配内存进行初始化,也就是代码:new A()。
- 以上的过程可以理解为“正”
- 上面的情况下对象是我们自己new的,程序相当于写死了让JVM去跑。但是实际应用中服务器会突然遇到某个请求要用到某个类,但是还没加载进JVM,难道要停下来自己写段代码再new一下然后再启动服务器?显然这是不可能的,因此就需要另想办法。
- 因此就需要程序在运行时,能够根据需要动态的加载一些类。例如数据库有时是用mysql,有时用oracle,就需要动态地根据实际情况加载驱动类,这个时候反射就有用了,程序可以写得比较动态化,通过
Class clz = Class.forName("驱动类的全类名");
通过类的全类名(传入的参数)让JVM在服务器中找到并加载这个类。这就体现出了反射的好处了,也即Java语言的动态性! - 使用反射的好处:
- 在程序运行过程中操作对象
- 可以解耦,提高程序的可扩展性
3. 什么是反射?
- 反射机制允许在程序运行时获得任何类的内部信息[属性、方法、构造器等],并能直接调用任意对象的属性和方法,这种动态获取类的信息以及动态调用对象的方法的功能称为java的反射机制。
- 反射把类中的各个组成部分(属性、方法、构造器等)分别映射成一个个Java对象
- 一个类有:成员变量、方法、构造器、父类和接口等信息,利用反射技术可以对一个类进行解剖,把各种成分分别映射成一个个对象
- (其实:类中这些成员方法、构造方法,在加入类中都有一个类来描述)
- 反射的本质:Class对象的由来是将class文件读入内存,并为之创建一个Class对象,然后反向获取对应类的信息
4. Class类的理解
- 程序经过编译(javac.exe命令)以后,会生成一个或多个字节码文件(.class结尾)。接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件加载到JVM内存中,此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此运行时类,就作为Class的一个实例。
- Class和类的关系可以类比类和对象的关系,Class可以理解为是所有其他类的类,每个加载到内存中的类对应一个Class的实例,Class类的实例表示正在运行的 Java 程序中的类和接口(包括基本数据类型)
- 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
5. 反射的使用
- Class、Field、Method、Constructor四个对象的关系:
5.1 获取Class对象
- 在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象,获取Class对象有三种方法:
1.使用 Class.forName()
静态方法
Class clz = Class.forName("java.lang.String");
- 将字节码文件加载进内存,返回Class对象。
- 当知道该类的全路径名时,可使用该方法。 多用于配置文件,将类名定义在配置文件中。读取文件,加载类
2. 使用类名.class
属性
Class clz = String.class;
- 这种方法只适合在编译前就知道操作的 Class。多用于参数的传递
3. 使用obj.getClass()
方法
String str = new String("Hello");
Class clz = str.getClass();
getclass()
方法在object类中定义着。- 多用于对象的获取字节码的方式
5.2 创建运行时类的对象
- 通过反射创建类对象主要有两种方式:通过Class对象的newInstance()方法、通过Constructor 对象的newInstance()方法。
- newInstance()内部调用了运行时类的空参的构造器,因此必须保证运行时类有无参构造器
1. 通过Class对象的 newInstance() 方法。
Class clz = Person.class;
Person p1 = (Person)clz.newInstance();
2. 通过Constructor对象的 newInstance() 方法
Class clz = Person.class;
Constructor constructor = clz.getConstructor();
Person p2 = (Person)constructor.newInstance();
- 通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造器进行了类对象的初始化。
Class clz = Person.class;
Constructor constructor = clz.getConstructor(String.class, int.class);//参数姓名和年龄
Person p3 = (Person)constructor.newInstance("张三", 25);
5.3 获取成员变量并调用
Field[] getFields()
:获取所有public修饰的成员变量(包括父类)Field[] getDeclaredFields()
:获取所有的成员变量(不包括父类)Field getField(String name)
:获取指定名称的public修饰的成员变量Field getDeclaredField(string name)
:获取指定名称的成员变量- 设置成员变量的值:
void set(Object obj,Object value)
:参数说明:1. obj:要设置的字段所在的对象;2. value:要为字段设置的值;
- 获取值:
get(object obj)
Student类:
package fanshe.field;
public class Student {
public Student(){
}
//**********字段*************//
public String name;
protected int age;
char sex;
private String phoneNum;
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", sex=" + sex
+ ", phoneNum=" + phoneNum + "]";
}
}
测试类:
package fanshe.field;
import java.lang.reflect.Field;
public class Fields {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.field.Student");
//2.获取字段
System.out.println("************获取所有公有的字段********************");
Field[] fieldArray = stuClass.getFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("************获取所有的字段(包括私有、受保护、默认的)********************");
fieldArray = stuClass.getDeclaredFields();
for(Field f : fieldArray){
System.out.println(f);
}
System.out.println("*************获取公有字段**并调用***********************************");
Field f = stuClass.getField("name");
System.out.println(f);
//获取一个对象
Object obj = stuClass.getConstructor().newInstance();//产生Student对象--》Student stu = new Student();
//为字段设置值
f.set(obj, "刘德华");//为Student对象中的name属性赋值--》stu.name = "刘德华"
//验证
Student stu = (Student)obj;
System.out.println("验证姓名:" + stu.name);
System.out.println("**************获取私有字段****并调用********************************");
f = stuClass.getDeclaredField("phoneNum");
System.out.println(f);
f.setAccessible(true);//暴力反射,解除私有限定
f.set(obj, "18888889999");
System.out.println("验证电话:" + stu);
}
}
输出结果:
************获取所有公有的字段********************
public java.lang.String fanshe.field.Student.name
************获取所有的字段(包括私有、受保护、默认的)********************
public java.lang.String fanshe.field.Student.name
protected int fanshe.field.Student.age
char fanshe.field.Student.sex
private java.lang.String fanshe.field.Student.phoneNum
*************获取公有字段**并调用***********************************
public java.lang.String fanshe.field.Student.name
验证姓名:刘德华
**************获取私有字段****并调用********************************
private java.lang.String fanshe.field.Student.phoneNum
验证电话:Student [name=刘德华, age=0, sex=
5.4 获取成员方法并调用
-
public Method[] getMethods()
:获取所有public修饰的方法"(包含父类的) -
public Method[] getDeclaredMethods()
:获取所有的成员方法,包括私有的(不包括父类的) -
public Method getMethod(String name,Class<?>... parameterTypes)
:获取指定名称的公共方法(包括父类) -
public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
:获取指定名称的方法(不包括父类)参数说明:1. name : 方法名;2. Class … : 形参的Class类型对象
-
调用方法:
public Object invoke(Object obj, Object... args):
参数说明:1. obj : 要调用方法的对象;2. args:调用方式时所传递的实参;
student类:
package fanshe.method;
public class Student {
//**************成员方法***************//
public void show1(String s){
System.out.println("调用了:公有的,String参数的show1(): s = " + s);
}
protected void show2(){
System.out.println("调用了:受保护的,无参的show2()");
}
void show3(){
System.out.println("调用了:默认的,无参的show3()");
}
private String show4(int age){
System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
return "abcd";
}
}
测试类:
package fanshe.method;
import java.lang.reflect.Method;
public class MethodClass {
public static void main(String[] args) throws Exception {
//1.获取Class对象
Class stuClass = Class.forName("fanshe.method.Student");
//2.获取所有公有方法
System.out.println("***************获取所有的”公有“方法*******************");
stuClass.getMethods();
Method[] methodArray = stuClass.getMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取所有的方法,包括私有的*******************");
methodArray = stuClass.getDeclaredMethods();
for(Method m : methodArray){
System.out.println(m);
}
System.out.println("***************获取公有的show1()方法*******************");
Method m = stuClass.getMethod("show1", String.class);
System.out.println(m);
//实例化一个Student对象
Object obj = stuClass.getConstructor().newInstance();
m.invoke(obj, "刘德华");
System.out.println("***************获取私有的show4()方法******************");
m = stuClass.getDeclaredMethod("show4", int.class);
System.out.println(m);
m.setAccessible(true);//解除私有限定
Object result = m.invoke(obj, 20);//需要两个参数,一个是要调用的对象(获取有反射),一个是实参
System.out.println("返回值:" + result);
}
}
输出结果:
***************获取所有的”公有“方法*******************
public void fanshe.method.Student.show1(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
***************获取所有的方法,包括私有的*******************
public void fanshe.method.Student.show1(java.lang.String)
private java.lang.String fanshe.method.Student.show4(int)
protected void fanshe.method.Student.show2()
void fanshe.method.Student.show3()
***************获取公有的show1()方法*******************
public void fanshe.method.Student.show1(java.lang.String)
调用了:公有的,String参数的show1(): s = 刘德华
***************获取私有的show4()方法******************
private java.lang.String fanshe.method.Student.show4(int)
调用了,私有的,并且有返回值的,int参数的show4(): age = 20
返回值:abcd