"java 反射 1. 什么是反射 reflect java 虚拟机加载完一个类之后,会在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,所以我们能通过这个Class对象,在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个 ...."

java 反射

java 反射

1. 什么是反射 reflect

java 虚拟机加载完一个类之后,会在堆内存中会产生一个Class类型的对象(一个类只有一个Class对象),这个对象包含了完整的类的结构信息,所以我们能通过这个Class对象,在运行时,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。而且这个Class对象就像一面镜子,透过这个镜子看到类的结构,所以被称之为:反射。

程序中一般的对象的类型都是在编译期就确定下来的,而Java反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。反射的核心是JVM在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

重点:是运行时而不是编译时

2. 反射能做什么

java 的反射机制可能对于专注于应用层开发的同学来说可能了解的比较少,但其实我们在应用开发过程中用到的各种框架,无论是在android 端,还是在后台的各种框架,都是基于反射的原理的。比如说:fastjson, DBExecutor, Hibernate, spring 等。了解java的反射机制,才能深入的理解各框架的原理,甚至可以自己去造轮子。附自己造的一个轮子:DBHelper,看完本文后,想找个轮子来分析一下的话,可以拿来看看,逻辑还是比较简单的。

即使你只专注于应用层的开发,当你掌握了反射的方法后,会发现一些以前是不可能的事情,会变得很简单:

  • 调用 android framework 层的 hide 方法。虽然不提倡,但有时候确实是很有用,做launcher开发的同学可能有体会
  • 修改一些 private 属性的类成员。没办法,某些API设计得不合理,但我们又无法修改
  • apk 动态加载,插件开发

3. 反射的基本使用方法

我们先定义这么一个类,以便后续知识点的开展:

package com.shaoqiu.reflect;
public class User {
    private String name;
    private String passwd;
    public User() {
        this.name = "user";
        this.passwd = "passwd";
    }
    public User(String name, String passwd) {
        this.name = name;
        this.passwd = passwd;
    }
    public String getPasswd() {
        return passwd;
    }
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    private String privateMethod(String msg) {
        return msg;
    }
}

在这个类的基础上,可以进行以下这些操作。

3.1. 获取 Class 对象

User user = new User();
Class<?> claz;
//如果有这个类的实例
claz = user.getClass();
//如果在编译时能知道类的名字
claz = User.class;
//如果只能在运行时获取类的名字,类的名字必须为全名,包括包名
claz = Class.forName("com.shaoqiu.reflect.User");

有了 Class 对象了,我们就可以做以下的操作了。

3.2. 获取包名,类名

System.out.println("package name = " + claz.getPackage());
System.out.println("class name = " + claz.getName());
System.out.println("simple class name = " + claz.getSimpleName());

3.3. 获取父类,及实现的接口

Class superClass = claz.getSuperclass();
Class[] interfaces = claz.getInterfaces();

注意:getInterfaces()方法仅仅只返回当前类所实现的接口。当前类的父类如果实现了接口,这些接口是不会在返回的 Class 集合中的。

3.4. 获取构造函数,创建类的对象

Constructor getConstructor(Class[] params) 
Constructor[] getConstructors()
Constructor getDeclaredConstructor(Class[] params) 
Constructor[] getDeclaredConstructors()

一个类实际上可以拥有很多个构造函数。那么我们获取的构造函数是哪个呢?我们可以根据构造函数的参数标签对构造函数进行明确的区分,因此,如果我们在Java反射时指定构造函数的参数,那么我们就能确定地返回我们需要的那个“唯一”的构造函数。getConstructor(Class[] params)getDeclaredConstructor(Class[] params)正是这种确定唯一性的方式。但是,如果我们不清楚每个构造函数的参数表,或者我们出于某种目的需要获取所有的构造函数的信息,那么我们就不需要明确指定参数表,而这时返回的就应该是构造函数数组,因为构造函数很可能不止一个。getConstructors()getDeclaredConstructors()就是这种方式。

另外,我们还可以通过构造函数的访问权限进行分类。在设计类的时候,我们往往有一些构造函数需要声明为privateprotect或者default,目的是为了不让外部的类调用此构造函数生成对象。于是,基于访问权限的不同,我们可以将构造函数分为public和非public两种。
getConstructor(Class[] params)getConstructors()仅仅可以获取到public的构造函数,而getDeclaredConstructor(Class[] params)getDeclaredConstructors()则能获取所有(包括public和非public)的构造函数。

通过获取到的构造函数我们可以创建一个类的对象。

//指定参数类型个数
Constructor<?> constructor = claz.getConstructor(String.class, String.class);
User shaoqiu = (User) constructor.newInstance("shaoqiu", "qiushao");
System.out.println("name = " + shaoqiu.getName());
System.out.println("passwd = " + shaoqiu.getPasswd());

3.5. 成员变量的获取及访问

Field getField(String name)
Field[] getFields()
Field getDeclaredField(String name)
Field[] getDeclaredFields()

name参数为成员变量名。关于访问权限和确定性的问题,与构造函数基本一致。
成员变量的获取及访问例子:

Field name = claz.getField("name");
name.setAccessible(true);
name.set(user, "admin");
System.out.println("name = " + name.get(user));

