4年前 (2020-10-19)  Java系列 |   抢沙发  161 
文章评分 0 次,平均分 0.0

使用Java反射

反射是Java编程语言中的一个特性。“在执行程序时检查或操作程序的内部属性”。例如,Java类可以获取所有成员的名称并显示它们。

从Java类内部检查和操作Java类的能力听起来可能不太像,但在其他编程语言中,这个特性根本不存在。例如,在PascalCC++程序中,没有办法获得关于该程序中定义的函数的信息。

反射的一个具体用途是在JavaBeans中,在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转储方法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以及它们的完全限定参数和返回类型。

此程序使用类forName,然后调用getDeclaredMethods检索类中定义的方法列表。java.lang.reflect.Method是表示单个类方法的类。

设置为使用反射

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

获取类对象的一种方法是:

Class c = Class.forName("java.lang.String") 获取字符串的类对象。另一种方法是使用:Class c = int.class; or Class c = Integer.TYPE; 获取基本类型的类信息。后一种方法访问基本类型的包装器的预定义类型字段(例如Integer)。

第二步是调用getDeclaredMethods之类的方法,以获取类声明的所有方法的列表。

一旦掌握了这些信息,第三步就是使用反射API来操作信息。例如,序列:

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

Method m[] = c.getDeclaredMethods();

System.out.println(m[0].toString())

将显示字符串中声明的第一个方法的文本表示形式。

在下面的示例中,这三个步骤结合起来,展示了如何使用反射来处理特定应用程序的自包含插图。

模拟instanceof操作符

一旦掌握了类信息,下一步通常是询问有关类对象的基本问题。例如I类实例方法可用于模拟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());
               System.out.println("decl class = " +
                              m.getDeclaringClass());
               Class pvec[] = m.getParameterTypes();
               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("return type = " +
                                  m.getReturnType());
               System.out.println("-----");
            }
         }
         catch (Throwable e) {
            System.err.println(e);
         }
      }
   }

程序首先获取method1的类描述,然后调用getDeclaredMethods检索方法对象的列表,每个对象对应于类中定义的每个方法。这些方法包括publicprotectedpackageprivate方法。如果在程序中使用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 [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 = 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);
          }
       }
   }

这个例子与前面的例子相似。一个新特性是使用修饰符。这是一个反射类,它表示在字段成员上找到的修饰符,例如“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[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用于在类中查找具有两个整型参数类型且具有适当名称的方法。一旦这个方法被找到并捕获到一个方法对象中,它就被调用到一个适当类型的对象实例上。要调用方法,必须构造一个参数列表,基本整数值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[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语言中的数组是一种特殊类型的类,数组引用可以分配给对象引用。

要了解数组的工作原理,请考虑以下示例:

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]);
      }
   }

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

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

Java反射总结

Java反射非常有用,因为它支持按名称动态检索有关类和数据结构的信息,并允许在正在执行的Java程序中对它们进行操作。这个特性非常强大,在其他传统语言中没有等价的东西,如C、C++、FORTRAN或Pascal。

 

除特别注明外,本站所有文章均为老K的Java博客原创,转载请注明出处来自https://javakk.com/679.html

关于

发表评论

表情 格式

暂无评论

登录

忘记密码 ?

切换登录

注册