Java中的反射机制(reflection)
1.概述
反射就是在程序运行过程中,根据内存中的类,查看类信息,创建类实例,访问字段,调用方法的技术。反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取他所有的成员变量和方法并且显示出来。许多框架例如:log4j,Servlet、SSM的底层都是用到了反射机制,所以说反射是真的很重要。
1)运行程序,启动java虚拟机JVM,JVM的类加载器会把类的字节码文件加载进内存。
2)在java中把Person,Test,String,Scanner,System,Object等所有的类抽象为Class类,即每一个类就是一个Class对象。
3)把所有的字段抽象为Field类,每一个字段就是一个Field对象;把所有方法抽象为Method类,每个方法就是一个Method对象。
4)把所有的构造方法抽象为Constructor类,每个构造方法就是一个Constructor对象,把所有修饰符抽象为Modifier类,每个修饰符就是一个Modifier对象。
5)应用程序类加载器把类的字节码加载到内存后,系统就会创建一个对应的Class对象,可以简单的把内存中的类理解为一个Class对象。
6)反射就是程序运行后,根据Class对象进行编程,即简单的理解为根据内存中的类(字节码)进行编程,编程就是创建对象,访问字段,调用方法。
Java中编译类型有两种:
- 静态编译:在编译时确定类型,绑定对象即通过。
- 动态编译:运行时确定类型,绑定对象。动态编译最大限度地发挥了Java的灵活性,体现了多态的应用,可以降低类之间的耦合性。
Java反射是Java被视为动态(或准动态)语言的一个关键性质。 这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public、static等)、superclass(例如Object)、实现interfaces(例如Cloneable),也包括fields和methods的所有信息,并可于运行时改变fields内容或唤起methods。
Reflection可以在运行时加载、探知、使用编译期间完全未知的classes。即Java程序可以加载一个运行时才得知名称的class,获取其完整构造,并生成器对象实体、或对其fields设置、或唤起其methods。
反射(reflection)允许静态语言在运行时(runtime)检查、修改程序的结构与行为。
在静态语言中,使用一个变量时,必须知道它的类型。在Java中,变量的类型信息在编译时都保存到了class文件中,这样在运行时才能保证准确无误;换句话说,程序在运行时的行为都是固定的。如果想在运行时改变,就需要反射这东西了。
2.反射的作用
1)动态创建对象。
2)动态操作属性。
3)动态调用方法。
4)解耦合,提高程序的可扩展性。
3.反射的使用——通过反射获取类的五种方式
1)第一种方式
Class.forName(“全类名”);
将字节码文件加载进内存,返回Class对象。多用于配置文件,将类名定义在配置文件中。读取配置文件,加载类。
即,通过类.Class获取
Class class1 = Class.forName("reflect中Class对象的创建.Person");
System.out.println("1:"+class1);
2)第二种方式
类名.Class
通过类名的属性class获取。多用于参数传递。
Class class2=Person.class;
System.out.println("2:"+class2);
3)第三种方式
对象.getClass()
getClass()方法在Object类中定义着。多用于对象获取字节码的方式。
Person person=new Person();
Class class3 = person.getClass();
System.out.println("3:"+class3);
4)第四种方式
Class class= Integer.TYPE 对于内置类型的包装类,可以通过 TYPE 反射
Class class4= Integer.TYPE;
System.out.println("4:"+class4);
5)第五种方式
Class class5 = class1.getSuperclass()
获取父类
Class class5 = class1.getSuperclass();
System.out.println("5:"+class5);
运行截图:
案例:
需求:声明一个Person类和Student用于完成基于五种方式来获取Class对象
资源:全类名:包名+类名,即全类名为reflect中Class对象的创建.Person
类的名字
对象的名字
目录结构如下:
Person类
package reflect中Class对象的创建;
public class Person {
private String name;
private int age;
public String a;
public String b;
public String c;
public String d;
public Person() {
}
//public reflect中Class对象的创建.Person(java.lang.String,int)
public Person(String name, int age) {
this.name = name;
this.age = age;
}
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 getA() {
return a;
}
public void setA(String a) {
this.a = a;
}
public String getB() {
return b;
}
public void setB(String b) {
this.b = b;
}
public String getC() {
return c;
}
public void setC(String c) {
this.c = c;
}
public String getD() {
return d;
}
private void setD(String d) {
this.d = d;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", a='" + a + '\'' +
", b='" + b + '\'' +
", c='" + c + '\'' +
", d='" + d + '\'' +
'}';
}
//重载方法的名称和参数
public void eat(){
System.out.println("eat......");
}
public void eat(String food){
System.out.println("eat....."+food);
}
}
学生类
package reflect中Class对象的创建;
public class Student {
public void sleep(){
System.out.println("sleep......");
}
}
反射测试类
package reflect中Class对象的创建;
public class ReflectDemo {
public static void main(String[] args) throws ClassNotFoundException {
//1. Class.forName("全类名"):将字节码文件加载进内存,返回Class对象
Class class1 = Class.forName("reflect中Class对象的创建.Person");
System.out.println("1:"+class1);
//2.类名.class:通过类名的属性class获取
Class class2=Person.class;
System.out.println("2:"+class2);
//3.对象.getClass(),getClass()方法在Object类中定义着。
Person person=new Person();
Class class3 = person.getClass();
System.out.println("3:"+class3);
//4.Class class= Integer.TYPE
Class class4= Integer.TYPE;
System.out.println("4:"+class4);
//获取父类
Class class5 = class1.getSuperclass();
System.out.println("5:"+class5);
/**
* 同一个字节码文件(*.class)在一次程序运行过程中,
* 只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
*
* 地址判断的方式验证一下 ==
*/
System.out.println(class1==class2);
System.out.println(class1==class3);
//获取Student类的Class对象
Class class6 = Student.class;
System.out.println(class1==class6);
}
}
运行结果如下:
结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个。
4.反射机制常用的类文件
Java.lang.Class;
Java.lang.reflect.Constructor;
Java.lang.reflect.Field;
Java.lang.reflect.Method;
Java.lang.reflect.Modifier;
在JDK中,主要有以下类来实现Java反射机制,这些类都位于Java.lang.reflect包下
Class类:代表一个类
Constructor类:代表类的构造方法
Field类:代表类的成员变量(属性)
Method类:代表类的成员方法
5.反射的入口Class类
除了基本的数据类型外,其他类型都是Class类
Class类是Java 反射机制的起源和入口
- 用于获取与类相关的各种信息
- 提供了获取类信息的相关方法
- Class类继承自Object类
Class类是所有类的共同的图纸
- 每个类有自己的对象,好比图纸和实物的关系
- 每个类也可看做是一个对象,有共同的图纸Class,存放类的结构信息,比如类的名字、属性、方法、构造方法、父类和接口,能够通过相应方法取出相应信息
Class类的对象称为类对象
6.Class对象功能
目录结构:
6.1获取成员变量
- Field[] getFields():获取所有public修饰的成员变量
- Field getField(String name):获取指定名称的public修饰的成员变量
- Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
- Field getDeclaredField(String name):获取指定名称的成员变量,不考虑修饰符
package Class对象功能的演示;
import reflect中Class对象的创建.Person;
import java.lang.reflect.Field;
public class ReflectDemo1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
//通过类名.class获取
Class personClass= Person.class;
System.out.println(personClass);
/**
* 一、获取公有成员变量
* * Field[] getFields() : 获取所有public修饰的成员变量
* * Field getField(String name) 获取指定名称的 public修饰的成员变量
* *
*/
Field[] fields = personClass.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("==========");
Field b = personClass.getField("b");
System.out.println(b);
System.out.println("==========");
/**
* 获取到的变量b的作用:
* 赋值
* 取值
*/
Person person=new Person();
//Object value = b.get(person);
//System.out.println("value:"+value); //null
b.set(person,"云尚"); //通过反射动态赋值
Object value = b.get(person); //取值
System.out.println("value:"+value); //云尚
/**
* 二、获取的成员变量不考虑修饰符
* *Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* *Field getDeclaredField(String name) 获取指定名称的成员变量,不考虑修饰符
*/
System.out.println("==========");
Field[] declaredFields = personClass.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
System.out.println("==========");
Field age = personClass.getDeclaredField("age");
System.out.println(age);
System.out.println("==========");
//------暴力反射
age.setAccessible(true); //需要操作私有,请求放行
Object value1 = age.get(person);
System.out.println("age:"+value1);
//异常:Class Class对象功能的演示.ReflectDemo1 can not access a member of class reflect中Class对象的创建.Person with modifiers "private"
//需要加age.setAccessible(true)方法
age.set(person,18);
Object value2 = age.get(person);
System.out.println(value2);
}
}
运行结果如下:
6.2获取构造方法
- Constructor<?>[] getConstructors()
- Constructor getConstructor(类<?>… parameterTypes)
- Constructor getDeclaredConstructor(类<?>… parameterTypes)
- Constructor<?>[] getDeclaredConstructors()
package Class对象功能的演示;
import reflect中Class对象的创建.Person;
import java.lang.reflect.Constructor;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception{
//通过类名.class获取
Class personClass= Person.class;
System.out.println(personClass);
System.out.println("=============");
//获取公有的构造方法
Constructor constructor = personClass.getDeclaredConstructor(String.class, int.class);
System.out.println(constructor);
Person person = (Person) constructor.newInstance("云尚", 18);
System.out.println(person);
}
}
6.3获取成员方法
- Method[] getMethods()
- Method getMethod(String name, 类<?>… parameterTypes)
- Method[] getDeclaredMethods()
- Method getDeclaredMethod(String name, 类<?>… parameterTypes)
package Class对象功能的演示;
import reflect中Class对象的创建.Person;
import java.lang.reflect.Method;
public class ReflectDemo3 {
public static void main(String[] args) throws Exception{
//通过类名.class获取
Class personClass= Person.class;
System.out.println(personClass);
System.out.println("=============");
//一、获取公有无参成员方法
Method eat_method=personClass.getMethod("eat");
System.out.println(eat_method); //输出结果:public void reflect中Class对象的创建.Person.eat()
//执行方法
Person person=new Person();
eat_method.invoke(person);
System.out.println("=============");
//二、获取公有有参成员方法
Method eat1_method = personClass.getMethod("eat", String.class);
System.out.println(eat1_method);
eat1_method.invoke(person,"米饭");
System.out.println("所有成员方法===没重写的都继承自Object类==========");
//获取所有的成员方法-------默认继承Object的所有方法
Method[] methods = personClass.getMethods();
for (Method method : methods) {
System.out.println(method+"——"+method.getName()); //方法的名称
}
System.out.println("私有成员方法=============");
//获取私有的成员方法
Method[] declaredMethods = personClass.getDeclaredMethods();
//获取私有的方法
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod+"——"+declaredMethod.getName());
}
System.out.println("=============");
String className = personClass.getName();
System.out.println("personClass:"+personClass);
System.out.println("className:"+className);
}
}
运行结果如下:
总结:使用反射可以赋予jvm动态编译的能力,否则类的元数据信息只能用静态编译的方式实现,例如热加载,Tomcat的classloader等等都没法支持。
7.反射的优缺点
7.1反射优点:
可以实现动态创建对象和编译,体现出很大的灵活性,特别是在J2EE的开发中它的灵活性就表现的十分明显。比如,一个大型的软件,不可能一次就把把它设计的很完美,当这个程序编译后,发布了,当发现需要更新某些功能时,我们不可能要用户把以前的卸载,再重新安装新的版本,假如这样的话,这个软件肯定是没有多少人用的。采用静态的话,需要把整个程序重新编译一次才可以实现功能的更新,而采用反射机制的话,它就可以不用卸载,只需要在运行时才动态的创建和编译,就可以实现该功能。
7.2反射缺点:
对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于只直接执行相同的操作。