天天看点

【Java】--反射(Java)【前言】是什么为什么要使用反射适用场景是什么?实例

【前言】

最早的计算机在它们的原生汇编语言里编程,它本质上是反射,因为它是由定义编程指令作为数据。反射功能,如修改指令或分析它们是很平常的。编程迁移到更高层次的语言如C,这种实践消失了,直到带有反射特性的编程语言在它们的类型系统出现。
   Brian Cantwell Smith在1982年的博士论文中介绍了编程语言的计算反射的这种概念,并且meta-circular解释器的概念用作3-Lisp的一个组成部分。
           

是什么

定义:是在计算机程序正运行是(run time) 可以访问、检测和修改它本身状态或行为的一种能力。(维基百科)
           

为什么要使用反射

反射通常由需要检查或修改在Java虚拟机中运行的应用程序的运行时行为的能力的程序使用。这是一个比较先进的功能,只能由强调掌握语言基础的开发人员使用。考虑到这一点,反思是一种强大的技术,可以使应用程序执行否则是不可能的操作。
           
  • 可扩展性特征

    应用程序可以通过使用其全限定名称创建可扩展性对象的实例来使用外部用户定义的类。

  • 类浏览器和可视化开发环境

    类浏览器需要能够枚举类的成员。可视化开发环境可以利用反映的类型信息来帮助开发人员编写正确的代码。

  • 调试器和测试工具

    调试者需要能够在课堂上检查私人成员。测试线束可以利用反射系统地调用类上定义的可发现的集合API,以确保测试套件中的高水平代码覆盖。

适用场景是什么?

1).Java的反射机制在做基础框架的时候非常有用,有一句话这么说来着:反射机制是很多Java框架的基石。而一般应用层面很少用,不过这种东西,现在很多开源框架基本都已经给你封装好了,自己基本用不着写。典型的除了hibernate之外,还有spring也用到很多反射机制。经典的就是在xml文件或者properties里面写好了配置,然后在Java类里面解析xml或properties里面的内容,得到一个字符串,然后用反射机制,根据这个字符串获得某个类的Class实例,这样就可以动态配置一些东西,不用每一次都要在代码里面去new或者做其他的事情,以后要改的话直接改配置文件,代码维护起来就很方便了,同时有时候要适应某些需求,Java类里面不一定能直接调用另外的方法,这时候也可以通过反射机制来实现。

总的来说,自己写的很少,具体什么时候要用那要看需求,反射机制无非就是根据一个String来得到你要的实体对象,然后调用它原来的东西。但是如果是要自己写框架的话,那就会用得比较多了。

2)当你做一个软件可以安装插件的功能,你连插件的类型名称都不知道,你怎么实例化这个对象呢?因为程序是支持插件的(第三方的),在开发的时候并不知道 。所以无法在代码中 New出来 ,但反射可以,通过反射,动态加载程序集,然后读出类,检查标记之后再实例化对象,就可以获得正确的类实例。

3)在编码阶段不知道那个类名,要在运行期从配置文件读取类名, 这时候就没有办法硬编码new ClassName(),而必须用到反射才能创建这个对象.反射的目的就是为了扩展未知的应用。比如你写了一个程序,这个程序定义了一些接口,只要实现了这些接口的dll都可以作为插件来插入到这个程序中。那么怎么实现呢?就可以通过反射来实现。就是把dll加载进内存,然后通过反射的方式来调用dll中的方法。很多工厂模式就是使用的反射。

实例

要了解反射如何工作,请考虑这个简单的例子:

