java反射机制

                                                                          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构造。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值