美文网首页
Java -- 反射

Java -- 反射

作者: 徘徊0_ | 来源:发表于2017-10-16 14:14 被阅读0次

    一,Java 理解 JVM

    JVM -- java的虚拟机,java之所以可以跨平台就是因为它。可以把 jvm 理解成一个进程、程序,只不过他的作用是用来跑你的代码的。下图(摘自网络)是java的内存模型图,需要关注,一个方法区,一个堆(Heap),一个栈(Stack)。


    java内存模型.jpg

    例:Object o = new Object();
    这段代码运行,经历了:JVM启动,代码编译成 .class 文件,然后被类加载器加载进jvm的内存中。该类Object加载到方法区中,创建了Object类的class对象到堆中(注:这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区的数据机构的接口)。jvm 创建对象前,会检查类是否加载,寻找类对应的class对象,若加载好,就为该对象分配内存,初始化也就是代码:new Object();

    这个流程,在理解反射时,会有帮助!

    二,反射

    反射(Reflection)是Java 程序开发语言的特征之一,它允许运行中的 Java 程序获取自身的信息,并且可以操作类或对象的内部属性。

    简而言之,通过反射,我们可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。
    程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。
     反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

    Java反射框架主要提供以下功能:

    • 运行时判断任意一个对象所属的类;
    • 运行时构造任意一个类的对象;
    • 运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
    • 运行时调用任意一个对象的方法
    反射基本使用

    两个测试类

    • 父类
    public class Parent {
        //私有成员变量
        private String privateParentField;
        //公有成员变量
        public String publicParentFielde;
    
        private void parentPrivateMethod() {
            System.out.println(" Parent  : parentPrivateMethod  私有方法 ");
        }
    
        public void parentPublicMethod(String name) {
            System.out.println(" Parent  :  parentPublicMethod 公有方法" + name);
        }
    }
    
    • Child.java
    public class Child extends Parent {
    
        //父类私有成员变量
        private Parent mPrivateParentField;
    
        //私有成员变量
        private String childPriveteField;
    
        //私有成员变量
        public String childPublicField;
    
        private void childPrivateMethod() {
            System.out.println(" child  : childPrivateMethod  私有方法 ");
        }
    
        public void childPublicMethod(String name) {
            System.out.println(" child  :  childPublicMethod 公有方法" + name);
        }
    }
    

    1,Class对象获取

    反射可以用于判断任意对象所属的类,获得Class对象,构造任意一个对象以及调用一个对象。获得Class对象,有三种方法,如下:

        public static void main(String[] args) {
            // Person 的实例对象
            Person p=new Person();
            /**
             * Person 这个类,也是Class的一个实例对象
             * 任何一个类都是Class的实例对象,这个实例对象,有如下三种表示方式
             */
    
            // 方式 一,下面的方式告诉我们,任何类都有一个隐含的静态成员变量class (注:静态成员变量可以   类名. 调用
            Class c1=Person.class;
    
            // 方式 二,知道该类对象 可以通过调用 getClass();
            Class c2=p.getClass();
            /**
             * c1 c2 表示了Person类的 类类型(class type)
             * 相当于,Person类,是 Class的对象
             *
             * 而 p 是 Person的对象
             *
             * 不管c1 或  c2 都代表了Person类的类类型,一个类只可能是Class类的一个实例对象,不论用那种方式表示。
             * 故 c1 = c2
             */
    
            // 方式 三,需要完整类名,此时 c1 = c2 = c3
            try {
                Class c3=Class.forName("com.csx.Person");
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            //也可以通过 类类型,创建该类的对象
            //调用默认的**无参**构造器,如果该类没有默认无参的构造器,则会抛出异常!
            try {
                Person p2=(Person)c1.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    class Person{
    
    }
    
    2,常用方法

    1,Field、method、constructor get方法

    包含父类

    • getFields() :返回public(包含父类)的成员变量,
    • getMethods():返回public(包含父类)的方法
    • getConstructors():返回public(包含父类)的构造函数

    可以对getMethods()使用上面的测试类进行验证:

    public static void main(String[] ags) {
            Child child = new Child();
            Class cls = child.getClass();
    
            //将所有的Methods名字打印出来
            Method[] methods = cls.getMethods();
            for (Method method : methods) {
                System.out.println(" ReflectTest :getMethods()包含:" + method.getName());
            }
        }
    
    getMethods方法结果打印.png
    注:所有的类都继承自Object,所以上图才会有equals、toString等的方法;

    不包含父类

    • getDeclaredFields() :返回全部成员变量,
    • getDeclaredMethods():返回全部的方法
    • getDeclaredConstructors():返回全部的构造函数

    getDeclaredMethods()使用上面的java测试类进行测试如下:

            Child child = new Child();
            Class cls = child.getClass();
            //
            Method[] methods = cls.getDeclaredMethods();
            for (Method method : methods) {
                System.out.println(" ReflectTest :getDeclaredMethods()包含:" + method.getName());
            }
    
    getDeclaredMethods测试结果.png

    2,获取方法信息

    // 基本的数据类型,都存在类类型;如:int double Double void .....
    public static void printClassMsg(Object obj) {
            //获取类的信息,首先获得类的类类型;传递的是哪个子类的对象, c 就是该子类的类类型
            Class c=obj.getClass();
            //获取类的名称
            System.out.println("类的名称是:"+c.getName());
            /**
             * Method 类,方法对象
             * 类中的每个成员方法,就是一个Method对象
             * getMethods(); 获取的是所有public的函数,包括父类继承来的方法。
             * getDeclaredMethods(); 获取所有该类中自己声明的方法,
             */
            Method[] ms=c.getMethods();
            Method[] msDeclared=c.getDeclaredMethods();
            
            for (int i = 0; i < msDeclared.length; i++) {
                //得到返回值类型的类类型 例如:int String
                Class returnType=msDeclared[i].getReturnType();
                System.out.println("得到返回值类型的类类型:"+returnType.getName());
                
                //获取方法名称
                System.out.print(msDeclared[i].getName()+" ( ");
                        
                //获得参数类型-->得到的是参数列表的类型的类类型
                Class[] paramTypes=msDeclared[i].getParameterTypes();
                for (Class class1 : paramTypes) {
                    System.out.println();
                }
                
                System.out.println("-----------------------------------");
    
            }
        }
    
    

    3,获取成员变量的信息

    public static void printFieldMsg(Object obj) {
            Class c=obj.getClass();
            /**
             * 成员变量也是对象
             * java.lang.reflect.Field
             * Field类,封装了关于成员变量的操作
             * getFields(); 方法获取的是所有public的成员变量信息
             * getDeclaredFields(); 获取的是该类中,自己声明的成员变量的信息
             */
            Field[] fields=c.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                //得到成员变量的类型的类类型
                Class fieldType=fields[i].getType();
                String typeName=fieldType.getName();
                
                //得到变量的名称
                String fieldName=fields[i].getName();
                System.out.println("类型名称为:"+typeName+"   变量名称为:"+fieldName);
            }
            
        }
    

    4,获取对象的构造函数信息

    public static void printConstructorMsg(Object obj) {
            Class constructorClass=obj.getClass();  
            /**
             * 构造函数也是对象,
             * java.lang.Constructor 中封装了构造函数的信息
             * 
             * getConstructors 获取所有  public 的构造方法
             * 
             * getDeclaredConstructors 得到自己声明的构造方法
             */
            Constructor[] cs1=constructorClass.getConstructors();
            
            Constructor[] cs2=constructorClass.getDeclaredConstructors();
            
            for (Constructor constructor : cs2) {
                
                System.out.print(constructor.getName()+" ( ");  
                //获取构造函数 参数列表 --> 得到参数列表的类类型
                Class[] paramTypes=constructor.getParameterTypes();
                
                for (Class class1 : paramTypes) {   
                    System.out.print(class1.getName());
                }
                
                System.out.println(")");
            }
    
        }
    

    5,方法的反射

    public class ClassDemo4 {
    
        public static void main(String[] args) {
            /**
             * 要获取A类中,print(int,int)方法
             * 1,获取一个方法,就是获取类的信息,首先要获取类的类类型
             */
            A a1=new A();
            Class c=a1.getClass();//获取类类型
            /**
             * 2,获取方法  由 名称和参数列表所决定
             * getMethod(name, parameterTypes)获取的是public方法
             * getDeclaredMethod(name, parameterTypes) 获取自己声明的方法
             */
            //c.getMethod(name, parameterTypes)
            try {
                //Method m=c.getDeclaredMethod("print", new Class[] {int.class,int.class});
                //parameterTypes 也可以直接这样写
                Method m=c.getDeclaredMethod("print",int.class,int.class);
            
                /**
                 * 方法的反射操作 (正常的是  a1.print(1,1); 对象.方法调用)
                 * 
                 * 使用 m 对象,进行操作,效果跟上面的相同
                 * 方法如果没有返回值就null,有返回值返回具体返回值
                 */
                Object o=m.invoke(a1, 1,2);
                
                Method m2=c.getMethod("print", String.class,String.class);
                Object o2=m2.invoke(a1, "a","b");
                //如果没参数,可以不写 Object o3=m2.invoke(a1);
            
            } catch (Exception e) {
                e.printStackTrace();
            }
    
        }
    
    }
    
    class A{
        public void print(int a,int b) {
            System.out.println("A + B = "+(a+b));
        }
        
        public void print(String a,String b) {
            System.out.println("A , B 转换为大写为 : "+a.toUpperCase()+"  "+b.toUpperCase());
        }
    }
    

    注意:invoke方法,参数解释如下:

    public native Object invoke(Object obj, Object... args);
    obj:调用底层方法的对象(官方注释翻译)
    args:用于调用方法的参数

    6,反射(都是在运行时的操作) -- 泛型的理解

    public static void main(String[] args) {
            /**
             * 反射,理解泛型
             * 
             * 反射的操作,都是在编译之后的操作
             */
            ArrayList list1=new ArrayList();
    
            ArrayList<String> list2=new ArrayList<>();
            list2.add("s");
            //list2.add(1);//泛型string规定,只可以输入String类型
            
            Class c1=list1.getClass();
            Class c2=list2.getClass();
            System.out.println("此时规定泛型的List = 没有规定泛型的List ="+(c1==c2));
            /**
             * c1==c2 说明编译之后的集合,是去泛型
             * 
             * java中集合的泛型,是为了防止错误输入的,只在编译阶段有效。绕过编译则无效
             * 验证:通过方法的反射操作,绕过编译
             */
            try {
                //通过反射,调用add();
                Method m=c1.getMethod("add", Object.class);
                m.invoke(list2, 100);//此时在规定了String泛型的集合中,添加 100
                //此时,集合中有String int 两种类型,不能遍历(会抛出类型转换异常)
                System.out.println("泛型为String的集合,添加 100 后为:"+list2);
            } catch (Exception e) {
                // TODO: handle exception
                e.printStackTrace();
            }
    
        }
    

    8,反射中常用的 Field、Method、Constructor

    • Modifier 修饰符判断
      三个类中都包含了一个getModifiers(),返回值为int类型,可以根据该返回值判断修饰符类型(public、private等....),也可以通过 Modifier静态方法来判断,如下图
      Modifier判断修饰符静态方法.png
      也可以通过静态方法 Modifier.toString()打印出String的名称,如下图:
      Modifier.toString().png
    • Field - 用于描述类的 成员变量 也叫做域
      Field中的 getType()方法,返回值为:成员变量的Class的所属类型。
    /**
         * Returns a {@code Class} object that identifies the
         * declared type for the field represented by this
         * {@code Field} object.
         *
         * @return a {@code Class} object identifying the declared
         * type of the field represented by this object
         */
        public Class<?> getType() {
            return type;
        }
    

    9,Field的 get和set方法

    • public Object get(Object obj):参数obj为对象,表示的字段的值来自那个对象,例如:
    • public void set(Object obj, Object value):参数obj为成员变量所属的对象;value为给成员变量设置的值
      如下图示例,得到Child.java中的私有成员变量childPrivateField,调用set方法将其赋值为"haha" ,再通过get方法打印出来,结果如下:
    Field的 get和set方法.png

    相关文章

      网友评论

          本文标题:Java -- 反射

          本文链接:https://www.haomeiwen.com/subject/gtizyxtx.html