import java.lang.reflect.*;

   public class DumpMethods {
      public static void main(String args[])
      {
         try {
            Class c = Class.forName(args[]);
            Method m[] = c.getDeclaredMethods();
            for (int i = ; i < m.length; i++)
            System.out.println(m[i].toString());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }
           

调用:

输出为:

public java.lang.Object java.util.Stack.push(
    java.lang.Object)
   public synchronized 
     java.lang.Object java.util.Stack.pop()
   public synchronized
      java.lang.Object java.util.Stack.peek()
   public boolean java.util.Stack.empty()
   public synchronized 
     int java.util.Stack.search(java.lang.Object)
           

也就是说,列出了类java.util.Stack的方法名称,以及它们的全限定参数和返回类型。

该程序使用class.forName加载指定的类,然后调用getDeclaredMethods来检索在类中定义的方法列表。 java.lang.reflect.Method是表示单一类方法的类。

设置使用反射

反射类,如Method,在java.lang.reflect中找到。 使用这些类必须遵循三个步骤。 第一步是为要操作的类获取一个java.lang.Class对象。 java.lang.Class用于表示正在运行的Java程序中的类和接口。

获取Class对象的一种方式是:

Class c = Class.forName("java.lang.String"); 
           

获取String的Class对象。 另一种方法是使用:

Class c = int.class; 
or
  Class c = Integer.TYPE; 
           

获取基本类型的类信息。 后一种方法访问基本类型的包装器的预定义TYPE字段(例如整数)。

第二步是调用getDeclaredMethods等方法来获取该类声明的所有方法的列表。

一旦这个信息在手,那么第三步就是使用反射API来操纵信息。 例如,序列:

Class c = Class.forName("java.lang.String");
 Method m[] = c.getDeclaredMethods();      System.out.println(m[].toString());
           

将显示在String中声明的第一个方法的文本表示。

在下面的例子中,将三个步骤结合起来,展示如何使用反射来解决具体应用。

模拟运算符的实例

一旦Class信息在手,通常下一步是询问有关Class对象的基本问题。 例如,Class.isInstance方法可用于模拟instanceof运算符:

class A {}

   public class instance1 {
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("A");
            boolean b1 
              = cls.isInstance(new Integer());
            System.out.println(b1);
            boolean b2 = cls.isInstance(new A());
            System.out.println(b2);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }
           

在此示例中,创建了一个用于A的Class对象,然后检查类实例对象,以查看它们是否是A.的实例。integer(37)不是,但是new A()是。

找出一个类的方法

反思最有价值和最基本的使用之一是找出课堂中定义的方法。 为此,可以使用以下代码:

import java.lang.reflect.*;

   public class method1 {
      private int f1(
       Object p, int x) throws NullPointerException
      {
         if (p == null)
            throw new NullPointerException();
         return x;
      }

      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("method1");

            Method methlist[] 
              = cls.getDeclaredMethods();
            for (int i = ; i < methlist.length;
               i++) {  
               Method m = methlist[i];
               System.out.println("name 
                 = " + m.getName());
               System.out.println("decl class = " +
                              m.getDeclaringClass());
               Class pvec[] = m.getParameterTypes();
               for (int j = ; j < pvec.length; j++)
                  System.out.println("
                   param #" + j + " " + pvec[j]);
               Class evec[] = m.getExceptionTypes();
               for (int j = ; j < evec.length; j++)
                  System.out.println("exc #" + j 
                    + " " + evec[j]);
               System.out.println("return type = " +
                                  m.getReturnType());
               System.out.println("-----");
            }
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }
           

程序首先获取method1的类描述,然后调用getDeclaredMethods来检索Method对象列表,一个用于在类中定义的每个方法。 这些包括public,protected,package和private方法。 如果在程序中使用getMethods而不是getDeclaredMethods,那么还可以获取继承方法的信息。

一旦获得了Method对象的列表,只需显示有关参数类型,异常类型和每种方法的返回类型的信息。 这些类型中的每一种,无论它们是基本类还是类类型,都由Class描述符表示。

程序的输出是:

name = f1
   decl class = class method1
   param #0 class java.lang.Object
   param #1 int
   exc #0 class java.lang.NullPointerException
   return type = int
   -----
   name = main
   decl class = class method1
   param #0 class [Ljava.lang.String;
   return type = void
   -----
           

获取关于构造函数的信息

类似的方法用于了解类的构造函数。 例如:

import java.lang.reflect.*;

   public class constructor1 {
      public constructor1()
      {
      }

      protected constructor1(int i, double d)
      {
      }

      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("constructor1");

           Constructor ctorlist[]
               = cls.getDeclaredConstructors();
         for (int i = ; i < ctorlist.length; i++) {
               Constructor ct = ctorlist[i];
               System.out.println("name 
                 = " + ct.getName());
               System.out.println("decl class = " +
                            ct.getDeclaringClass());
               Class pvec[] = ct.getParameterTypes();
               for (int j = ; j < pvec.length; j++)
                  System.out.println("param #" 
                     + j + " " + pvec[j]);
               Class evec[] = ct.getExceptionTypes();
               for (int j = ; j < evec.length; j++)
                  System.out.println(
                    "exc #" + j + " " + evec[j]);
               System.out.println("-----");
            }
          }
          catch (Throwable e) {
             System.err.println(e);
          }
      }
   }
           

在此示例中没有检索到返回类型的信息,因为构造函数并没有真正的返回类型。

运行此程序时,输出为:

name = constructor1
   decl class = class constructor1
   -----
   name = constructor1
   decl class = class constructor1
   param #0 int
   param #1 double
   -----
           

找出类字段

还可以找出在类中定义的数据字段。 为此,可以使用以下代码:

import java.lang.reflect.*;

   public class field1 {
      private double d;
      public static final int i = ;
      String s = "testing";

      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field1");

            Field fieldlist[] 
              = cls.getDeclaredFields();
            for (int i 
              = ; i < fieldlist.length; i++) {
               Field fld = fieldlist[i];
               System.out.println("name
                  = " + fld.getName());
               System.out.println("decl class = " +
                           fld.getDeclaringClass());
               System.out.println("type
                  = " + fld.getType());
               int mod = fld.getModifiers();
               System.out.println("modifiers = " +
                          Modifier.toString(mod));
               System.out.println("-----");
            }
          }
          catch (Throwable e) {
             System.err.println(e);
          }
       }
   }
           

这个例子与以前的例子相似。 一个新功能是使用Modifier。 这是一个反映类,它表示在一个字段成员上找到的修饰符,例如“private int”。 修饰符本身由整数表示,Modifier.toString用于以“官方”声明顺序返回字符串表示形式(例如“final”之前的“static”)。 程序的输出是:

name = d
   decl class = class field1
   type = double
   modifiers = private
   -----
   name = i
   decl class = class field1
   type = int
   modifiers = public static final
   -----
   name = s
   decl class = class field1
   type = class java.lang.String
   modifiers =
   ----- 
           

与方法一样,可以获取有关类(getDeclaredFields)中声明的字段的信息,也可以获取有关在超类(getFields)中定义的字段的信息。

按名称调用方法

到目前为止,所提供的例子都与获取课堂信息有关。 但是也可以以其他方式使用反射,例如调用指定名称的方法。

要了解如何工作,请考虑以下示例:

import java.lang.reflect.*;

   public class method2 {
      public int add(int a, int b)
      {
         return a + b;
      }

      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("method2");
           Class partypes[] = new Class[];
            partypes[] = Integer.TYPE;
            partypes[] = Integer.TYPE;
            Method meth = cls.getMethod(
              "add", partypes);
            method2 methobj = new method2();
            Object arglist[] = new Object[];
            arglist[] = new Integer();
            arglist[] = new Integer();
            Object retobj 
              = meth.invoke(methobj, arglist);
            Integer retval = (Integer)retobj;
            System.out.println(retval.intValue());
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }
           

假设一个程序想要调用add方法,但直到执行时才知道。也就是说,方法的名称在执行期间被指定(例如,这可以由JavaBeans开发环境完成)。上面的程序显示了一个这样做的方法。

getMethod用于在类中找到一个具有两个整数参数类型并具有适当名称的方法。一旦这个方法被发现并被捕获到一个方法对象中,它将被调用适当类型的对象实例。要调用一个方法,必须构造参数列表,其中整数值37和47包含在整数对象中。返回值(84)也被包裹在一个整数对象中。

创建新对象

因为调用构造函数等效于创建一个新的对象(最精确的是,创建一个新对象涉及内存分配和对象构造),所以没有等同于构造函数的方法调用。所以最接近于前一个例子就是说:

import java.lang.reflect.*;

   public class constructor2 {
      public constructor2()
      {
      }

      public constructor2(int a, int b)
      {
         System.out.println(
           "a = " + a + " b = " + b);
      }

      public static void main(String args[])
      {
         try {
           Class cls = Class.forName("constructor2");
           Class partypes[] = new Class[];
            partypes[] = Integer.TYPE;
            partypes[] = Integer.TYPE;
            Constructor ct 
              = cls.getConstructor(partypes);
            Object arglist[] = new Object[];
            arglist[] = new Integer();
            arglist[] = new Integer();
            Object retobj = ct.newInstance(arglist);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   } 
           

它找到一个构造函数来处理指定的参数类型并调用它,以创建对象的新实例。 这种方法的价值在于它纯粹是动态的,在执行时,而不是在编译时,使用构造函数查找和调用。

改变字段的值

反射的另一个用途是改变对象中数据字段的值。 它的值再次从反射的动态特性得出,其中可以通过执行程序中的名称查找一个字段,然后改变其值。 这通过以下示例说明:

import java.lang.reflect.*;

   public class field2 {
      public double d;

      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field2");
            Field fld = cls.getField("d");
            field2 f2obj = new field2();
            System.out.println("d = " + f2obj.d);
            fld.setDouble(f2obj, );
            System.out.println("d = " + f2obj.d);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   } 
           

在此示例中,d字段的值设置为12.34。

使用数组

反思的最后一个用途是创建和操纵数组。 Java语言中的数组是一种专门类的类,数组引用可以分配给Object引用。

要了解阵列如何工作,请考虑以下示例:

import java.lang.reflect.*;

   public class array1 {
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName(
              "java.lang.String");
            Object arr = Array.newInstance(cls, );
            Array.set(arr, , "this is a test");
            String s = (String)Array.get(arr, );
            System.out.println(s);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }
           

此示例创建一个10长的字符串数组,然后将数组中的位置5设置为字符串值。 检索并显示该值。

更复杂的数组操作如下代码所示:

import java.lang.reflect.*;

   public class array2 {
      public static void main(String args[])
      {
         int dims[] = new int[]{, , };
         Object arr 
           = Array.newInstance(Integer.TYPE, dims);

         Object arrobj = Array.get(arr, );
         Class cls = 
           arrobj.getClass().getComponentType();
         System.out.println(cls);
         arrobj = Array.get(arrobj, );
         Array.setInt(arrobj, , );

         int arrcast[][][] = (int[][][])arr;
         System.out.println(arrcast[][][]);
      }
   }
           

此示例创建一个5 x 10 x 15的int数组,然后继续将数组中的位置[3] [5] [10]设置为值37.注意,多维数组实际上是数组数组 ,所以例如,在第一个Array.get之后,arrobj中的结果是一个10 x 15的数组。 这再次剥离,以获得一个15长的阵列,该阵列中的第十个插槽是使用Array.setInt设置的。

请注意,创建的数组的类型是动态的,在编译时不一定要知道。

总结

Java反射是有用的,因为它支持通过名称动态检索关于类和数据结构的信息,并允许在执行的Java程序中进行操作。 此功能非常强大,在其他常规语言(如C,C ++,Fortran或Pascal)中没有任何相似之处。