需要注意的是这几个方法能获取到的只是当前类声明的成员变量而已,父类中的public成员变量是不能获取到的,如果需要获取继承树中的某个父类的成员变量,则需要先获取父类class,再查询,不断的递归,直到找到所需要的成员变量。Method的获取同理。
对于静态变量来说,调用方法也是一样的,只不过是将对象换成null而已。对于枚举类来说也是一样的调用。我们就把枚举类当作一个普通的类就可以。枚举变量都是静态变量。

3.6. 方法的获取及调用

Method getMethod(String name, Class[] params)
Method[] getMethods()
Method getDeclaredMethod(String name, Class[] params) 
Method[] getDeclaredMethods()

name为方法名,params 为方法的参数类型。关于访问权限和确定性的问题,和构造函数基本一致。方法的获取及调用例子:

Method method = claz.getDeclaredMethod("privateMethod", String.class);
method.setAccessible(true);
String result = (String) method.invoke(user, "just a test");
System.out.println("method invoke result = " + result);

invoke的第一个参数是类实例对象,如果需要调用的是类的静态方法,则传一个null值即可。

3.7. 获取修饰符

修饰符即 private, protected, public, static, final ...这些属性
这些属性可以是类的,方法的,域的,它们的使用方法都是相同的。下面的操作是获取一个类的修饰符,我们也可以在方法,域上使用相同的方法获取其修饰符。

int modifiers = claz.getModifiers();

修饰符都被包装成一个int类型的数字,这样每个修饰符都是一个位标识(flag bit),这个位标识可以设置和清除修饰符的类型。可以使用java.lang.reflect.Modifier类中的方法来检查修饰符的类型:

Modifier.isAbstract(int modifiers);
Modifier.isFinal(int modifiers);
Modifier.isInterface(int modifiers);
Modifier.isNative(int modifiers);
Modifier.isPrivate(int modifiers);
Modifier.isProtected(int modifiers);
Modifier.isPublic(int modifiers);
Modifier.isStatic(int modifiers);
Modifier.isStrict(int modifiers);
Modifier.isSynchronized(int modifiers);
Modifier.isTransient(int modifiers);
Modifier.isVolatile(int modifiers);

如果我们想要把标识位转换成字符串,则需要使用

//结果为 public, private, static 之类的
Modifier.toString(int modifiers)

3.8. 类,域,方法的注解获取

我们可以在一个类,方法,域上获取其上的注解。他们的使用方法是类似的。关于注解的内容将单独在另外一篇笔记中讲解。

// Table类是一个自定义注解
Table table = claz.getAnnotation(Table.class);
// 如果没有指定类型的注解,则返回null
if (null != table && !table.value().isEmpty()) {
    System.out.println(table.value());
} else {
    System.out.println("no Table annotation on this class");
}

4. 一个完整的例子

反射的使用其实挺简单的,来看个例子就一清二楚了。
User.java:

package com.shaoqiu.reflect;
public class User {
    private String name;
    private String passwd;
    public User() {
        this.name = "user";
        this.passwd = "passwd";
    }
    public User(String name, String passwd) {
        this.name = name;
        this.passwd = passwd;
    }
    public String getPasswd() {
        return passwd;
    }
    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    private String privateMethod(String msg) {
        return msg;
    }
}

Main.java:

package com.shaoqiu.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

public class Main {
    public static void main(String[] args) {
        try {
            User user = new User("shaoqiu", "qiushao");
            Class<?> claz;
            claz = user.getClass();
            claz = User.class;
            claz = Class.forName("com.shaoqiu.reflect.User");

            System.out.println("package name = " + claz.getPackage());
            System.out.println("class name = " + claz.getName());
            System.out.println("simple class name = " + claz.getSimpleName());

            Class<?> superClass = claz.getSuperclass();
            System.out.println("superclass = " + superClass.getName());
            Class<?>[] interfaces = claz.getInterfaces();
            System.out.println("interfaces:");
            for (Class<?> i : interfaces) {
                System.out.print(i.getName() + " ");
            }
            System.out.println("");

            Constructor<?> constructor = claz.getConstructor(String.class, String.class);
            user = (User) constructor.newInstance("shaoqiu", "qiushao");
            System.out.println("name = " + user.getName());
            System.out.println("passwd = " + user.getPasswd());

            Field name = claz.getDeclaredField("name");
            name.setAccessible(true);
            name.set(user, "admin");
            System.out.println("name = " + name.get(user));

            Method method = claz.getDeclaredMethod("privateMethod", String.class);
            method.setAccessible(true);
            String result = (String) method.invoke(user, "just a test");
            System.out.println("method invoke result = " + result);

            int modifier = claz.getModifiers();
            System.out.println("class modifier = " + Modifier.toString(modifier));
            modifier = method.getModifiers();
            System.out.println("methoed modifier = " + Modifier.toString(modifier));

        } catch (ClassNotFoundException 
                | NoSuchMethodException 
                | SecurityException 
                | InstantiationException 
                | IllegalAccessException 
                | IllegalArgumentException 
                | InvocationTargetException 
                | NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

output:

package name = package com.shaoqiu.reflect
class name = com.shaoqiu.reflect.User
simple class name = User
superclass = java.lang.Object
interfaces:

name = shaoqiu
passwd = qiushao
name = admin
method invoke result = just a test
class modifier = public
methoed modifier = private

5. 相关资料

*参见java官方文档
http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html

*反射相关教程
http://ifeve.com/java-reflection/
http://www.sczyh30.com/posts/Java/java-reflection-1/

0     0     0     0     0    
0 回帖