本文运用的emoji:

简介

反射是Java编程语言中的一个特性。它答应履行的Java程序 检查 或 操作 自身,并操作程序的内部特点。例如,Java类能够获取其所有成员的称号并显现它们。

反射的一个详细用处是在JavaBeans中,软件组件能够经过一个构建东西进行可视化操作。该东西运用反射来获取Java组件 (类) 动态加载时的特点。

Java反射学习笔记--使用示例☔

一个简略的比如

要了解反射是怎么作业的,请考虑以下简略示例:

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

对于以下项的调用:

java DumpMethods java.util.Stack

输出为:

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.反射中找到。运用这些类必须遵循三个过程。第一步是获得一个java.lang.Class要操作的类的目标。java.lang.Class用于表明正在运转的Java程序中的类和接口。

获取类目标的一种办法是:

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

上述代码获取的类目标 String.

另一种办法是运用:

Class c = int.class;

或许

Class c = Integer.TYPE;

获取根本类型的类信息。后一种办法拜访预界说TYPE 包装类型 (例如Integer) 为根本类型。

第二步是调用办法,例如getDeclaredMethods,以获取该类声明的所有办法的列表。

一旦把握了这些信息,那么第三步就是运用反射API来操作这些信息。例如:

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

在下面的示例中,将三个过程结合在一起,以呈现怎么运用反射处理特定应用的独立插图。

模拟 instanceof 运算

一旦把握了类信息,下一步通常是问询有关类目标的根本问题。

例如,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(37));
            System.out.println(b1);
            boolean b2 = cls.isInstance(new A());
            System.out.println(b2);
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

在此示例中,类目标A被创立,然后我们检查类实例目标,检查它们是否是类目标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 = 0; i < methlist.length;
               i++) {  
               Method m = methlist[i];
               System.out.println("name 
                 = " + m.getName());
               for (int j = 0; j < pvec.length; j++)
                  System.out.println("
                   param #" + j + " " + pvec[j]);
               Class evec[] = m.getExceptionTypes();
               for (int j = 0; j < evec.length; j++)
                  System.out.println("exc #" + j 
                    + " " + evec[j]);
               System.out.println("-----");
            }
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

程序首先获取method1的类描绘,然后调用getDeclaredMethods(一个用于获取类中界说的每个办法的函数)检索Method 目标列表。这些办法包含public、protect、package和priva。假如你在程序中运用getMethods 而不是getDeclaredMethods,还能够获取承继办法的信息。

程序的输出为:

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 java.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 = 0; 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 = 0; j < pvec.length; j++)
                  System.out.println("param #" 
                     + j + " " + pvec[j]);
               Class evec[] = ct.getExceptionTypes();
               for (int j = 0; 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 = 37;
      String s = "testing";
      public static void main(String args[])
      {
         try {
            Class cls = Class.forName("field1");
            Field fieldlist[] 
              = cls.getDeclaredFields();
            for (int i 
              = 0; 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)。

按称号调用办法

到目前为止,现已提出的比如都与获取class有关。但是也能够以其他办法运用反射,例如调用指定称号的办法。

要了解其作业原理,请考虑以下示例:

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[2];
            partypes[0] = Integer.TYPE;
            partypes[1] = Integer.TYPE;
            Method meth = cls.getMethod(
              "add", partypes);
            method2 methobj = new method2();
            Object arglist[] = new Object[2];
            arglist[0] = new Integer(37);
            arglist[1] = new Integer(47);
            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用于在类中查找具有两个integer参数类型并具有适当称号的办法。一旦找到此办法并将其捕获到Method 目标,它是在适当类型的目标实例上调用的。要调用办法,必须结构一个参数列表,根本整数值为37和47Integer 目标。回来值 (84) 也被包含在Integer 目标。

创立新目标

结构函数不等同于办法调用,由于调用结构函数等同于创立新目标 (最精确地说,创立新目标触及内存分配和目标结构)。所以最接近前面比如的是:

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[2];
            partypes[0] = Integer.TYPE;
            partypes[1] = Integer.TYPE;
            Constructor ct 
              = cls.getConstructor(partypes);
            Object arglist[] = new Object[2];
            arglist[0] = new Integer(37);
            arglist[1] = new Integer(47);
            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, 12.34);
            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, 10);
            Array.set(arr, 5, "this is a test");
            String s = (String)Array.get(arr, 5);
            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[]{5, 10, 15};
         Object arr 
           = Array.newInstance(Integer.TYPE, dims);
         Object arrobj = Array.get(arr, 3);
         Class cls = 
           arrobj.getClass().getComponentType();
         System.out.println(cls);
         arrobj = Array.get(arrobj, 5);
         Array.setInt(arrobj, 10, 37);
         int arrcast[][][] = (int[][][])arr;
         System.out.println(arrcast[3][5][10]);
      }
   }

此示例创立一个5x10x15的int数组,然后继续将数组中的方位 [3][5][10] 设置为值37。请注意,多维数组实际上是数组数组,因此,例如,在第一个array.get之后,arrobj中的结果是10×15数组。再次将其剥离以获得15长的数组,并运用Array.setInt

请注意,创立的数组类型是动态的,不必在编译时知道。

总结

Java反射非常有用,由于它支持按称号动态检索有关类和数据结构的信息,并答应在履行的Java程序中进行操作。此功能非常强大,但是也要谨慎运用


本文正在参与「金石计划 . 瓜分6万现金大奖」