动态语言
动态语言,是指程序在运行时可以改变其结构:新的函数可以引进,已有的函数可以被删除等结构上的变化。比如常见的JavaScript 就是动态语言,除此之外Ruby,Python 等也属于动态语言,而C、C++则不属于动态语言。从反射角度说JAVA 属于半动态语言。
引入反射的目的
关于java语言中为什么要引入反射,有以下3个原因:
- 提高编程的灵活性,java代码在执行时,需要知道这个类的所有属性及方法,增加注解,事务回滚,反射就可以解决。
- 赋予jvm动态编译的能力,例如热加载,Tomcat的classLoader。
反射机制概念
- 反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。
- 反射可以在一个类运行的时候获取类的信息的机制,可以获取在编译期不可能获得的类的信息。
- 对于任意一个对象,都能调用它的任意一个方法和属性。
- 因为类的信息是保存在Class对象中的,而这个Class对象是在程序运行时被类加载器(ClassLoader)动态加载的。
- 当类加载器装载运行了类后,动态获取Class对象的信息以及动态操作Class对象的属性和方法的功能称为Java语音的反射机制。
反射的应用场景
编译时类型和运行时类型
在Java 程序中许多对象在运行是都会出现两种类型:编译时类型和运行时类型。 编译时的类型由声明对象时实用的类型来决定,运行时的类型由实际赋值给对象的类型决定 。如:
Person p = new Student();
其中编译时类型为Person,运行时类型为Student。
某些从外部传入的对象,在编译期时类型为Object,但是在运行时,需要获取该对象和类的真实信息,比如获取该类的某个方法。因为编译时无法预知该对象和类属于哪个类,程序只能依靠运行时信息来预测该对象和类的真实信息,此时就必须使用反射了。
Java 反射API
通过Java反射可以获取JVM中的类、接口或对象的信息。
Class —— 类的创建
1. 获取Class对象的方法
// 会让ClassLoader装载类,并进行
Class c1 = Class.forName("com.mxm.Reflect");
// 返回类对象运行时真正所指的对象、所属类型
Class c2 = Reflect.class;
// ClassLoader装载入内存,不对类
Class c3 = new Reflect().getClass();
- 无参数创建对象
//newInstance式使用类的加载机制
Class c4 = Class.forName("com.mxm.Reflect");Object o = c4.newInstance();
- 有参数创建对象
// Class对象所表示的类的指定的公共构造 , getConstructor方法返回了一个Constructor对象,它反映了此
Constructor<?> csr = c4.getConstructor(String.class,,int.class);
Object o = csr.newInstance("王",28);
Constructor —— 反射类中构造方法
Field —— 反射方法
- 获取属性
Field field = class.getDeclaredField("name");使用setAccseeible取消封装,特别是可以取消私有字段的访问权限。field.setAccessible(true);
field.set(object,"老王");
- Field类描述
Field类描述的是属性对象,其中可以获取到很多属性信息,包括名字、属性类型、属性的注解。Java.lang.reflect包中的类,表示类的成员变量,可以用来获取和设置类之中的属性值。
- 安全管理
在安全管理器中会使用checkPermission方法来检查权限 ,而setAccessible(true)并不是将方法的权限改为public,而是取消Java的权限控制检查,所以即使是public方法,其accessible属性默认也是false,就是说也要做权限检查的。
- 修改属性中的修饰符
Field field = class.getDeclaredField("name");
String prive = Modeifier.toString(field.getModofoers());
Method —— 反射方法
Method m = class.getDeclaredMethod("setName",String.class);
m.setAccessible(true); //同样需要忽略访问权限的限制
m.invoke(class,"老王");
Modifier —— 访问修饰符的信息
反射使用步骤(获取Class 对象,调用对象方法)
- 获取想要操作的类的Class 对象,他是反射的核心,通过Class 对象我们可以任意调用类的方法。
- 调用Class 类中的方法,既就是反射的使用阶段。
- 使用反射API 来操作这些信息。
获取Class 对象的3 种方法
- 调用某个对象的getClass()方法。
Person p=new Person();
Class clazz=p.getClass();
- 调用某个类的class属性来获取该类对应的Class对象
Class clazz=Person.class;
- 使用Class类中的forName()静态方法(最安全/性能最好)
Class clazz=Class.forName("类的全路径"); (最常用)
当我们获得了想要操作的类的Class 对象后,可以通过Class 类中的方法获取并查看该类中的方法和属性。
//获取Person 类的Class 对象
Class clazz=Class.forName("reflection.Person");
//获取Person 类的所有方法信息
Method[] method=clazz.getDeclaredMethods();
for(Method m:method) {
System.out.println(m.toString());
}
//获取Person 类的所有成员属性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field) {
System.out.println(f.toString());
}
//获取Person 类的所有构造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor) {
System.out.println(c.toString());
}
反射实践
- 获取不到Class。当Class.foeName()中路径获取不到对应的Class时,会抛出异常。
- 获取不到Field。(1)确实不存在这个Field,抛出异常。(2)修饰符导致的权限问题,抛出相同异常。
- 获取父类修饰符。
(1)getField只能获取对象和父类的public修饰的属性。
(2)getDeclaredField获取对象中的各种修饰符属性,但是不能获取父类的任何属性。
(3)先使用getSupperclass方法可以获取父类的suppereClass对象,再使用getDeclaredField方法获取父类的全部属性。 - 获取不到父类的非public的方法
- 获取不到父类的构造方法
- 反射静态方法
static方法因为属于类本身 , 关键是Method.invoke的第一个 , 所以不需要填写对象,填写null就可以
public class TestMethod {
static void test(){}
}
Class cla = Class.foeName("TestMethod");
Method m = cla.getDeclaredMethod("test");
m.invoke(null);
- 反射泛型参数方法
(1)Java的泛型擦除概念,泛型T在编译时会自动向上转型为Object。
public class Test<T> {
public void test(T t){}
}
Class cla = Test.class;
Method m = cla.getDeclaredMethod("test",Object.class);
m.invoke(new Test<Integer>(),1);
- 反射框架:jOOR
newInstance方法创建类对象的两种方法
Class 对象的newInstance()
- 使用Class 对象的newInstance()方法来创建该Class 对象对应类的实例,但是这种方法要求该Class 对象对应的类有默认的空构造器。
调用Constructor对象的newInstance()
- 先使用Class 对象获取指定的Constructor 对象,再调用Constructor 对象的newInstance()方法来创建 Class 对象对应类的实例,通过这种方法可以选定构造方法创建实例。
//获取Person 类的Class 对象
Class clazz=Class.forName("reflection.Person");
//使用.newInstane 方法创建对象
Person p=(Person) clazz.newInstance();
//获取构造方法并创建对象
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//创建对象并设置属性
Person p1=(Person) c.newInstance("李四","男",20);