Java反射之Constructor、Method、Field使用及说明

1 简介

在编程生活中,一般处理业务时不需要使用反射的内容,因为反射属于Java编程中的高级知识。但当我们需要迈向更高级的Java编程时,运行时类型信息是必须深刻理解的,在Spring MVC中,各种注解底层实现的方式就是使用了类型信息。
运行时类型信息(runtime type infomation)使得你可以在程序运行时发现和使用类型信息。它使得我们从只能在编译期执行面向类型的操作的禁锢中解脱出来,并且可以使用某些非常强大的程序。理解Java是如何在运行时识别对象和类的信息的。主要是有两种方式。一种是传统的RTTI,它假定我们在编译时已经知道了所有的类型;另外一种 是“反射”机制,允许我们在运行时发现和使用类的信息。在Java中,所有的类型转换都是在运行时进行正确性检查的。这也是RTTI名字的含义:

在运行时,识别一个对象的类型。

2 为什么需要RTTI

在类型继承层次中,对于Shape继承层次中,Shape对象实际执行什么样的代码,是由引用所指向的具体对象Circle,Square或者Triangle所决定的。通常,也正是这样要求的;我们希望大部分代码尽可能通用,尽可能少的了解对象的具体类型,而是只与对象家族中的一个通用表示打交道。这样代码会更容易写,更容易读,且便于维护;设计也更容易实现、理解和改变。所以“多态”是面向对象编程的基本目标。

Field继承层次

有一类特殊的问题-----如果能够知道某个泛化的引用的确切类型,就可以使用最简单的方式去解决它,那么此时该怎么办?例如假如我们允许用户将某一具体类型的几何形状全部变成某种特殊的颜色,以便突出显示它们。或者,可能要用某个方法来旋转列出的所有图形,但想跳过圆形,因为对圆形进行旋转没有意义。使用RTTI,可以查询某个shape引用所指向的对象的确切类型,然后选择或者剔除特例。

3 Class对象

类是程序的一部分,每个类都有一个Class对象。当编译了一个新类时,就会产生一个Class对象(可以在磁盘上看到同名的.class文件,若为内部类也会有相应的.class文件)

Counter.class
LocalInnerClass$1.class
LocalInnerClass$1LocalCounter.class

如果内部类是匿名的,编译期会简单的产生一个数字作为其标识符。如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与”$”的后面。
然后为了生成这个类的对象,运行这个程序的Java虚拟机(JVM)将使用被称为“类加载器”的子系统。所有的类都是在对其第一次使用时,动态加载到JVM中,当程序创建第一个对类的静态成员的引用时就会加载这个类。因此构造器也是类的静态方法。一旦某个类的Class对象被载入内存,它就被用来创建这个类的所有对象。

3.1 Class对象的获取

3.1.1 Class.forName()

forName是Class类的静态方法,该类具体实现如下:

    @CallerSensitive
    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        Class<?> caller = null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // Reflective call to get caller class is only needed if a security manager
            // is present.  Avoid the overhead of making this call otherwise.
            caller = Reflection.getCallerClass();
            if (sun.misc.VM.isSystemDomainLoader(loader)) {
                ClassLoader ccl = ClassLoader.getClassLoader(caller);
                if (!sun.misc.VM.isSystemDomainLoader(ccl)) {
                    sm.checkPermission(
                        SecurityConstants.GET_CLASSLOADER_PERMISSION);
                }
            }
        }
        return forName0(name, initialize, loader, caller);
    }
    	@CallerSensitive
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
    }

