目录
Java高级:反射
Java反射机制概述
反射是被视为动态语言的关键,反射机制允许程序在执行期借助于Relfection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。
动态语言:一类在运行时可以改变其结构的语言:例如新的函数、对象甚至代码可以被引进,已有的函数可以被删除或是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件改变自身结构
主要动态语言:Object-C、C#、JavaScript、PHP、Python、Erlang。
静态语言:与动态语言相对应,运行时结构不可改变的语言就是静态语言,如Java、C、C++。
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象,这个类就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个类就像一面镜子,透过这个镜子看到类的结构,所以我们形象的称之为反射。
Java反射机制研究及应用
Java反射机制提供的功能
- 在运行时判断任意一个对象所属的类
- 在运行时判断任意一个类所具有的成员变量和方法
- 在运行时获取泛型信息
- 在运行时调用任意一个对象的成员变量和方法
- 在运行时处理注解
- 生成动态代理
目前看起来很抽象,我将通过实例来举例。
先创建一个实体类Person,这个Person类有一个私有的name属性和一个公开的age属性,有参构造器,无参构造器,私有的带有一个参数的构造器,一个普通的方法,一个私有的方法。
package com.claw.pojo;
/**
* 一个Person类
*
* @author Claw
*/
public class Person {
// 属性
private String name;
public Integer age;
/**
* 无参构造器
*/
public Person() {
}
/**
* 有参构造器
*
* @param name
* @param age
*/
public Person(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 私有的带有一个参数的构造器
*
* @param name
*/
private Person(String name) {
this.name = name;
}
/**
* toString方法
*
* @return
*/
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
/**
* 一个普通的方法
*/
public void show() {
System.out.println("你好,我是人");
}
/**
* 一个私有的有返回值的方法
*
* @param nation
* @return
*/
private String whereNation(String nation) {
System.out.println("国籍是:" + nation);
return nation;
}
}
那么在使用反射之前,Person类可以做到的事情:
- 实例化Person对象
- 通过对象调用内部属性
- 通过对象调用普通方法
实例(一):感受反射之前Person类可以做的事情,实例化Person对象,调用对象的内部属性,但只能调用公开的age属性,调用方法可只能调用普通方法。在Person类外部,不可以通过Person类对象调用其内部私有结构 比如name,whereNation()及私有的构造器
/**
* 反射之前Pserson能做的事情
*/
@Test
public void reflectTest1() {
// Person类的实例化
Person p1 = new Person("小蟹", 16);
// 通过对象,调用其内部属性方法
p1.age = 10;
System.out.println(p1.toString());
p1.show();
}
实例(二):看看反射能做什么
通过这个例子,我们可以看到通过反射能够实例化对象,调用普通属性和方法,也能调用私有属性私有方法以及私有构造器。
/**
* 通过反射能够做的事情:
*
* @throws Exception
*/
@Test
public void reflectTest2() throws Exception {
// 得到Class
Class<Person> personClass = Person.class;
// 通过反射得到构造器 他需要构造器的参数类型作为参数 Person类的构造器的参数是String和Integer类型
Constructor<Person> cons1 = personClass.getConstructor(String.class, Integer.class);
// 通过构造器实例化对象
Person person = cons1.newInstance("claw",10);
// 查看结果
System.out.println(person);
// 调用公有属性 getDeclaredField方法需要传入一个String类型的参数 也就是属性名 在Person里 age是公有的
Field age = personClass.getDeclaredField("age");
// 设置属性
age.set(person,11);
System.out.println(person);
// 调用私有构造器 使用的是 getDeclaredConstructor
Constructor<Person> cons2 = personClass.getDeclaredConstructor(String.class);
cons2.setAccessible(true);
Person person2 = cons2.newInstance("privateConstructor");
System.out.println(person2);
// 调用私有属性
Field name = personClass.getDeclaredField("name");
name.setAccessible(true);
name.set(person2,"private name");
System.out.println(person2);
// 调用私有方法
Method whereNation = personClass.getDeclaredMethod("whereNation", String.class);
whereNation.setAccessible(true);
whereNation.invoke(person2,"中国");
}
可以看到通过Person.class得到的 perosnClass对象是反射的源头。
我们首先要理解一下 java.lang.Class类
类的加载过程:
程序经过java.exe命令以后,会生成一个或者多个字节码文件(.class结尾) -----------编译
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于某个字节码文件加载到内存中,此过程就称为类的加载。 ----加载
加载到内存中的类,我们就称为运行时类,此运行时类,就作为一个Class实例
(拿Person类举例 person.class 就是Class的实例)
(类本身就是对象)
换句话说,Class实例就对应着一个运行时类
那么如何获取Class实例呢?
有四种方式:
- 调用运行时类的属性 Class
- 调用运行时类的对象,调用getClass()
- 调用Class的静态方法,forName() ------使用频率更高
- 使用类的加载器 ClassLoader
/**
* 获取Class实例
*/
@Test
public void getClassTest() throws ClassNotFoundException {
// 调用运行时的属性 .class
Class<Person> clazz1 = Person.class;
// 调用运行时类的对象,调用getClass()
System.out.println(clazz1);
// 1.得到一个运行时类对象
Person person = new Person();
// 2.调用getClass();
Class<? extends Person> clazz2 = person.getClass();
System.out.println(clazz2);
// 调用Class的静态方法:forName(String classPath)
Class<?> clazz3 = clazz3 = Class.forName("com.claw.pojo.Person");
System.out.println(clazz3);
// 使用类加载器 :ClassLoader
// 1.得到当前类的类加载器
ClassLoader classLoader = ReflectTest2.class.getClassLoader();
// 2.通过类的加载器加载对象
Class<?> clazz4 = classLoader.loadClass("com.claw.pojo.Person");
System.out.println(clazz4);
System.out.println(clazz1 == clazz2);
System.out.println(clazz2 == clazz3);
System.out.println(clazz3 == clazz4);
}
结果:可以看到不管通过什么方式来获取Class对象,他们都指向的同一个地址值。加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式来获取此运行时类。
class com.claw.pojo.Person
class com.claw.pojo.Person
class com.claw.pojo.Person
class com.claw.pojo.Person
true
true
true
类的加载过程
当程序主动使用某个类时,如果该类还未被加载进内存中,则系统会通过如下三个步骤来进行类的初始化
加载:将某个class文件字节码内容加载到内存中,并将些静态数据转换为方法区的运行时数据结构,然后生成一个一个代表这个类的java.lang.Class对象,作为方法区中类的数据访问入口,也就是引用地址。所有需要访问和使用类数据只能通过Class对象。这个加载的过程需要类加载器参与
链接:将Java类的二进制代码合并到JVM运行中的过程
- 验证:确保加载的类信息符合JVM规范,例如:以cafe开头,没有安全方面的问题
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存 都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程
初始化:
- 执行类构造器<clinit>()方法的过程。类构造器<clinit>()方法是由编译期自动收集类中 所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信 息的,不是构造该类对象的构造器)。
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类 的初始化。
- 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
ClassLoader
通过反射创建运行时类的对象
实例(一): 通过反射创建运行时类的对象
需要注意两点:
- 运行时类必须提供空参构造器
- 空参构造器的访问权限得够,通常设置为public
直接调用newInstance()的方法目前已过时,现在是通过getDeclaredConstructor().newInstance() 来创建运行时类的对象
/**
* 通过反射创建运行时类的对象
* @throws NoSuchMethodException
* @throws IllegalAccessException
* @throws InvocationTargetException
* @throws InstantiationException
*/
@Test
public void newInstanceTest() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 得到Class对象
Class<Person> clazz = Person.class;
// 创建运行时类的对象
Person person = clazz.getDeclaredConstructor().newInstance();
System.out.println(person);
}
这也是为什么,在javaBean中要求提供一个public的空参构造器,便于通过反射,创建运行时类的对象,便于子类继承此运行时类时,调用super()时,保证父类有此构造器。
在讲述反射概念的时候,我们提到反射是被视为动态语言的关键,在这里,我们如果要创建某一个类运行时的对象,在编译的时候是确定不下来的,在运行时才知道要造哪个类的对象。
创建一个方法,根据权限定类名来创建运行时类的对象
/**
* 根据权限定类名创建运行时类的实例
*
* @param classPath
* @return
* @throws ClassNotFoundException
* @throws NoSuchMethodException
*/
public Object getInstanceByClasspath(String classPath) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> clazz = Class.forName(classPath);
return clazz.getConstructor().newInstance();
}
实例(二)体会反射的动态性 根据传入的权限定类名来创建运行时类的对象,当我们写完这个代码时,明面上看不出它会创建出谁的对象,只有运行时,我们才知道它要创建谁的对象。
/**
* 体会反射的动态性
*/
@Test
public void name() {
// 多循环几次感受结果
for (int i = 0; i < 20; i++) {
int num = new Random().nextInt(3);//0,1,2
String classPath = "";
switch (num) {
case 0:
classPath = "java.util.Date";
break;
case 1:
classPath = "java.lang.Object";
break;
case 2:
classPath = "com.claw.pojo.Person";
break;
}
try{
Object obj = getInstanceByClasspath(classPath);
System.out.println(obj);
}catch (Exception e){
}
}
}
体会反射的动态性非常重要,在使用框架时,我们会利用反射的动态性去创建实例。
获取运行时类的完整结构
当我们有一个Class实例时,就指向了方法区类的本身,类会有构造器,方法,属性,甚至注解等等都可以通过反射来获取,当然实际开发中并不会无聊到去获取类的完整结构,但是我们确实可以通过反射去得到这些东西。
为了体现它能够获取到类的结构,我们先创建 父类,接口,注解,实体类来体现这一点。
创建一个父类
/**
* 父类
* @param <T>
*/
public class Creature<T> implements Serializable {
private char gender;
public double weight;
private void breath(){
System.out.println("呼吸");
}
public void eat(){
System.out.println("吃");
}
}
创建一个接口
/**
* @author Claw
*/
public interface MyInterface {
/**
* 一个接口方法
*/
void info();
}
创建一个注解
/**
* @author Claw
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE, MODULE})
@Retention(RetentionPolicy.SOURCE)
public @interface MyAnotation {
String value() default "hello";
}
创建一个Person2类去继承父类以及实现接口,以及使用注解,这个Person2类有公共的属性,方法,构造器,也有私有的属性,方法,构造器。
/**
* 一个Person类
*
* @author Claw
*/
@MyAnotation(value = "hi")
public class Person2 extends Creature implements Comparable,MyInterface{
//私有属性
private String name;
// 默认属性
Integer age;
// 公有属性
public Integer id;
/**
* 公共的无参构造器
*/
public Person2() {
}
/**
* 公共权限的带参构造器
* @param name
*/
@MyAnotation("adc")
public Person2(String name) {
this.name = name;
}
/**
* 默认权限的构造器
* @param name
* @param age
*/
Person2(String name, Integer age) {
this.name = name;
this.age = age;
}
/**
* 重写的方法
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
return 0;
}
/**
* 重写的方法
*/
@Override
public void info() {
System.out.println("我是一个人");
}
/**
* 一个私有的有返回值的方法
*
* @param nation
* @return
*/
private String whereNation(String nation) {
System.out.println("国籍是:" + nation);
return nation;
}
/**
* 一个公有的带返回值的方法
* @param interest
* @return
*/
public String dispaly(String interest){
return interest;
}
实例(一): 使用 getFields() 获取当前运行时类及其父类中声明为public访问权限的属性
@Test
public void test1(){
Class<Person2> clazz = Person2.class;
// 获取属性 getFields() :获取当前运行时类及其父类中声明为public访问权限的属性
Field[] fields = clazz.getFields();
for (Object field:fields) {
System.out.println(field);
}
}
结果:访问权限为public的属性被遍历了出来,有子类的,也有父类的。
子类和父类中的结构:
实例(二):getDeclaredFields 获取当前运行时类钟声明的所有属性 不包含父类中声明的属性
@Test
public void test1(){
Class<Person2> clazz = Person2.class;
// getDeclaredFields:获取当前运行时类钟声明的所有属性 不包含父类中声明的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Object declaredField:declaredFields) {
System.out.println(declaredField);
}
}
结果:
private java.lang.String com.claw.pojo.Person2.name
java.lang.Integer com.claw.pojo.Person2.age
public java.lang.Integer com.claw.pojo.Person2.id
实例(三):获取属性的内部结构:修饰符,变量名,变量类型
/**
* 获取属性的权限修饰符,数据类型,变量名
*/
@Test
public void test2() {
Class<Person2> clazz = Person2.class;
// getDeclaredFields:获取当前运行时类钟声明的所有属性 不包含父类中声明的属性
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
// 1.权限修饰符
int modifiers = declaredField.getModifiers();
System.out.println(Modifier.toString(modifiers));
// 2.数据类型
Class<?> type = declaredField.getType();
System.out.println(type);
// 3.获取变量名
String name = declaredField.getName();
System.out.println(name);
}
}
实例(四):获取运行时类的方法
/**
* 获取运行时类的方法
*/
@Test
public void test3() {
Class<Person2> clazz = Person2.class;
// 获取运行时类的公共的方法以及父类的公共的方法
Method[] methods = clazz.getMethods();
for (Method method: methods) {
System.out.println(method);
}
// getDeclaredMethods :获取运行时类声明的所有方法
Method[] declaredMethods = clazz.getDeclaredMethods();
for (Method method: declaredMethods) {
System.out.println(method);
}
}
实例(五):获取运行时实现类的接口
/**
* 获取运行时实现类的接口
*/
@Test
public void test4() {
Class<Person2> clazz = Person2.class;
// getInterfaces :获取运行时类的接口
Class[] interfaces = clazz.getInterfaces();
for (Class c: interfaces) {
System.out.println(c);
}
// 获取运行时类父类的接口
Class<?>[] interfaces1 = clazz.getSuperclass().getInterfaces();
for (Class c: interfaces1) {
System.out.println(c);
}
}
实例(六)调用运行时类的构造器
/**
* 得到运行时类的构造器
*/
@Test
public void test5() {
Class<Person2> clazz = Person2.class;
// getConstructors() 得到运行时类的公共的构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor c : constructors) {
System.out.println(c);
}
// getDeclaredConstructors()
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println(c);
}
}
调用运行时类的指定结构
主要指属性、方法和构造器
实例(一):获取指定的属性
- getField():获取指定的属性,此方法要求属性的访问权限为公共的,因此不常用。
/**
* 获取指定的属性
*/
@Test
public void test6() throws Exception {
Class<Person2> clazz = Person2.class;
// 创建运行时类的对象
Person2 person2 = clazz.getConstructor().newInstance();
// 获取指定的属性: 要求运行时类中的属性声明为public
Field id = clazz.getField("id");
// 设置当前的属性 set() 参数1:指明设置哪个对象的属性 参数2:将此属性值设置为多少
id.set(person2, 1001);
// 获取当前属性的值
int pid = (int) id.get(person2);
System.out.println(pid);
}
实例(二):如何去操作运行时类指定的属性
/**
* 如何去操作运行时类指定的属性
* @throws Exception
*/
@Test
public void test7() throws Exception {
Class<Person2> clazz = Person2.class;
// 创建运行时类的对象
Person2 person2 = clazz.getConstructor().newInstance();
Field name = clazz.getDeclaredField("name");
// 保证当前属性是可访问的
name.setAccessible(true);
name.set(person2,"claw");
System.out.println(name.get(person2));
}
实例(三):去操作运行时类指定的方法
-
getDeclaredMethod():参数1:方法的名称 参数2:指明获取的方法的形参列表 invoke():调用方法 参数1:方法的调用者 参数2:给方法形参赋值的实参 此方法有返回值 为调用方法本身的返回值
/**
* 如何去操作运行时类指定的属性
* @throws Exception
*/
@Test
public void test7() throws Exception {
Class<Person2> clazz = Person2.class;
// 创建运行时类的对象
Person2 person2 = clazz.getConstructor().newInstance();
Field name = clazz.getDeclaredField("name");
// 保证当前属性是可访问的
name.setAccessible(true);
name.set(person2,"claw");
System.out.println(name.get(person2));
}
实例(四):调用运行时类的静态方法
首先需要一个静态方法
/**
* 一个静态方法
*/
private static void staticMethod(){
System.out.println("这是一个静态方法");
}
调用运行时类的静态方法
- 如果调用的运行时类中的方法没有返回值,此invoke()返回null
/**
* 如何操运行时类指定的方法
*/
@Test
public void name() throws Exception {
Class<Person2> clazz = Person2.class;
Person2 person2 = clazz.getConstructor().newInstance();
// 调用静态方法
Method staticMethod = clazz.getDeclaredMethod("staticMethod");
staticMethod.setAccessible(true);
// 如果调用的运行时类中的方法没有返回值,此invoke()返回null
Object invoke1 = staticMethod.invoke(person2);
}
反射的应用:动态代理
(此部分待完善)
使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
静态代理的特征是代理类和目标的对象的类都是在编译期间确定下来,不利于程序的拓展,同时,每一个代理类都只能为一个接口服务,这样以来程序开发中必然会产生过多的代理。最好可以通过一个代理类完成全部的代理功能。