java 知识之 反射的使用


所谓反射,是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

反射有如下的功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;
  • 生成动态代理。

这里的反射主要通过Class类来实现。

一、Class类的使用

Class类的实例表示java 应用运行时的类或者接口,包括数组、基本类型(int/long/Indeger/String/...)、关键字(void/...)等。没有共有构造方法,在使用new创建对象或者加载ClassLoader时,由JVM自动调用。

每个java类运行时都在JVM里表现为一个class对象,可通过类名.class,类型.getClass(),Class.forName(“类名”)等方法获取class对象。

可以通过下面三种方式获得Class对象:

1.通过 类名.class 获取,可以认为每个类都有一个静态的Class对象。

Class c1 = Person.class;

2.通过类的对象获取

 Person person = new Person();
 Class c2 = person.getClass();

3.通过类名获取,动态加载(这个再很多地方都有使用,如:加载数据库驱动)

 Class c3 = null;
    try {
        c3 = Class.forName("Person");
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

上面的c1/c2是Person的类类型,Person是Class类的对象。可以理解为一切类都是Class的对象

打印上面三种方法获取的Class对象:

System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
System.out.println(c2 == c3);//true

上面三种方式获取的Class对象,两两均相等。可见,可以理解为每个对象都有一个静态的Class变量。因为在java 中,Object 是一切类的父类,在Object中有一个获取Class对象的native(由C++或C语言实现,通过java 调用,具体的可参见jni相关的知识)方法。如下部分Object开头源码:

 public class Object {

     // ...

     /**
     * Returns the runtime class of this {@code Object}. The returned
     * {@code Class} object is the object that is locked by {@code
     * static synchronized} methods of the represented class.
     *
     * <p><b>The actual result type is {@code Class<? extends |X|>}
     * where {@code |X|} is the erasure of the static type of the
     * expression on which {@code getClass} is called.</b> For
     * example, no cast is required in this code fragment:</p>

     * @return The {@code Class} object that represents the runtime
     *         class of this object.
     *         
 */
    public final native Class<?> getClass();

   // ...

通过上面 getClass 方法前的部分注释,可以清楚的明白他的作用。

二、动态加载类

在java 中,使用new创建的对象都是静态加载的,这些类必须存在,不管在实际中是否使用,否则编译不通过。可以通过动态加载类来解决该问题。
通过Class类的forName方法进行加载,传入相应的名加载具体的类,可以使程序的扩展性更好,避免编译出错。

该方法的原型为:

  public static Class<?> forName(String className)

三、使用Class获取类信息

Class类提供的部分获取类信息的方法如下:

方法名 说明
getFields() 获取所有public类型的成员变量
getDeclaredFields() 获取该类自己声明的所有变量
getConstructors() 获取所所有public类型的构造方法
getDeclaredConstructors() 获取所有的构造方法
getMethods() 获取所有的public函数,包括父类继承来的
getDeclaredMethods 获取该类所有声明的方法

上面分别为获取成员变量、构造方法和成员方法。

获取成员参数

public class ClassUtils {
    public static void getClassField(Object obj) {
            Class c = obj.getClass();
            Field[] fs = c.getDeclaredFields();// c.getMethods();
            for (Field f : fs) {
                Class fieldType = f.getType();
                System.out.println(fieldType.getName() + " " + f.getName());
            }
        }
}

上述代码是获取所有成员变量的,获取的每个成员方法都是 Fidld 类型的对象。
如下测试代码获取String类中的成员变量:

ClassUtils.getClassField("reflect");

运行结果:

[C value
int hash
long serialVersionUID
[Ljava.io.ObjectStreamField; serialPersistentFields
java.util.Comparator CASE_INSENSITIVE_ORDER

其中第一个和第三个表示的是数组。查看String中的源码,成员变量如下:

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {

    private final char value[];

    private int hash; // Default to 0

    private static final long serialVersionUID = -6849794470754667710L;

    private static final ObjectStreamField[] serialPersistentFields =
            new ObjectStreamField[0];

    //...
     public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();

    //...
}

同样的可以调用该方法查看其他类中的成员变量。

获取构造方法和成员方法

类似于上面获取成员变量的方法,如下获取构造函数和成员方法则很好理解了。

public class ClassUtils {
    //...

    /**
     * 获取所有的成员方法
     *
     * @param obj
     */
    public static void getClassMethods(Object obj) {
        Class c = obj.getClass();
        System.out.println("类名为:" + c.getName());
        Method[] ms = c.getDeclaredMethods();

        for (Method m : ms) {
            Class returnType = m.getReturnType();
            //返回值的类类型,如String-->String.class
            System.out.print(returnType.getName() + " ");
            //方法名称
            System.out.print(m.getName() + "(");
            //获取参数--参数列表的类类型
            Class[] paramTypes = m.getParameterTypes();
            int paramsNum = paramTypes.length;
            Class paramType;
            for (int i = 0; i < paramsNum; i++) {
                paramType = paramTypes[i];
                if (i == 1 || (i == paramsNum - 1))
                    System.out.print(paramType.getName());
                else
                    System.out.print(paramType.getName() + " ,");
            }
            System.out.println(")");
        }
    }
    /**
     * 获取所有的构造函数
     *
     * @param obj
     */
    public static void printConMessage(Object obj) {
        Class c = obj.getClass();
        Constructor[] cs = c.getDeclaredConstructors();
        for (Constructor constructor : cs) {
            System.out.print(constructor.getName() + " (");
            Class[] paramTypes = constructor.getParameterTypes();
            int paramsNum = paramTypes.length;
            Class paramType;
            for (int i = 0; i < paramsNum; i++) {
                paramType = paramTypes[i];
                if (i == 1 || (i == paramsNum - 1))
                    System.out.print(paramType.getName());
                else
                    System.out.print(paramType.getName() + " ,");
            }
            System.out.println(")");
        }
    }
}

此处省略测试。

四、方法反射的基本操作

对于方法的获取,可以通过方法名称和参数列表来确定某个方法。主要使用了 Method.invoke(对象,参数列表); 操作。

首先需要获取类信息,然后指定方法名和参数获取方法对象,再通过Method.invoke()进行反射获取方法执行结果。若该方法无返回值,则invoke方法最后返回null,否则返回相应的结果

如下示例代码:

public class TestDemo {
    public static void main(String[] args) {
        //1.获取类信息
        A a = new A();
        Class aClass = a.getClass();
        //2.获取方法名称和参数
        try {
//            Method method = aClass.getMethod("print",new Class[]{int.class,int.class});
            Method method = aClass.getMethod("print", int.class, int.class);
            //方法返回结果:若无返回结果则o为null,否则为返回结果,这里返回为30
            Object o = method.invoke(a, 10, 20);
            System.out.println(o);//30
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}
//测试反射方法
class A {
    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.toUpperCase());
    }
}

其中 Method()和invoke()方法第二个参数为可变数组,可有可无,如果有接写。可以通过new数组的方式或者直接一个个写出。

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

我们都知道,在java中,定义集合指定什么泛型,就只能添加该类型的数据到集合中,否则就会报错。

通过反射可以往集合中添加不同类型的数据,这样虽没有多大意义,不便于遍历。这里主要说明了集合在编译后是去泛型化的,集合的泛型可以防止错误输入,在编译极前有效。

如下测试代码:

public class TestDemo04 {
    public static void main(String[] args) {
        ArrayList<String> l1 = new ArrayList<>();
        ArrayList l2 = new ArrayList();
        l1.add("java reflect");
//        datas.add(100);//报错
        Class c1 = l1.getClass();
        Class c2 = l2.getClass();
        System.out.println(c1 == c2);//true
        try {
            Method method = c2.getMethod("add", Object.class);
            method.invoke(l1, 100);
            System.out.println("数组大小:"+l1.size());
            System.out.println("数组内容:"+l1.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

输出结果:

true
数组大小:2
数组内容:[java reflect, 100]

上面的代码,通过反射往String类型的集合中添加了int类型的100。

通过对反射的学习,发现反射是从.class到.java的过程,感觉像反编译字节码。以 Class c1 = Person.class;为例,Person.java类在编译后生成的字节码文件为Person.class,这里获取的Class正式这样的。


文章作者: imtianx
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC 4.0 许可协议。转载请注明来源 imtianx !
评论
 上一篇
java 知识之 注解的使用和解析 java 知识之 注解的使用和解析
在java中,例如重写父类方法使用的 @Override,就是注解。在开发中使用的框架,大部分也是用了注解。通过注解可以是代码更加简洁,更加清晰。在jdk1.5后,引入了注解。官方概念:java提供了已汇总源程序中的元素关联任何信息和任何元
下一篇 
java 知识 之 内存管理 java 知识 之 内存管理
读书笔记:《疯狂java 程序员的基本修养》第四章——java内存管理 java 中的内存管理包括内存分配和内存回收,这些都是由 JVM 帮我们完成的。当创建对象时为其分配内存空间;当失去引用时,GC会自动清除并回收他们所占用的空间。
  目录