zoukankan      html  css  js  c++  java
  • Java之反射机制初识

           前几天被问到了反射,当时没有回答出来多少,后来去看了一下,这里大概总结一下!

           首先,我们要知道反射机制,那么什么是反射呢?

           答:反射是程序可以访问、检测和修改他本身状态或行为的一种能力。那么java语言是如何支持反射的呢?别急,我们来慢慢聊。

         我们以前的学习中有遇到过Java中万事万物皆为对象之说,那么静态变量呢?还有基本数据类型的数据呢?它们也是面向对象的吗?我们都知道静态是属于类的,不是哪个类的对象的,基本数据类型是属于包装类的,那么难道类也是对象?答案就是是。Java中的每一个类都是java.lang.Class类的对象。

    一、Class类的使用

          在Java中,每一个class都有一个相应的Class对象。换句话说,就是当我们编写一个类时,编译完成后,在生成的.class文件中,就会产生一个Class对象,用于表示这个类的类型信息。也就是说,Class类的实例对象表示java应用程序运行时的类或者接口。虚拟机为每种类型管理一个独一无二的Class对象,也就是说,每个类型都有一个Class对象,运行程序时,jvm首先检查所要加载的类对应的Class对象是否已经加载,如果没有加载,jvm就会根据类名查找.class文件并将其Class对象载入。

          可以通过类名.class、类的对象.getClass()、Class.forName()三种方法获取Class对象。具体的使用如下:

    package Reflect;
    
    public class ClassRefelect {
    
        public static void main(String[] args){
            //获得Foo的类对象
            Foo foo1 = new Foo();
            /**
             * 获得Foo类对象有三种方法
             * 1.类名.class    每一个类都有一个隐含的成员变量,class
             * 2.类对象.getClass();
             * 3.Class.forName(包名+类名);
             * 下面的c1,c2,c3官网称之为“Class Type"  类类型
             */
    
            /**
             * 万事万物皆为对象,那么类也是对象,类是什么的对象呢》
             *java.lang.Class的对象
             * 该类中封装了类的相关操作
             */
            Class  c1 = Foo.class;
    
            Class c2 = foo1.getClass();
    
            Class c3 = null;
            try {
                c3 = Class.forName("Reflect.Foo");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            /**
             *那么这三个类类型是否相同呢?
             * 答案是相同,为什么呢?
             * 因为每一个类只可能有一种类类型,
             * 就比如每一个对象只有一个类一样,
             * 这个类类型是Class的实例对象
             */
    
            System.out.println(c1==c2);      //true
            System.out.println(c3==c2);      //true
        }
    }
    
    class Foo{
            public void print(){
                System.out.println("创建了Foo类的对象");
            }
    }

            可以看到上面的代码中有对三个Class对象的引用变量进行比较,答案当然是true,因为它们都是Foo类对象,而上面我们也提到了,每一个类都有一个独一无二的Class对象,所以结果应该是true.

           这里还有一个问题,我们怎么区分foo1和c1、c2等呢?我们知道foo1是类Foo的一个对象,那么就是Foo的对象,官网上对于c1,c2有一种说法,是Class Type----->类类型,也就是c1,c2是Foo的类类型,其实我们还可以这样区别,c1,c2是Foo对象,而foo1是Foo的对象。

           既然我们已经得到了Foo的类类型,里面含有Foo类的相关信息,那么我们有一个大胆的想象,能否用Foo类类型得到Foo的某个对象呢?答案是当然可以。Class对象有一个newInstance()方法,代码演示如下:

    package Reflect;
    
    public class ClassRefelect {
    
        public static void main(String[] args){
            Class  c1 = Foo.class;
       
            try {
                Foo foo2  = (Foo) c1.newInstance();
                foo2.print();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
    
    class Foo{
            public void print(){
                System.out.println("创建了Foo类的对象");
            }
    }

    这里的foo2相当于new Foo();创建出来的对象。

    下面我们来聊聊方法的反射。

    二、方法的反射

           要想了解方法的反射,我们首先要获得Class对象,才能通过Class对象获得方法的相关信息,获得Class对象的方法上面已经讲过,这里插播一条广告,上面不是提到了每种类型都有Class对象么,那么基本数据类型以及void类型有木有呢?恩,有的,看下面的代码

    package Reflect;
    
    /**
     * 基本数据类型类类型
     * 包装类类类型
     *
     */
    public class ClassType {
    
        public static void main(String[] args){
            Class c1 = int.class;                       //int的类类型
            Class c2 = String.class;                    //String类类型
            Class c3 = Double.class;                    //Double包装类类型
            Class c4 = double.class;                    //double类类型
            Class c5 = void.class;                      //void 的类类型
            System.out.println(c1.getName());            //int
            System.out.println(c2.getName());            //java.lang.String
            System.out.println(c2.getSimpleName());      //String
            System.out.println(c3.getName());            //java.lang.Double
            System.out.println(c4.getName());            //double
            System.out.println(c3.getSimpleName());      //Double
            System.out.println(c5.getName());            //void
        }
    }

          既然可以拿到Class对象,那么获取方法的一系列信息就不成问题了。

          方法也是对象,是Method类的对象,该类中封装了方法的一系列操作,该类是java.lang.reflect包下的类。

         这里介绍几个方法,后面会用到

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

     1.getMethods()                                          //获得一个类中的所有public修饰的方法对象,包括继承父类的方法

     2.getDeclaredMethods()                           //获得一个类中的所有自己声明的方法对象,不问访问权限,不包括父类的

     3.getName()                                             //获得调用者的名称

     4.getReturnType()                                    //获得方法的返回值类类型,即如果返回值是int,那么返回的是int.class

     5.getParameterTypes()                            //获得参数列表的类类型

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------

    package Reflect;
    
    import java.lang.reflect.Method;
    
    public class ClassUtil {
    
        public static void printMessage(Object obj){
    
            //获得类的类类型
    
            /**
             * 如果传入的参数是Object类型,
             * 则或取的就是Object的类类型,
             * 如果是其子类,则获取的就是其子类的类类型
             */
            Class c1 = obj.getClass();
    
            //或取类的名称
            System.out.println("类的名称是:"+c1.getName());
    
            //获取类中的方法
        
            //获取类中的方法
            Method[] ms = c1.getMethods();
            for(int i=0;i<ms.length;i++){
                //获取方法的返回值类型
                Class returnType = ms[i].getReturnType();
                System.out.print(returnType.getName()+" ");
                //获取方法的名称
                System.out.print(ms[i].getName()+"(");
                //获取参数类型----》获取的是参数列表的类类型
                Class[] parameterType = ms[i].getParameterTypes();
                for (Class class1:parameterType) {
                    System.out.print(class1.getName()+",");
    
                }
                System.out.println(")");
            }
        }
     

    这里传入的如果是Object类型的对象,那么就会获取Object类的方法信息,如果是Object类的子类,那么获取的就是
    该子类的方法信息。

    这里我们测试一下:

    package Reflect;
    
    public class TestClassUtil {
        public static void main(String[] args){
     
            Integer s = 1;
            ClassUtil.printMessage(s);
        
        }
    }

           从这里我们可以看到打印出了Integer类的所有的public方法的信息。

           那么什么是方法的反射呢?方法的反射是什么样的呢?

           平时我们使用方法的步骤是什么呢?是不是先获取一个类的对象(如果是实例方法),然后对象.方法()对吗?那么方法反射恰好相反,是利用方法对象操作类的对象的。

           方法的反射也有几个步骤:

           0.获取类类型  

           1.通过类类型获取某个方法的方法对象 

      2.方法对象.invoke(类的对象,参数列表)

           invoke()方法API文档上时这样讲的:“对带有指定参数的指定对象调用由此 Method 对象表示的底层方法”,通俗的说就是可以利用invoke()方法进行反射。

    下面列出反射中需要的方法:

    --------------------------------------------------------------------------------------------------------------------------------------------------------------------

    ****说明一下:以下提到的c是Class对象,某个类的类类型,method是1,2方法返回的方法对象**** 

    1.getMethod("Method name","Parameter Type")                           //方法名称和参数列表可以唯一确定一个方法,注意这个方法  只能获取类中public 声明的方法,包括继承自父类的方法

    用法举例:public void print(int a,int b){}

                      c.getMethod(“print",int.class,int.class)  或者  c.getMethod("print",new Class[]{int.class,int.class})

    2.getDeclaredMethod("Method name","Parameter Type")               //获取类中自己声明的方法,不问权限,不包括继承自父类的方法

    用法举例:public void print(int a,int b){}

                      c.getDeclaredMethod(“print",int.class,int.class)  或者  c.getDeclaredMethod("print",new Class[]{int.class,int.class})

     以上两个方法都是获取方法对象的

    3.invoke(Object obj,args)                                             //对带有args参数的obj对象调用由此Method对象表示的底层方法

    用法举例:class A{

            public void print(int a,int b){}

          }

                     method.invoke(new A(),10,20)  或者  method.invoke(new A(), new Object[]{10,20});

    invoke方法返回值是null,或者是Object或者Object的子类,当是Object时不存在问题,如果(Object的子类)想要具体的返回值类型,那么必须进行强制类型转换,如

     Object o = method.invoke(a1,10,10);

     Integer i   = (Integer)method.invoke(a1,10,10);

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

    接下来我们看具体的代码:

    package Reflect;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    public class MethodRefelect {
        public static void main(String[] args){
            /**
             * 1.要想获取一个方法的信息,首先需要获取类类型
             * 2.获取了类类型后通过类类型得到方法对象
             * 3.通过方法对象可以反射操作方法
             */
    
            A a  = new A();
            Class c = a.getClass();
            try {
                Method m = c.getMethod("print",new Class[]{int.class,int.class});
                try {
                 //   Object o = m.invoke(a,new Object[]{10,20});
                    Integer o = (Integer)m.invoke(a,10,20);
                    System.out.println(o);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
    
            try {
                Method m2 = c.getDeclaredMethod("print",String.class,String.class);
                try {
                    Object o = m2.invoke(a,new Object[]{"Hello","ninhao"});
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
    
            try {
              //  Method m3 = c.getDeclaredMethod("print",new Class[]{});
                Method m3 = c.getDeclaredMethod("print");
                try {
                   // Object o = m3.invoke(a,new Object[]{});
                    Object o = m3.invoke(a);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
        }
    }
    
    class A{
    
        public void print(){
            System.out.println("hello");
        }
        public int print(int a,int b){
            System.out.println(a+b);
            return a+b;
        }
    
        public void print(String a,String b){
            System.out.println(a.toUpperCase()+","+b.toLowerCase());
        }
    }

      方法的反射讲完后,我们说一下Field的反射操作

    三、Field的反射

      在讲Field的反射之前,先来了解一下如何通过Class对象获取某个类的Field的信息,如果需要获取Field的信息,首先需要获得类中所有的Filed,然后依次获得每一个Field的数据类型和名称。

      接下来列出获得Field的信息的方法

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

    1.getDeclaredFields()                  //获得一个类中所声明的所有的Filed,返回值是一个数组

    2.getType()                                  //获得调用者的类型

    ----------------------------------------------------------------------------------------------------------------------------------------------------------------------

      代码相对来说简单,如下:

    public static void printFiled(Object obj) {
            
            Class c1 = obj.getClass();
           Field[] fs
    = c1.getDeclaredFields(); //自己类中声明的所有的成员变量 for(Field filed: fs){ //根据每一个成员变量获取成员变量的类型 Class returnType = filed.getType(); String returnName = returnType.getName(); //返回每一个成员变量的名称 String FiledName = filed.getName(); System.out.println(returnName+" "+FiledName); } }
    package Reflect;
    
    public class TestClassUtil {
        public static void main(String[] args){
    
            Integer s = 1;
            ClassUtil.printFiled(s);
         
        }
    }

        测试了一下,结果中列出了Integer类的Field.[C这个是数组类型的反射,这里不做详述。

        接着到了我们Field的反射表演的时间了。

      Field的反射操作步骤如下:

           0.获得类类型

           1.通过类类型获得某个Field

     1.5.一般,Field是private,所以需要setAccessible(true),大概的意思是可以操作private的数据

      2.对获得的Field进行相关操作

      补充一下:Field(成员变量)也是面向对象的,它是java.lang.reflect.Filed的实例对象

           具体的实践如下:

    package Reflect;
    
    import java.lang.reflect.Field;
    public class FieldReflect {
        public static void main(String[] args){
            B b = new B();
            Class c = b.getClass();
            //首先获取成员变量对象
            try {
                /**
                 * 这里的getField之所以会出错,
                 * 是因为getField获取的是public的,
                 * 而之前我并没有指定a的访问权限
                 */
               // Field f = c.getField("a");
                Field f = c.getDeclaredField("a");     //通过getDeclaredField("Field name");获得该属性对象
                try {
                    f.setAccessible(true);             //如果不设置该标志,将不能访问私有成员
                    f.set(b,12);                       //平时我们都是b.setF(xxx);这里通过Field对象f反向操作B类的对象b
                    System.out.println(f.get(b));      //12
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
    
    
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            }
        }
    }
    
    class B{
    
        private int a;
        private int b;
    
        public void setA(int a) {
            this.a = a;
        }
    
        public void setB(int b) {
            this.b = b;
        }
    
        public int getA() {
            return a;
        }
        public int getB() {
            return b;
        }
    }

    四、构造方法的反射

      首先我们还是来获取构造方法的信息,构造方法的获取所需要的方法同上面的普通方法、成员变量大概相似,这里不做赘述。直接看代码即可理解:

    public static void printConMessage(Object obj){
            //获得类类型
            Class c1 = obj.getClass();
            //获得类中的自己声明的构造方法
            Constructor[] constructors = c1.getDeclaredConstructors();
            for(Constructor constructor:constructors){
                //获得构造方法的名称
                System.out.print(constructor.getName()+"(");
                //获得构造方法的参数列表中的参数类型
                Class[] parameterTypes = constructor.getParameterTypes();
                //打印出每一个构造方法的参数
                for (Class para: parameterTypes) {
                    System.out.print(para.getName()+",");
                }
                System.out.println(")");
            }
        }
    package Reflect;
    
    public class TestClassUtil {
        public static void main(String[] args){
         
            Integer s = 1;
           ClassUtil.printConMessage(s);
        }
    }

           构造方法也是对象,是java.lang.reflect.Constructor的实例对象

          反射所需要的步骤:

         1.根据类类型获得构造方法对象

         2.通过构造方法对象创建该类的对象

    下面的是构造方法的反射的代码:

    package Reflect;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public class ConRefelect {
        public static void main(String[] args){
            //构造方法的反射操作
            C c = new C();
            Class c1 = c.getClass();
            //获得构造方法
            try {
                Constructor constructor1 = c1.getConstructor();
                Constructor constructor2 = c1.getConstructor(String.class);
    
                try {
                    /**
                     * 类类型.newInstance()可以创建一个类对象
                     * 类的构造对象.newInstance()也可以创建一个类对象
                     * 这个的意思是
                     */
                    C o = (C)constructor1.newInstance();
          C q = (C)constructor2.newInstance("hello");
                    q.print();
                    o.print();
              //      System.out.println(c1==constructor1);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
    
                System.out.println(constructor1.getName());
                System.out.println(constructor2.getName());
    
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
    
        }
    }
    
    class C{
        public String a;
        public C(){
    
        }
        public C(String a){
            this.a = a;
        }
    
        public void print(){
            System.out.println("构造方法被反射了");
        }
    }

    通过反射我们还可以查看泛型的本质原理:

    五、通过反射了解泛型的本质

          泛型指的是集合中的数据只能输入指定的数据类型的数据。如下所示:

          ArrayList<String> list = new ArrayList<String>();

          这个list中只能输入String 类型的数据,如果输入其他的不兼容的就会报错。

    那么现在我们通过反射了解反射:

    package Reflect;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.ArrayList;
    
    public class TestGeneric {
        public static void main(String[] args){
    
            ArrayList list = new ArrayList();
    
            ArrayList<String> list1 = new ArrayList<String>();
            list1.add("hello");
            // list1.add(20);
            //下面通过反射来判断泛型的本质
    
            Class c = list1.getClass();
            try {
                Method m = c.getMethod("add",new Class[]{Object.class});
                try {
                    Object o = m.invoke(list1,20);
                    System.out.println(list1.size());           //2
                    System.out.println(list1);                  [hello,20]
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            }
    
        }
    }

           上面的代码中刚开始时在list1中插入20时编译报错,接着我们通过反射获得方法对象,操作集合的add方法,将20成功插入了集合中。为什么呢?因为反射是运行时进行的,所以上面我们绕过了编译将20成功插入了集中由此可得出泛型实际上是在编译时设置了“路障”,在编译后会驱泛型化,它的存在只是为了放置错误的输入,只在编译时有效,绕过编译则无效。

      反射大概就总结这些,接下来我们小小的聊聊动态加载类。

    六、动态加载类

      类的加载分为静态加载和动态加载,静态加载指的是编译时加载,动态加载指的是运行时加载。

      new 创建对象时是静态加载类,在编译时刻就需要将所需要的所有的类加载进来。这样有一个问题就是,当我们有一个功能类,我的功能还不是很完整,但是我需要提前搭建起来框架,但是编译时发现有一个现在不使用的类不存在,所以我现在这个功能类则编译不通过。具体的代码演示如下:

    public class test{
        public static void main(String[] args){
                Word word = new Word();
                word.start();
        
               Excel excel = new Excel();
               excel.start();
     }
    
    class Word{
        void start(){};
    }

          如上所示,当我有一个Word类时,我就想要编译运行这个test类,测试一下我的Word类是否是好的,但是现在没有Excel类,所以无法检测,这就是高耦合,依赖性太强,这在工程中是不会出现的,我们必须使得我们的代码低耦合,高内聚才能符合软件工程的要求。

      那么如何改善呢?这个本质的原因还是因为类是静态加载,如果改为动态加载,那么就不会出现这个问题,我只需要在动态加载时发现导入我需要的类即可。

    package Reflect;
    
    public class TestLoose {
        public static void main(String[] args){
            try {
                Class a  = Class.forName(args[0]);     //动态加载类,在编译时根本不会出现问题,只有
    
                try {
                    Able able  = (Able)a.newInstance();
                    able.start();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    }
    
    interface   Able{
          public void start();
    }
    
    class Word implements Able{
        public void start(){}
    
    }

      上面的代码就是一个低耦合,高内聚的代码规范,如何说呢。我们可以从编译、运行两部分来说明,首先在编译时TestLoose是不会出错的,因为我们有一个Able的接口存在,所以编译通过;当运行时我们有Word类,所以我们在运行时输入Word也是不会报错的,我们输入Excel才会出错,这样就会解决上面的如果存在一个类而不能测的问题,如果我们后边需要或者这段代码由别的程序员来继续写,它们只需要实现Able即可,如Excel    implements Able;即可继续添加。

      这是编写代码的一种新思想,从高强大的依赖种脱身而出。

      总结:今天总结关于反射的问题,就到这里了。这里没有提到反射的数组和动态代理等关于反射更多、更复杂的东西,只是基础的知识。

  • 相关阅读:
    架构师如何才能够设计一个安全的架构
    Google Analytics实用用小技巧
    如何从Linux系统中删除用户账户
    使用C++编译器的编译流程
    JavaScript中双叹号的使用实例
    Android合并文件的三种方式代码
    自学Linux命令的四种方法
    前端工程师必备实用网站
    给 iOS App 开发者的 39 个开源的 Swift UI 库
    关于iOS项目的一本书
  • 原文地址:https://www.cnblogs.com/future-liu1121/p/7384334.html
Copyright © 2011-2022 走看看