Java反射技术
概述
反射:主要是指程序可以访问,检测和修改它本身状态或行为的一种能力,并能根据自身行为的状态和结果,调整或修改应用所描述行为的状态和相关的语义。
直白的讲,大家可以把java中的反射理解为:使用一种技术在程序运行时就能去操作java对象中的属性、方法、构造方法等。
正常编程逻辑中,我们的代码都是先编译成.class再运行,在程序运行时是不能去添加一些代码或者改变一些属性的,然而利用反射技术却可以动在程序运行时动态地去改变对象中的一些代码,或者说属性。
在现有的绝大部分技术的底层,都是利用反射来实现的,可以说反射技术是运用于底层代码的高级技术,而反射的强大之处就在于它很大程度上冲击了JavaSE基础的一些内容,比如对象的创建、方法的调用、封装中的私有化,可以说正是因为有了反射这样的技术,才能以很多不可思议的方式完成功能,所以才有了各种框架以及各种功能强大的第三方工具包。
当我们获取到一个类的字节码文件后,就可以通过字节码对象操作该字节码对象所涉及到的实例。在常规的java技术中,要使用一个类我们只能通过new关键字,或者直接调用静态属性和方法的方式达到目的,也就是说如果该类不存在,则无法被被调用,所以所有的类的建立过程必须一步一步进行,而在实际开发中为了保证效率,必须要多个进度同时进行,而在其他人没有完成相应的功能创建之前使用反射动态得调用方法无疑会使得工程效率极大地提高,在这里我只讲解反射技术的基本运用,关于如何使用反射动态地调用方法以及操作属性,我会在后面即将建立的Servlet专栏中作详细的讲解
(1) 获取字节码对象
不管是操作类的什么属性或者方法,反射的必要条件是获得类的字节码对象。在JVM中,要运行java程序,首先就是要将我们的字节码文件加载到JVM中,并且在JVM中字节码文件加载到JVM中后也可以作为一个对象来使用
我将化分成类、接口、数组、基本数据类型(包括void)四种类型讲述如何获取字节码对象
①类
类获取字节码对象有三种方式
packge wltyx.nyybw
class Student{
}
//通过类名获取字节码对象
Class stu1 = Student.class;
//通过类对象获取字节码对象
Student s = new Student();
Class stu2 = s.getClass();
//通过完全限定名获取字节码对象
Class stu3 = Class.forName("wltyx.nyybw.Student");
②接口
接口不能实例化对象所有只有两种获取字节码对象的方法
packge wltyx.nyybw
interface Student(){
}
//通过类名获取字节码对象
Class stu1 = Student.class;
//通过完全限定名获取字节码对象
Class stu3 = Class.forName("wltyx.nyybw.Student");
③数组
数组不含有完全限定名所有只有通过数组类型名和数组对象获取
String[] str = new Sting[10];
//通过数组类型名获取字节码对象
Class str1 = String[].class;
//通过数组对象获取字节码对象
Class str2 = str.getClass();
④基本数据类型(包含void)
我就以void为例获取字节码对象
//通过类型名获取字节码对象
Class v = void.class;
//获取对应的包装类型获取字节码对象
Class v2 = Void.TYPE;
(2) 反射操作构造器
在Java中,要实例化一个类的对象,势必会调用到类的构造器以实现对象的创建,我在上面提到过反射技术对java基础技术的冲击,在此处可以体现为,通过反射技术你可以破坏类的封装性获取其构造器创建实例,以达到调用其中的几乎所有已存在的属性和方法(包括私有化)
(3) 反射操作字段
在上面的操作构造器中,我们通过字节码对象获取了一个包含构造器的Constructor对象,其实在java中不但含有Constructor对象还包括Field对象以及Method对象,通过指定字段获取其Field对象我们就可以在使用中动态地修改字段中存储的数据
(4) 反射操作方法
同操作字段相比,反射操作方法不能改变方法原有的结构,而只是实现方法的调用,并不会改变方法的内部逻辑
下面是关于上述四步的简单代码示例
public class Student {
@SuppressWarnings("unused")
private int l = 24;
public int c = 36;
public void c(){
System.out.println("public method");
}
@SuppressWarnings("unused")
private void l(){
System.out.println("private method");
}
public Student(){
System.out.println("public constructor");
}
@SuppressWarnings("unused")
private Student(int i){
System.out.println("private constructor");
}
}
public class GetClassObject {
@Test
public void test() throws Exception{
//获取字节码对象
Class<Student> studentClass = Student.class;
/*构造器*/
//获取类的无参构造器
Constructor<Student> constructor = studentClass.getConstructor();
//创建实例化的Student对象
Student newInstance = constructor.newInstance();
/*私有构造器*/
//获取类的私有的有参构造器
Constructor<Student> declaredConstructor = studentClass.getDeclaredConstructor(int.class);
//忽略权限
declaredConstructor.setAccessible(true);
//私有构造器创建实例化的Student对象
Student newInstance2 = declaredConstructor.newInstance(1);
/*字段*/
//获取字段
Field field = studentClass.getField("c");
//获取并打印字段内容
System.out.println("字段修改前:"+field.get(newInstance));
//设置字段内容
field.set(newInstance, 360);
//获取并打印字段内容
System.out.println("字段修改后:"+field.get(newInstance));
/*私有字段*/
//获取私有字段
Field declaredField = studentClass.getDeclaredField("l");
//忽略权限
declaredField.setAccessible(true);
//打印私有字段内容
System.out.println("私有字段修改前:"+declaredField.get(newInstance2));
//设置私有字段内容
declaredField.set(newInstance, 240);
System.out.println("私有字段修改后:"+declaredField.get(newInstance2));
/*方法*/
//获取方法
Method method = studentClass.getMethod("c");
//运行方法
method.invoke(newInstance);
/*私有方法*/
//获取方法
Method declaredMethod = studentClass.getDeclaredMethod("l");
//忽略权限
declaredMethod.setAccessible(true);
//运行私有方法
declaredMethod.invoke(newInstance2);
}
}
运行的结果为
public constructor
private constructor
字段修改前:36
字段修改后:360
私有字段修改前:24
私有字段修改后:24
public method
private method
(5) 反射操作注解
注解能够帮助开发者减少很对繁琐的代码操作,一般来说注解都是要配合用反射,如果你还不了解什么是注解可以参考我之前写的博客Java中的内置注解、元注解以及自定义注解。
其实在自定义注解之后,我们自定义的注解并不能实现太多功能,而想要同过注解减少开发过程中的代码量就必须使用反射来根据注解附加其想要扩展的功能
代码示例
/**
* 先自定义一个注解
*/
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Vip {
int value() default 0;
}
/**
* 定义一个使用自定义注解的类
*/
public class User {
@Vip(4)
public int i;
}
/**
* 根据注解赋予其新的功能
*/
import java.lang.reflect.Field;
import org.junit.Test;
public class AnnotionRedlexct {
@Test
public void run() throws NoSuchFieldException, SecurityException{
//获取字节码对象
Class<User> cla = User.class;
//获取字段
Field field = cla.getField("i");
//获取字段上面的注解
Vip annotation = field.getAnnotation(Vip.class);
switch (annotation.value()) {
case 0:
System.out.println("青铜会员");
break;
case 1:
System.out.println("白银会员");
break;
case 2:
System.out.println("黄金会员");
break;
case 3:
System.out.println("钻石会员");
break;
default:
System.out.println("废铁会员");
break;
}
}
}
运行的结果为
废铁会员