java反射机制
一、什么是反射
1.jvm的大体工作原理
package test;
/**
* Created by on 2017/12/13.
*/
public class TestHelloWorld {
private String desc;
public TestHelloWorld(String desc) {
this.desc = desc;
}
public String toString() {
return "desc的值为" + desc;
}
public static void main(String args[]) {
TestHelloWorld test = new TestHelloWorld("beijing welcome you!");
System.out.println(test.toString());
}
}
运行上面代码,
1)首先JVM会启动,上面的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中。加载是指将编译后的java类文件(也就是.class文件)中的二进制数据读入内存,并将其放在运行时数据区的方法区内,然后再堆区创建一个Java.lang.Class对象,注意这个不是new出来的对象,而是类的类型对象,用来封装类在方法区的数据结构。即加载后最终得到的是Class对象,并且更加值得注意的是:该Java.lang.Class对象是单实例的,无论这个类创建了多少个对象,他的Class对象是唯一的。
每个Java程序执行前都必须经过编译、加载、连接、和初始化这几个阶段。
a)连接
i)验证:确保被加载的类的正确性
ii)准备:为类的静态变量分配内存,并将其初始化为默认值
iii)解析:把类中的符号引用转化为直接引用
b)初始化:为类的静态变量赋予正确的初始值
2)jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码: new TestHelloWorld("beijing welcome you!")。上面的流程就是自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,程序也停止了。
2.反射的应用场景
上面的程序对象是自己new的,程序相当于写死了给jvm去跑。假如一个服务器上突然遇到某个请求哦要用到某个类,哎呀但没加载进jvm,是不是要停下来自己写段代码,new一下,哦启动一下服务器,(脑残)!
反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载。
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。
反射机制主要提供了以下功能:
i)在运行时判断任意一个对象所属的类;
ii)在运行时构造任意一个类的对象;
iii)在运行时判断任意一个类所具有的成员变量和方法;
iv)在运行时调用任意一个对象的方法
二、Class类
Java程序在运行时,Java运行时系统一直对所有的对象进行所谓的运行时类型标识。这项信息纪录了每个对象所属的类。虚拟机通常使用运行时类型信息选准正确方法去执行,用来保存这些类型信息的类是Class类。Class类封装一个对象和接口运行时的状态,当装载类时,Class类型的对象自动创建。
Class 没有公共构造方法。Class 对象是在加载类时由Java 虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。
虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。
一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。
三、反射的应用
1.获取Class对象
package test;
public class TestRefelct {
public static void main(String args[]) {
try {
//获取Class对象
Class cls1 = Class.forName("test.Employee");//返回与给定的字符串名称相关联类或接口的Class对象
Class cls2 = Employee.class;//每个类都有class属性
Employee employee = new Employee("lulu","IT",30);
Class cls3 = employee.getClass();//每个类的对象都有getClass()方法
System.out.println("类名称----------");
System.out.println("类名称cls1=" + cls1.getName());
System.out.println("类名称cls2=" + cls2.getName());
System.out.println("类名称cls3=" + cls3.getName());
System.out.println("对象值----------");
System.out.println("employee对象的值为:"+employee.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Employee {
public String name;//职员姓名
private String department;//职员部门
private int age; //年龄
public Employee() {
}
public Employee(String name, String department, int age) {
this.name = name;
this.department = department;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
private int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "name="+name+";department="+department+";age="+age;
}
}
运行结果:
2.获取类的对象
package test;
public class TestRefelct {
public static void main(String args[]) {
try {
//获取Class对象
Class cls1 = Class.forName("test.Employee");//返回与给定的字符串名称相关联类或接口的Class对象
//获取类的对象
Employee employeeNew1 = (test.Employee)cls1.newInstance();
System.out.println("employeeNew1对象的值为:"+employeeNew1.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
employeeNew1对象的值为:name=null;department=null;age=0
3.获取类的成员变量
1)getDeclaredFields():取得类的全部属性,包括public、private和proteced,但是不包括父类的声明字段
2)getFields():获得类的所有的公共(public)的字段,包括父类的
package test;
import java.lang.reflect.Field;
public class TestRefelct {
public static void main(String args[]) {
try {
//获取Class对象
Class cls1 = Class.forName("test.Employee");//返回与给定的字符串名称相关联类或接口的Class对象
//取得类的全部属性,包括public、private和proteced,但是不包括父类的申明字段
System.out.println("declaredFields----------");
Field[] declaredFields = cls1.getDeclaredFields();
for (Field field2 : declaredFields) {
System.out.println("Employee类的属性declaredFields为"+field2 );
}
//获得类的所有的公共(public)的字段,包括父类
System.out.println("fields----------");
Field[] fields = cls1.getFields() ;
for(Field field1 :fields){
System.out.println("Employee类的属性fields为"+field1 );
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
4.获取类的成员方法
1)getDeclaredMethods():获得类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
2)getMethods():类的所有公用(public)方法,包括其继承类的公用方法
package test;
import java.lang.reflect.Method;
public class TestRefelct {
public static void main(String args[]) {
try {
//获取Class对象
Class cls1 = Class.forName("test.Employee");//返回与给定的字符串名称相关联类或接口的Class对象
//获得类或接口声明的所有方法,包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法
Method[] declaredMethods = cls1.getDeclaredMethods();
System.out.println("declaredMethods----------");
for(Method method1 : declaredMethods){
System.out.println("Employee类的declaredMethods为"+method1 );
}
//类的所有公用(public)方法,包括其继承类的公用方法
Method[] methods = cls1.getMethods();
System.out.println("methods----------");
for(Method method2:methods){
System.out.println("Employee类的methods为"+method2 );
}
} catch (Exception e) {
e.printStackTrace();
}
}
运行结果
5.综合应用
package test;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class TestRefelct {
public static void main(String args[]) {
try {
//获取Class对象
Class cls1 = Class.forName("test.Employee");//返回与给定的字符串名称相关联类或接口的Class对象
//获取类的对象
Employee employeeNew1 = (test.Employee)cls1.newInstance();
System.out.println("employeeNew1对象的值为:"+employeeNew1.toString());
//获取类的特定方法
Method setAgeMethod = cls1.getMethod("setAge",int.class);
System.out.println("setAgeMethod----------");
System.out.println("Employee类的setAgeMethod为" + setAgeMethod);
setAgeMethod.invoke(employeeNew1, 28);
System.out.println("对象值----------");
System.out.println("employeeNew1对象的值为:" + employeeNew1.toString());
//获得age属性
Field ageField = cls1.getDeclaredField( "age" ) ;
//打破封装 实际上setAccessible是启用和禁用访问安全检查的开关,并不是为true就能访问为false就不能访问
//由于JDK的安全检查耗时较多.所以通过setAccessible(true)的方式关闭安全检查就可以达到提升反射速度的目的
ageField.setAccessible( true );
//给属性赋值
ageField.set(employeeNew1,58);
System.out.println("employeeNew1对象的值为:" + employeeNew1.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:
6.获取方法的参数类型
1) Class<?>[] getParameterTypes():按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型。如果基础方法不带参数,则返回长度为 0 的数组。
2) Type[] getGenericParameterTypes():按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型的。如果基础方法不带参数,则返回长度为 0 的数组。如果形参类型是参数化类型,则为其返回的 Type 对象必须实际反映源代码中使用的实际类型参数。
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
public class TestReflectParam {
public static void main(String[] args){
try {
Class cls = Class.forName("Example");
List<String> list1 = new ArrayList<String>();
list1.add("北京");
list1.add("上海");
Example example = new Example(list1);
Method[] methods = cls.getDeclaredMethods();
if(methods != null && methods.length > 0){
for(int i=0;i<methods.length;i++){
Method tempMethod = methods[i];
if(tempMethod.getName().equals("getList")){
List<String> getList = (List<String>)tempMethod.invoke(example,null);
if(getList != null && getList.size()>0){
for(int j=0;j<getList.size();j++){
System.out.println("getList中,getList的第"+j+"个元素的值是"+ getList.get(j));
}
}
}
if(tempMethod.getName().equals("getParam")){
Class<?>[] clsArray = tempMethod.getParameterTypes();
Type[] typeArray = tempMethod.getGenericParameterTypes();
if(clsArray != null && clsArray.length>0){
for(int k=0;k<clsArray.length;k++){
System.out.println("getParameterTypes()中,第"+k+"个元素值:"+clsArray[k]);
System.out.println("getGenericParameterTypes()中,第"+k+"个元素值:"+typeArray[k]);
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Example{
private List<String> list;
public Example(List<String> list){
this.list = list;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public void getParam(List<String> listParam,int aParam,Integer bParam){
this.list = listParam;
if(listParam != null && listParam.size() >0){
for(int i=0;i<listParam.size();i++){
System.out.println("getParam()中,listParam的第"+i+"个元素的值是"+listParam.get(i));
}
}
System.out.println("getParam()中,aParam="+aParam+";bParam="+bParam);
}
}
运行结果:
7.获取方法的参数名
java的反射是不能获取方法的参数名的,需要借助第三方包javaassist
1)三方包javaassist
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
2) java代码
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;
import javassist.bytecode.MethodInfo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class TestReflectParam {
public static void main(String[] args){
try {
Class cls = Class.forName("Example");
Method[] methods = cls.getDeclaredMethods();
if(methods != null && methods.length > 0){
for(int i=0;i<methods.length;i++){
Method tempMethod = methods[i];
if(tempMethod.getName().equals("getParam")){
getParamNames(cls,"getParam");
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void getParamNames(Class<?> clazz,String methodName){
ClassPool pool = ClassPool.getDefault();
try {
CtClass ctClass = pool.get(clazz.getName());
CtMethod ctMethod = ctClass.getDeclaredMethod(methodName);
// 使用javassist的反射方法的参数名
MethodInfo methodInfo = ctMethod.getMethodInfo();
CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
LocalVariableAttribute attr = (LocalVariableAttribute) codeAttribute
.getAttribute(LocalVariableAttribute.tag);
if (attr != null) {
int len = ctMethod.getParameterTypes().length;
// 非静态的成员函数的第一个参数是this
int pos = Modifier.isStatic(ctMethod.getModifiers()) ? 0 : 1;
System.out.println("pos="+pos+";len="+len);
for (int i = 0; i < len+pos; i++) {
System.out.println("第" + i + "个参数名称为:" + attr.variableName(i + pos));
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Example{
private List<String> list;
public Example(List<String> list){
this.list = list;
}
public List<String> getList() {
return list;
}
public void setList(List<String> list) {
this.list = list;
}
public void getParam(List<String> listParam,int aParam,Integer bParam){
this.list = listParam;
if(listParam != null && listParam.size() >0){
for(int i=0;i<listParam.size();i++){
System.out.println("getParam()中,listParam的第"+i+"个元素的值是"+listParam.get(i));
}
}
System.out.println("getParam()中,aParam="+aParam+";bParam="+bParam);
}
}
运行结果:
8.getInterfaces():获取Class对象所实现的接口
package test;
import java.lang.reflect.Modifier;
public class TestRefelct {
public static void main(String args[]) {
try {
//获取Class对象
Class cls1 = Class.forName("test.Employee");//返回与给定的字符串名称相关联类或接口的Class对象
Class<?>[] classArray =cls1.getInterfaces();
for(Class cls : classArray){
System.out.println("cls="+cls+";修饰符为"+Modifier.toString(cls.getDeclaredMethod("run",null).getModifiers()));
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Employee implements RunBase{
public String name;//职员姓名
private String department;//职员部门
private int age; //年龄
public Employee() {
}
public Employee(String name, String department, int age) {
this.name = name;
this.department = department;
this.age = age;
}
public String getName() {
return name;
}
void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
private int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "name="+name+";department="+department+";age="+age;
}
public void run(){
System.out.println("run()方法被调用");
}
}
interface RunBase{
void run();
}
运行结果:
其中Modifier.toString(cls.getDeclaredMethod("run",null).getModifiers()))得到方法的修饰符,接口中的方法默认是public abstract
四、newInstance() 和 new 有什么区别
1)newInstance( )是一个方法,而new是一个关键字
2)从jvm的角度看,我们使用new的时候,这个要new的类可以没有加载。但是使用newInstance时候,就必须保证:1、这个类已经加载;2、这个类已经连接了。而完成上面两个步骤的正是class的静态方法forName()方法,这个静态方法调用了启动类加载器(就是加载javaAPI的那个加载器)。可以把new这个方式分解为两步,即,首先调用class的加载方法加载某个类,然后实例化即调用newInstance()。
3)newInstance: 弱类型。低效率。只能调用无参构造。
new: 强类型。相对高效。能调用任何public构造。