因此在传入字符串时,经常需要对ClassNotFoundException异常进行处理。
Class对象的常用方法如下:

    获取Class引用
    public static Class<?> forName(String className)
                throws ClassNotFoundException {
    使用Class引用创建实例,使用默认构造器
    public T newInstance()
        throws InstantiationException, IllegalAccessException
    判断指定的Class对象的引用是不是接口
    public native boolean isInterface();
    等价于instanceof,判断传入的对象与该Class对象赋值兼容assignment-compatible
    public native boolean isInstance(Object obj);
    
    判断该Class对象是否表示数组类型
    public native boolean isArray();
    
    返回由该Class对象所表示的实体(class, interface, array class, primitive type, void)的名称
    public String getName()
    //例子
    String.class.getName()
        returns "java.lang.String"
    byte.class.getName()
        returns "byte"
    (new Object[3]).getClass().getName()
        returns "[Ljava.lang.Object;"
    (new int[3][4][5][6][7][8][9]).getClass().getName()
        returns "[[[[[[[I"
    
    
    public native Class<? super T> getSuperclass();
    public Package getPackage() {
    返回该Class对象的所有接口对应的Class
    public Class<?>[] getInterfaces()
    返回对于这种类型或接口的Java语言标识符,编码成数字,标识符的集合由public, protected, private, final, static, abstract, interface组成
    public native int getModifiers();
    
    返回基础类型的简单的名称,正如在源代码中所给定的一样,如果是匿名类返回空串
    public String getSimpleName()
    public String getCanonicalName()

上述方法的使用参见如下例子:

    //: typeinfo/toys/ToyTest.java
    // Testing class Class.
    package typeinfo.toys;
    import static net.mindview.util.Print.*;
    
    interface HasBatteries {}
    interface Waterproof {}
    interface Shoots {}
    
    class Toy {
      // Comment out the following default constructor
      // to see NoSuchMethodError from (*1*)
      Toy() {}
      Toy(int i) {}
    }
    
    class FancyToy extends Toy
    implements HasBatteries, Waterproof, Shoots {
      FancyToy() { super(1); }
    }
    
    public class ToyTest {
      static void printInfo(Class cc) {
        print("Class name: " + cc.getName() +
          " is interface? [" + cc.isInterface() + "]");
        print("Simple name: " + cc.getSimpleName());
        print("Canonical name : " + cc.getCanonicalName());
      }
      public static void main(String[] args) {
        Class c = null;
        try {
          c = Class.forName("typeinfo.toys.FancyToy");
        } catch(ClassNotFoundException e) {
          print("Can't find FancyToy");
          System.exit(1);
        }
        printInfo(c);  
        for(Class face : c.getInterfaces())
          printInfo(face);
        Class up = c.getSuperclass();
        Object obj = null;
        try {
          // Requires default constructor:
          obj = up.newInstance();
        } catch(InstantiationException e) {
          print("Cannot instantiate");
          System.exit(1);
        } catch(IllegalAccessException e) {
          print("Cannot access");
          System.exit(1);
        }
        printInfo(obj.getClass());
      }
    } /* Output:
    Class name: typeinfo.toys.FancyToy is interface? [false]
    Simple name: FancyToy
    Canonical name : typeinfo.toys.FancyToy
    
    Class name: typeinfo.toys.HasBatteries is interface? [true]
    Simple name: HasBatteries
    Canonical name : typeinfo.toys.HasBatteries
    
    Class name: typeinfo.toys.Waterproof is interface? [true]
    Simple name: Waterproof
    Canonical name : typeinfo.toys.Waterproof
    
    Class name: typeinfo.toys.Shoots is interface? [true]
    Simple name: Shoots
    Canonical name : typeinfo.toys.Shoots
    
    Class name: typeinfo.toys.Toy is interface? [false]
    Simple name: Toy
    Canonical name : typeinfo.toys.Toy
    *///:~

上述的例子参见Java编程思想P317
在传递给forName()的字符串中,必须使用全限定名,即包含包名。
如果你有一个Class对象,还可以使用getSuperclass()方法查询其基类,这将返回用来进一步查询的Class对象。因此,你可以在运行时发现一个对象完整的类继承结构。
Class的newInstance()方法是实现“虚拟构造器”的一种途径,虚拟构造器允许你声明:“我不知道你确切的类型,但是无论如何要正确的创建你自己。”一个Class引用在编译期不具备任何更进一步的类型信息。当你创建新实例时,会得到Object引用,但是这个引用指向的是真实类型的对象。当然,在你可以发送Object能够接受的消息之外的任何消息之前,你必须更多地了解它,并执行某种转型。另外,使用newInstance()来创建的类,必须带有默认构造器。之后则可以使用Java反射API,用任意的构造器来动态的创建类的对象。

3.1.2 obj.getClass()

无论何时,只要你想在运行时使用类型信息,就必须首先获得对恰当的Class对象的引用。Class.forName()就是实现此功能的便捷途径,因为不需要为了获得Class引用而持有该类型的对象。
但是如果你已经有了一个感兴趣的类型的对象,那就可以通过调用getClass()方法来获取Class引用,这个方法是属于根类Object的一部分,它将返回该对象的实际类型的Class的引用。

    import java.io.*;
    class Student {
        private String name;
        private String score;
    }
    class test  
    {
    	public static void main (String[] args) throws java.lang.Exception
    	{
    	    Student s = new Student();
    	    Class c = s.getClass();
    	    
    		System.out.println(new Student());
    		printInfo(c);
    	}
    	
    	public static void printInfo(Class c) {
    	    print(c.getName());
    	    print("is Interface ? "+ c.isInterface());
    	    print("simpleName: "+ c.getSimpleName());
    	    print("Canonical name: "+c.getCanonicalName());
    	}
    	public static void print(Object object) {
    	    System.out.println(object.toString());
    	}
    	
    }

运行结果如下:

    Student@659e0bfd
    Student
    is Interface ? false
    simpleName: Student
    Canonical name: Student

3.1.3 类字面值常量

Java还提供了另一种方法来生成Class对象的引用,即使用类字面值常量。对于上述程序可以使用

Student.class

这样做的好处不仅更加简单,而且更安全,因为在编译期就会收到检查,并且根除了对forName()方法的调用,所以也更加高效。
类字面常量不仅可以应用于普通的类,也可以应用于接口、数组、及基本数据类型。对于基本数据类型的包装其类,有一个标准字段TYPE,TYPE字段是一个引用,指向对应基本数据类型的Class对象。

boolean.class    <==>   Boolean.TYPE
int.class        <==>   Integer.TYPE

3.1.4 泛化的Class引用

Class引用总是指向某个Class对象,它可以制造类的实例,并包含可作用于这些实例的所有方法代码。它还包含该类的静态成员,因此,Class引用表示的就是它所指向的对象的确切类型,而该对象便是Class类的一个对象。
通过使用Class泛型,可以让编译期强制执行额外的类型检查。

Class<Integer> genericIntClass = int.class; 
genericIntClass = double.class//Illegal

为了在使用泛化的Class引用时放松限制,可以使用通配符,即“?”
表示任何事物。

Class<?> intClass = int.class;
intClass = double.class;

Class<?>的好处是它表示你并非碰巧或者由于疏忽,而是用了一个非具体的类引用,你就是选择了非具体的半本。

4 类型转换前先做检查

RTTI形式有如下三种:

  1. 传统的类型转换,如(Shape),由RTTI确保类型转换的正确性,如果执行了一个错误的类型转换,就会抛出一个ClassCastException。
  2. 代表对象的类型的Class对象。通过查询Class对象可以获取运行时所需的信息。Java要执行类型检查,通常称为‘类型安全的向下转型’。由于知道Circle肯定是一个Shape,所以编译期允许自由做向上转型的赋值操作,而不需要任何显式的操作。而不允许向下转型到实际上不是待转类的子类的类型上。
  3. RTTI的第三种形式,就是关键字instanceof,返回一个布尔值,告诉我们对象是不是某个特定类型的实例。可以用提问的方式使用它.
  if (x instanceof Dog) {
    ((Dog)).bark();
  }

4.1 实例1

注意instanceof和isInstance()的区别

    interface A {
    }
     
    class B {
    }
     
    class C implements A {
    }
     
    class D extends B {
    }
     
    public class TestInstanceof {
     
        public static void main(String[] args) {
            C t1 = new C();
            D t2 = new D();
     
            // obj instanceof class:这个对象是不是这种类型.
            // 测试1:一个对象是本身类的一个对象
            System.out.println(t1 instanceof C); // true
            System.out.println("============");
     
            // 测试2:一个对象是本身类父类(父类的父类)和接口的一个对象
            System.out.println(t1 instanceof A); // true
            System.out.println(t2 instanceof D); // true
            System.out.println("============");
     
            // 测试3:所有对象都是object
            System.out.println(t1 instanceof Object); // true
            System.out.println(t2 instanceof Object); // true
            System.out.println("============");
     
            // 测试4:凡是null相关的都是false
            System.out.println(null instanceof Object); // false
            System.out.println(null instanceof B); // true
            System.out.println("============");
     
            // class.isInstance(obj):这个对象能不能被转化为这个类
            // 测试1:一个对象是本身类的一个实例
            System.out.println(C.class.isInstance(t1)); // true
            System.out.println("============");
     
            // 测试2:一个对象能被转化为本身类所继承的类(父类的父类)和实现的接口(接口的父接口)强转
            System.out.println(A.class.isInstance(t1)); // true
            System.out.println(B.class.isInstance(t2)); // true
            System.out.println("============");
     
            // 测试3:所有对象都能被Object强转
            System.out.println(Object.class.isInstance(t1)); // true
            System.out.println(Object.class.isInstance(t2)); // true
            System.out.println("============");
     
            // 测试4:凡是和null相关的都是false
            System.out.println(Object.class.isInstance(null)); // false
            System.out.println(D.class.isInstance(null)); // false
     
     
        }
    }

运行结果如下:

true
============
true
true
============
true
true
============
false
false
============
true
============
true
true
============
true
true
============
false
false

4.2 实例2

在Java编程思想中,Bruce Eckl也提供了一个instanceof和isInstance()的例子,代码如下:

    //: typeinfo/FamilyVsExactType.java
    // The difference between instanceof and class
    package typeinfo;
    import static net.mindview.util.Print.*;
    
    class Base {}
    class Derived extends Base {}	
    
    public class FamilyVsExactType {
      static void test(Object x) {
        print("Testing x of type " + x.getClass());
        print("x instanceof Base " + (x instanceof Base));
        print("x instanceof Derived "+ (x instanceof Derived));
        print("Base.isInstance(x) "+ Base.class.isInstance(x));
        print("Derived.isInstance(x) " +
          Derived.class.isInstance(x));
        print("x.getClass() == Base.class " +
          (x.getClass() == Base.class));
        print("x.getClass() == Derived.class " +
          (x.getClass() == Derived.class));
        print("x.getClass().equals(Base.class)) "+
          (x.getClass().equals(Base.class)));
        print("x.getClass().equals(Derived.class)) " +
          (x.getClass().equals(Derived.class)));
      }
      public static void main(String[] args) {
        test(new Base());
        test(new Derived());
      }	
    } /* Output:
    Testing x of type class typeinfo.Base
    x instanceof Base true
    x instanceof Derived false
    Base.isInstance(x) true
    Derived.isInstance(x) false
    x.getClass() == Base.class true
    x.getClass() == Derived.class false
    x.getClass().equals(Base.class)) true
    x.getClass().equals(Derived.class)) false
    Testing x of type class typeinfo.Derived
    x instanceof Base true
    x instanceof Derived true
    Base.isInstance(x) true
    Derived.isInstance(x) true
    x.getClass() == Base.class false
    x.getClass() == Derived.class true
    x.getClass().equals(Base.class)) false
    x.getClass().equals(Derived.class)) true
    *///:~

在查询类型信息时,以instanceof的形式(即以instanceof的形式或isInstance()的形式,它们产生相同的结果)与直接比较Class对象有一个比较重要的区别。从上述的代码返回结果,可以看出instanceof和isInstance()的结果完全一样,equals()也一样。但两组测试得出的杰伦却不也一样。instanceof保持了类型的概念,它指的是”你是这个类吗?或者你是这个类的派生类吗?”而如果==比较实际的Class对象,就没有考虑继承-----它或者是这个确切的类型,或者不是。

5 反射:运行时的类信息

如果不知道某个对象的确切类型,RTTI可以告诉你。但是有一个限制:这个类型在编译时必须已知,这样才能使用RTTI来识别它,并利用这些信息做一些有用的事情,即在编译时,编译期必须知道所有要通过RTTI来处理的类。

在传统的编程环境中北部太可能出现这种情况,但当我们置身于更大规模的编程世界中,在许多重要情况下就会发生上面的事情,首先是“基于构件的编程”,在这种编程方式中,将使用某种基于快速应用开发RAD的应用构建工具,即集成开发环境来构建项目。这是一种可视化编程方法,可以通过将代表不同组件的图标拖拽到表单中来创建程序。然后再编程时通过设置构件的属性来配置它们。这种设计时的配置,要求构件都是可实例化的,并且要暴露部分信息,以允许程序员读取和修改构件的属性。

人们想要在运行时获取类的信息的另一个动机,便是希望提供在跨网络的远程平台上创建和运行对象的能力,这被称为远程方法调用,它允许一个Java程序将对象分不到多台机器上。
Class类与java.reflect类库一起对反射的概念进行了支持,该类库包含了Field, Method, Constructor类,每个类都实现了Member接口。这些类型的对象是由JVM在运行时创建的,用来iaoshi未知类里对应的成员。这样你就可以使用Constructor创建新的对象,用get()和set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,getFields(),getMethods(),getConstructors()是很便利的方法用来返回表示字段、方法、以及构造器的对象的数组。

重要的是,要认识到反射机制并没有什么特别之处,当通过反射与一个未知类型打交道时,JVM只是简单的检查这个对象,看它属于那个特定的类。在用它做其他事情之前必须加载那个类的Class对象。因此那个类的Class对象是必须获取的:要么在本地机器上,要么可以通过网络取得。RTTI和反射的真正区别只在于:对RTTI来说,编译期在编译时打开和检查.class文件,而对于反射机制来说,.class文件在编译时是不可获得的,所以是在运行时打开和检查.class文件的。

5.1 Field类使用

5.1.1 Field常用方法

在Class类中与Field对象相关的方法如下:

    返回由该类型表示的类或接口的所有可访问的public(all the accessible public fields)
    public Field[] getFields() throws SecurityException {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
            return copyFields(privateGetPublicFields(null));
        }
    
    //返回一个反映了由该Class对象所表示的接口或类型的指定的public成员域对象,参数是一个字符串,指定了预期域的名称
    public Field getField(String name)
            throws NoSuchFieldException, SecurityException {
            checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
            Field field = getField0(name);
            if (field == null) {
                throw new NoSuchFieldException(name);
            }
            return field;
        }
    //返回一个Field对象,它反映了由该Class对象所表示的类型或接口的指定的声明的域,参数是一个字符串,指定了想要的域的的简单的名称
    public Field[] getDeclaredFields() throws SecurityException {
            checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
            return copyFields(privateGetDeclaredFields(false));
        }
    
    //返回一个反映了由该Class对象所表示的接口或类型的指定的声明的成员域对象,参数是一个字符串,指定了预期域的名称
    public Field getDeclaredField(String name)
            throws NoSuchFieldException, SecurityException {
            checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
            Field field = searchFields(privateGetDeclaredFields(false), name);
            if (field == null) {
                throw new NoSuchFieldException(name);
            }
            return field;
        }

Field类类型继承层次如下:
Field类继承层次
Field类的声明如下:

    //一个Field对象提供信息和动态访问一个类或接口的域的信息
    public final class Field extends AccessibleObject implements Member {
    //返回由该Field对象表示的域的名称
    public String getName() {
            return name;
    }
    //返回由该Field对象表示的Java语言修饰符,编码成一个整数,由Modifier类进行解码。
    public int getModifiers() {
        return modifiers;
    }
    
    //返回由该Field对象所表示的域的值,在指定的对象上,如果值是基本类型,则会被包装成为对象。如果此 Field 对象强制实施 Java 语言访问控制,并且底层字段是不可访问的,则该方法将抛出一个 IllegalAccessException。如果底层字段是静态的,并且声明该字段的类尚未初始化,则初始化这个类。
    public Object get(Object obj)
            throws IllegalArgumentException, IllegalAccessException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            return getFieldAccessor(obj).get(obj);
        }
    //返回静态或实例的boolean域的值
    public boolean getBoolean(Object obj)
            throws IllegalArgumentException, IllegalAccessException{
    同理,也有getChar, getByte, getInt, getLong, getFloat, getDouble
    
    //在指定的对象上,设置由Field对象所表示的域成指定的新值。如果基础域具有基础类型,则新值会自动解包(unwrapped)。注意如果此 Field 对象实施 Java 语言访问控制,并且底层字段是不可访问的,则该方法将抛出一个
    public void set(Object obj, Object value)
            throws IllegalArgumentException, IllegalAccessException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            getFieldAccessor(obj).set(obj, value);
        }
    
    //在指定的对象上,把一个域设置为一个boolean。注意如果此 Field 对象实施 Java 语言访问控制,并且底层字段是不可访问的,则该方法将抛出一个 IllegalAccessException。
    public void setBoolean(Object obj, boolean z)
            throws IllegalArgumentException, IllegalAccessException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            getFieldAccessor(obj).setBoolean(obj, z);
        }
    //同理,setByte, setChar, setDouble, setLong, setShort, setFloat,相应的参数修改为对应类型即可。
    
    //该setAccessible方法继承自AccessibleObject类。将此对象的 accessible 标志设置为指示的布尔值。值为 true 则指示反射的对象在使用时应该取消 Java 语言访问检查。值为 false 则指示反射的对象应该实施 Java 语言访问检查。
    public void setAccessible(boolean flag) throws SecurityException {
            SecurityManager sm = System.getSecurityManager();
            if (sm != null) sm.checkPermission(ACCESS_PERMISSION);
            setAccessible0(this, flag);
    }
    //该方法继承自AccessibleObject,返回该对象是否可访问的标志
     public boolean isAccessible() {

5.1.2 实例通过class设置public和private域

    //TestField.java文件, 该文件中有一个public域,一个private域
    public class TestField {
        private int testInt;
        public String testString;
        
        public String toString() {
            return testInt + ":" + testString;
        }
    }

如何仅仅通过Class对象生成一个TestField对象,并为成员赋值,私有和public进行操作呢,

    import java.lang.reflect.Field;
     
    public class AssignFieldDemo {
        public static void main(String[] args) {
            try {
                Class c = Class.forName(args[0]);
                Object targetObj = c.newInstance();
                
    				//因为testInt为私有,必须先取消访问权限控制
                Field testInt = c.getDeclaredField("testInt");
                testInt.setAccessible(true);
                testInt.setInt(targetObj, 99);
                
    				//因为testString为public,所以可以直接访问并设置
                Field testString = c.getDeclaredField("testString");
                //testString.setAccessible(true);
                testString.set(targetObj, "caterpillar");
                
                System.out.println(targetObj);
            } catch(ArrayIndexOutOfBoundsException e) {
                System.out.println("没有指定类");
            } catch (ClassNotFoundException e) {
                System.out.println("找不到指定的类");
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (NoSuchFieldException e) {
                System.out.println("找不到指定的域成员");
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }       
        }
    }

在上述代码的操作过程中,需要传入命令行参数”TestFiled”,通过Class.forName进行Class对象的获取。

注意:getFields()仅可以获取public域,对于private域是无法获取的。

5.1.3 BeanUtil,通过Map转换成相应的对象

    //Student.java文件
    public class Student {
        private String name;
        private int score; 
    
        public Student() {
            name = "N/A"; 
        } 
    
        public Student(String name, int score) { 
            this.name = name; 
            this.score = score; 
        } 
    
        public void setName(String name) {
            this.name = name;
        }
        
        public void setScore(int score) {
            this.score = score;
        }
    
        public String getName() { 
            return name; 
        } 
    
        public int getScore() { 
            return score; 
        } 
    
        public String toString() {
            return name + ":" + score;
        }
    }

该对象是简单的Student类型,只有两个属性name和score,并且类型提供了相应的设值/取值函数。
可以使用如下的程序片段动态生成Student实例,并通过shetName()设定名称,用getName()取得名称

Class clz = Class.forName(“Student”);
Constructor constructor = clz.getConstructor(String.class, Integer.class);
Object obj = constructor.newInstance(“cate”, 90);

同样,也可以指定方法名称和参数类型,通过getMethod()取得对应的公开Method实例

Method setter = clz.getMethod(“setName”, String.class);
//指定参数值调用对象obj的方法
setter.invoke(obj, “gate”);
Method getter = clz.getMethod(“getName”);
System.out.println(getter.invoke(obj));

上述的过程即为动态调用方法的基本流程

但在某些情况下,也许想要调用受保护protected或私有private的方法,可以使用getDeclaredMethod()取得方法对象,并在调用Method的setAccessible()指定为true,如下述代码片段:

Method priMeth = clz.getMethod(“priMth”, …);
priMth.setAccessible(true);
priMth.invoke(target, args);

也可以使用反射机制来存取类的数据成员Field, Class的getFiled()可以取得公开的Field,如果想要获取私有的Field,可以使用getDeclaredField(),例如动态建立Student实例,并存取private的name和score成员的代码片段如下:

Class clz = Student.class;
Object o = clz.newInstance();
Field name = clz.getDeclaredField(“name”);
Field score = clz.getDeclaredField(“score”);
name.setAccessible(true);
name.set(o, “Justin”);
score.setAccessible(true);
score.set(o, 90);
Student stu = (Student)o;
System.out.printf(“(%s, %d)%n”, stu.getName(), stu.getScore());

若在Map中收集了学生数据,则可以使用如下的代码转换Student实例。

    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.util.Map;
    
    public class BeanUtil {
        // 给定Map对象及要产生的Bean类名称
        // 可以取回已经设定完成的对象
        public static Object getCommand(Map requestMap, 
                                        String commandClass) 
                                          throws Exception {
            Class c = Class.forName(commandClass);
            Object o = c.newInstance();
        
            return updateCommand(requestMap, o);
        }
    
        // 使用reflection自动找出要更新的属性
        public static Object updateCommand(
                               Map requestMap, 
                               Object command) 
                                  throws Exception {
            Method[] methods = 
                       command.getClass().getDeclaredMethods();
        
            for(int i = 0; i < methods.length; i++) {
                // 略过private、protected成员
                // 且找出必须是set开头的方法名称
                if(!Modifier.isPrivate(methods[i].getModifiers()) &&
                   !Modifier.isProtected(methods[i].getModifiers()) &&  
                   methods[i].getName().startsWith("set")) {
                    // 取得不包括set的名称
                    String name = methods[i].getName()
                                            .substring(3)
                                            .toLowerCase();
                    // 如果setter名称与键值相同
                    // 呼叫对应的setter并设定值
                    if(requestMap.containsKey(name)) {
                        String param = (String) requestMap.get(name);
                        Object[] values = findOutParamValues(
                                            param, methods[i]);
                        methods[i].invoke(command, values);
                    }
                }
            }
            return command;  
        }
      
        // 转换为对应类型的值
        private static Object[] findOutParamValues(
                         String param, Method method) {
            Class[] params = method.getParameterTypes();
            Object[] objs = new Object[params.length];
        
            for(int i = 0; i < params.length; i++) {
                if(params[i] == String.class) {
                    objs[i] = param;
                }
                else if(params[i] == Short.TYPE) {
                    short number = Short.parseShort(param);
                    objs[i] = new Short(number);
                }
                else if(params[i] == Integer.TYPE) {
                    int number = Integer.parseInt(param);
                    objs[i] = new Integer(number);
                }
                else if(params[i] == Long.TYPE) {
                    long number = Long.parseLong(param);
                    objs[i] = new Long(number);
                }
                else if(params[i] == Float.TYPE) {
                    float number = Float.parseFloat(param);
                    objs[i] = new Float(number);
                }
                else if(params[i] == Double.TYPE) {
                    double number = Double.parseDouble(param);
                    objs[i] = new Double(number);
                }
                else if(params[i] == Boolean.TYPE) {
                    boolean bool = Boolean.parseBoolean(param);
                    objs[i] = new Boolean(bool);
                }
            }    
            return objs;
        }
    }

上述的代码,可以把一个Map转换成一个特定类型,参见如下的Demo

import java.util.*;

public class BeanUtilDemo {
    public static void main(String[] args) throws Exception {
        Map<String, String> request = 
                  new HashMap<String, String>();
        request.put("name", "caterpillar");
        request.put("score", "90");
        Object obj = BeanUtil.getCommand(request, args[0]);
        System.out.println(obj);
    }
}

5.2 Method使用

5.2.1 Method类

Method类隶属于java.lang.reflect反射包,该类型的继承层次如下图所示:
Method继承层次

Method 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
Method 允许在匹配要调用的实参与底层方法的形参时进行扩展转换;但如果要进行收缩转换,则会抛出 IllegalArgumentException。

在Method类中常用的方法如下:
我们知道一个标准的Java方法如下所示:

    public class BufferedInputFile {
      // Throw exceptions to console:
      public static String
      read(String filename) throws IOException {
        // Reading input by lines:
        BufferedReader in = new BufferedReader(
          new FileReader(filename));
        String s;
        StringBuilder sb = new StringBuilder();
        while((s = in.readLine())!= null)
          sb.append(s + "\n");
        in.close();
        return sb.toString();
      }
      public static void main(String[] args)
      throws IOException {
        System.out.print(read("BufferedInputFile.java"));
      }
    }

在上述方法中,主要的函数声明如下:

public static String
  read(String filename) throws IOException {

因此Method对象要为这种结构进行建模,则必然要有相应的字段去表示修饰符、方法名称、方法参数、返回类型、以及抛出的异常,自然应该提供相应的get方法,如

    getModifiers, getName, getTypeParameters(), getReturenType(),获取参数的个数getParameterCount(), getExceptionTypes()

    //返回由Method所表示的方法的名称,转换为字符串
    public String getName() {
            return name;
    }
    
    //返回该Method方法的修饰符
    public int getModifiers() {
            return modifiers;
    }
       //对带有指定参数的指定对象调用由此 Method 对象表示的底层方法。
       个别参数被自动解包,以便与基本形参相匹配,基本参数和引用参数都随需服从方法调用转换。 
        如果底层方法是静态的,那么可以忽略指定的 obj 参数。该参数可以为 null。 
        如果底层方法所需的形参数为 0,则所提供的 args 数组长度可以为 0 或 null。 
        如果底层方法是实例方法,则使用动态方法查找来调用它,这一点记录在 Java Language Specification, Second Edition 的第 15.12.4.4 节中;在发生基于目标对象的运行时类型的重写时更应该这样做。
        如果底层方法是静态的,并且尚未初始化声明此方法的类,则会将其初始化。 
        如果方法正常完成,则将该方法返回的值返回给调用者;如果该值为基本类型,则首先适当地将其包装在对象中。但是,如果该值的类型为一组基本类型,则数组元素不 被包装在对象中;换句话说,将返回基本类型的数组。如果底层方法返回类型为 void,则该调用返回 null。
       public Object invoke(Object obj, Object... args)
            throws IllegalAccessException, IllegalArgumentException,
               InvocationTargetException
        {
            if (!override) {
                if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                    Class<?> caller = Reflection.getCallerClass();
                    checkAccess(caller, clazz, obj, modifiers);
                }
            }
            MethodAccessor ma = methodAccessor;             // read volatile
            if (ma == null) {
                ma = acquireMethodAccessor();
            }
            return ma.invoke(obj, args);
        }

### 5.2.2	实例
>在Java编程思想P335页。

通常你需要直接使用反射工具,但是它们在你需要创建更加动态的代码时会很有用。反射是用来支持其他属性的,例如对象序列化和JavaBean,但是如果能动态的提取某个类的信息还是非常有用的。在浏览实现了类定义的源代码或是JDK文档,只能找到在这个类中被定义的或被覆盖的方法,但对你来说,可能有数十个方法非常有用,但都继承自基类。要找出这些方法很耗时而且乏味,但反射提供了一种方法,使我们能够编写可以自动展示完整接口的简单工具。在IDE中,通过圆点访问或者指针访问时自动提示的方法,便用了如下的功能:
 ![智能提示](https://img-blog.csdnimg.cn/20190116161655105.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2xrMTQyNTAw,size_16,color_FFFFFF,t_70)
```java
    //: typeinfo/ShowMethods.java
    // Using reflection to show all the methods of a class,
    // even if the methods are defined in the base class.
    // {Args: ShowMethods}
    import java.lang.reflect.*;
    import java.util.regex.*;
    import static net.mindview.util.Print.*;
    
    public class ShowMethods {
      private static String usage =
        "usage:\n" +
        "ShowMethods qualified.class.name\n" +
        "To show all methods in class or:\n" +
        "ShowMethods qualified.class.name word\n" +
        "To search for methods involving 'word'";
      //\w指的是词字符,下属的模式匹配对于java.lang.String会匹配java.lang.
      private static Pattern p = Pattern.compile("\\w+\\.");
      public static void main(String[] args) {
        if(args.length < 1) {
          print(usage);
          System.exit(0);
        }
        int lines = 0;
        try {
          Class<?> c = Class.forName(args[0]);
          Method[] methods = c.getMethods();
          Constructor[] ctors = c.getConstructors();
          if(args.length == 1) {
            for(Method method : methods)
              print(
                p.matcher(method.toString()).replaceAll(""));
            for(Constructor ctor : ctors)
              print(p.matcher(ctor.toString()).replaceAll(""));
            lines = methods.length + ctors.length;
          } else {
            for(Method method : methods)
              if(method.toString().indexOf(args[1]) != -1) {
                print(	
                  p.matcher(method.toString()).replaceAll(""));
                lines++;
              }
            for(Constructor ctor : ctors)
              if(ctor.toString().indexOf(args[1]) != -1) {
                print(p.matcher(
                  ctor.toString()).replaceAll(""));
                lines++;
              }
          }
        } catch(ClassNotFoundException e) {
          print("No such class: " + e);
        }
      }
    }

上述代码的输出结果如下:

    /* Output:
    public static void main(String[])  //本类中定义的方法
    public native int hashCode()    //其余均为Object类的方法,表明getMethods能够获得该类以及超类中的public方法
    public final native Class getClass()
    public final void wait(long,int) throws InterruptedException
    public final void wait() throws InterruptedException
    public final native void wait(long) throws InterruptedException
    public boolean equals(Object)
    public String toString()
    public final native void notify()
    public final native void notifyAll()
    public ShowMethods()    //该类提供的公共方法
    *///:~

Class.forName()生成的结果在编译时是不可知的,因此所有的方法特征签名都是在执行时被提取的。反射机制提供了足够的支持,使得能够创建一个在编译时完全未知的对象,并调用此对象的方法。
注意:上述代码中Method类的toString()一般打印结果如下:

public boolean java.lang.Object.equals(java.lang.Object)

访问修饰符按照由 “The Java Language Specification” 指定的规范化顺序放置。首先是 public,protected 或 private,接着是按以下顺序的其他修饰符:

abstract、static、final、synchronized、native。

因此需要使用正则表达式去除最后一个.之前的内容

5.3 Constructor使用

5.3.1 Constructor类

Constructor类型的继承层次如下:
Constructor继承层次
从上图的继承层次可以看出,Constructor与Method类非常相像,因为两者共有相同的祖先和继承层次,只不过Constructor建模的是类型的构造器,而构造器相对于类中成员方法而言具有更多的限制,比如说构造器没有返回值,即没有getReturnType()方法。
Constructor 提供关于类的单个构造方法的信息以及对它的访问权限。
Constructor 允许在将实参与带有底层构造方法的形参的 newInstance() 匹配时进行扩展转换,但是如果发生收缩转换,则抛出 IllegalArgumentException。
调用Constructor中的构造函数可以使用newInstance()方法,

//使用此 Constructor 对象表示的构造方法来创建该构造方法的声明类的新实例,
并用指定的初始化参数初始化该实例。个别参数会自动解包,以匹配基本形参,
必要时,基本参数和引用参数都要进行方法调用转换。
public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
{

5.3.2 实例

            //获得全部的构造方法
    		Constructor[] ct=c.getDeclaredConstructors();
    		for(Constructor cc:ct) {
    		System.out.println(cc);
    		}
    		System.out.println("--------------------------");
    		//获得全部公有的构造方法
    		Constructor[] ct1=c.getConstructors();
    		for(Constructor cc:ct1) {
    			System.out.println(cc);
    		}
    		System.out.println("--------------------------");
    		//获得指定的构造方法
    		Constructor ct2=c.getConstructor(Integer.class, Integer.class, String.class, Double.class, String.class);
    		System.out.println(ct2);

5.4 综合程序

RTTI类型层次结构图

5.4.1 阐述

在上述的结构图中,可以看到在反射过程中使用的主要类型结构的继承关系。在实际过程中,我们主要使用的类型有Constructor、Field、Method和Class类。对于Member, Modifier, AccessibleObject以及Package等类的介绍在不在本文中陈述。下属程序为一个相对复杂的程序,演示了使用Java程序提取一个类中所有信息,包括类的描述符,构造函数,域,成员方法等常用信息。

5.4.2 实例

    import java.lang.reflect.*;
    
    public class SimpleClassViewer {
         public static void main(String[] args) { 
            try {
                Class c = Class.forName(args[0]);
                // 取得包代表对象
                Package p = c.getPackage();
                System.out.printf("package %s;%n", p.getName());
                // 取得类型修饰,像是class、interface
                int m = c.getModifiers();
                
                System.out.print(Modifier.toString(m) + " ");
                // 如果是接口
                if(Modifier.isInterface(m)) {
                    System.out.print("interface ");
                }
                else {
                    System.out.print("class ");
                }
                
                System.out.println(c.getName() + " {");
    
                // 取得声明的域成员代表对象
                Field[] fields = c.getDeclaredFields();
                for(Field field : fields) {
                    // 显示权限修饰,像是public、protected、private
                    System.out.print("\t" + 
                        Modifier.toString(field.getModifiers()));
                    // 显示类型名称
                    System.out.print(" " + 
                        field.getType().getName() + " ");
                    // 显示域成员名称
                    System.out.println(field.getName() + ";");
                }
    
                // 取得声明的构造函数代表对象            
                Constructor[] constructors = 
                                c.getDeclaredConstructors();
                for(Constructor constructor : constructors) {
                    // 显示权限修饰,像是public、protected、private
                    System.out.print("\t" + 
                         Modifier.toString(
                           constructor.getModifiers()));
                    // 显示构造函数名称
                    System.out.println(" " + 
                          constructor.getName() + "();");
                }
                // 取得声明的方法成员代表对象             
                Method[] methods = c.getDeclaredMethods();
                for(Method method : methods) {
                    // 显示权限修饰,像是public、protected、private
                    System.out.print("\t" + 
                         Modifier.toString(
                                  method.getModifiers()));
                    // 显示返回值类型名称
                    System.out.print(" " + 
                         method.getReturnType().getName() + " ");
                    // 显示方法名称
                    System.out.println(method.getName() + "();");
                }
                System.out.println("}");
            }
            catch(ArrayIndexOutOfBoundsException e) {
                System.out.println("没有指定类");
            }
            catch(ClassNotFoundException e) {
                System.out.println("找不到指定类");
            }
        }
    }

如果传入命令行的参数为

    java.lang.String

则输出结果如下:

    package java.lang;
    public final class java.lang.String {
    	private final [C value;
    	private int hash;
    	private static final long serialVersionUID;
    	private static final [Ljava.io.ObjectStreamField; serialPersistentFields;
    	public static final java.util.Comparator CASE_INSENSITIVE_ORDER;
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	 java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public java.lang.String();
    	public boolean equals();
    	public java.lang.String toString();
    	public int hashCode();
    	public int compareTo();
    	public volatile int compareTo();
    	public int indexOf();
    	public int indexOf();
    	public int indexOf();
    	public int indexOf();
    	static int indexOf();
    	static int indexOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public static java.lang.String valueOf();
    	public char charAt();
    	private static void checkBounds();
    	public int codePointAt();
    	public int codePointBefore();
    	public int codePointCount();
    	public int compareToIgnoreCase();
    	public java.lang.String concat();
    	public boolean contains();
    	public boolean contentEquals();
    	public boolean contentEquals();
    	public static java.lang.String copyValueOf();
    	public static java.lang.String copyValueOf();
    	public boolean endsWith();
    	public boolean equalsIgnoreCase();
    	public static transient java.lang.String format();
    	public static transient java.lang.String format();
    	public void getBytes();
    	public [B getBytes();
    	public [B getBytes();
    	public [B getBytes();
    	public void getChars();
    	 void getChars();
    	private int indexOfSupplementary();
    	public native java.lang.String intern();
    	public boolean isEmpty();
    	public static transient java.lang.String join();
    	public static java.lang.String join();
    	public int lastIndexOf();
    	public int lastIndexOf();
    	static int lastIndexOf();
    	public int lastIndexOf();
    	public int lastIndexOf();
    	static int lastIndexOf();
    	private int lastIndexOfSupplementary();
    	public int length();
    	public boolean matches();
    	private boolean nonSyncContentEquals();
    	public int offsetByCodePoints();
    	public boolean regionMatches();
    	public boolean regionMatches();
    	public java.lang.String replace();
    	public java.lang.String replace();
    	public java.lang.String replaceAll();
    	public java.lang.String replaceFirst();
    	public [Ljava.lang.String; split();
    	public [Ljava.lang.String; split();
    	public boolean startsWith();
    	public boolean startsWith();
    	public java.lang.CharSequence subSequence();
    	public java.lang.String substring();
    	public java.lang.String substring();
    	public [C toCharArray();
    	public java.lang.String toLowerCase();
    	public java.lang.String toLowerCase();
    	public java.lang.String toUpperCase();
    	public java.lang.String toUpperCase();
    	public java.lang.String trim();
    }

上述代码演示了整体的使用这几种类型来提取一个类中的常见信息,这也是IDE中智能提示的基础。

6 总结

在日常的编程生活中,反射作为Java的高级特性,较为使用不到,但对于更深的理解各类Java框架,反射是必须掌握的基础知识。对有过过程化编程背景的人来说,很难让他们不把程序组织成一系列switch语句。但可以使用RTTI做到这一点。面向对象编程语言的目的是让我们在凡是可以使用的地方都使用多态机制,只在必须的时候使用RTTI。
RTTI有时能解决效率问题,也许你的程序漂亮地运用了多态,但其中某个对象以极端缺乏效率的方式达到这个目的的。你可以挑出这个类,使用RTTI,并且为其编写一段特别的代码以提高效率。然而

注意:不要过早地关注程序的效率问题,这是个诱人的陷阱。最好是首先让程序运作起来,然后再考虑它的速度。 ----参见Java编程思想-类型信息-p351

重构与性能冲突:重构时不必担心性能,优化时才需要在意,但那时你已经处于有利的位置,有更多选择可以完成有效优化。

除了对性能有严格要求的实时系统,其他任何情况下“编写快速软件”的秘密就是:首先写出可调的软件,然后调整它以求得足够速度。 ----参见重构 改善既有代码的设计-p69

7 引用

https://www.cnblogs.com/crazylqy/p/10191186.html
https://www.jianshu.com/p/11039acff57c

8 下载

https://download.csdn.net/download/lk142500/10921261
https://download.csdn.net/download/lk142500/10921168

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值