Java基础知识系列—Java反射

能够分析类能力的程序称为反射(reflective)。反射机制的功能非常强大,主要提供了如下功能:

  • 对于任意一个类,都能够知道这个类的所有属性和方法;

  • 对于任意一个对象,都能够调用它的任意方法和属性;

Class类

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。这个信息跟踪着每个对象所属的类。虚拟机利用运行时类型信息选择相应的方法执行。然而,可以通过专门的Java类访问这些信息,这个类为java.lang.Class

类Class的实例表示正在运行的Java应用程序中的类和接口。 枚举是一种类,一个注释是一种接口。每个数组也属于一个类,它被反映为具有相同元素类型和维数的所有数组共享的Class对象。 原始Java类型(boolean,byte,char,short,int,long,float和double)和关键字void也表示为Class对象。

Class类没有公共构造函数。Class对象由Java虚拟机自动构建,因为加载了类,并且通过调用类加载器中的defineClass方法。

虽然我们不能new一个Class对象,但是却可以通过已有的类得到一个Class对象,共有如下三种方式:

1、Class类的forName方法

 Class<?> clazz = Class.forName("com.codersm.study.jdk.reflect.Person");

2、通过一个类的对象的getClass()方法

 Class<?> clazz = new Person().getClass();

3、Class字面常量

 Class<Person> clazz = Person.Class;

小结:

  1. 注意调用forName方法时需要捕获一个名称为ClassNotFoundException的异常,因为forName方法在编译器是无法检测到其传递的字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出ClassNotFoundException异常。

  2. Class字面常量这种方法更加简单,更安全。因为它在编译器就会受到编译器的检查同时由于无需调用forName方法效率也会更高,因为通过字面量的方法获取Class对象的引用不会自动初始化该类? 更加有趣的是字面常量的获取Class对象引用方式不仅可以应用于普通的类,也可以应用用接口,数组以及基本数据类型。

Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本,因此我们只是做简单的介绍,详细请参考JDK文档

  • 获取类信息

    获取内容方法签名
    构造器Constructor<T> getConstructor(Class<?>... parameterTypes)
    方法Method getMethod(String name, Class<?>... parameterTypes)
    属性Field getField(String name)
    Annotation<A extends Annotation> A getAnnotation(Class<A> annotationClass)
    内部类Class<?>[] getDeclaredClasses()
    外部类Class<?> getDeclaringClass()
    实现的接口Class<?>[] getInterfaces()
    修饰符int getModifiers()
    所在包Package getPackage()
    类名String getName()
    简称String getSimpleName()

    注: getDeclaredXxx方法可以获取所有的Xxx,无论private/public。

  • 判断类本身信息的方法

    判断内容方法签名
    注解类型?boolean isAnnotation()
    使用了该Annotation修饰?boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)
    匿名类?boolean isAnonymousClass()
    数组?boolean isArray()
    枚举?boolean isEnum()
    原始类型?boolean isPrimitive()
    接口?boolean isInterface()
    obj是否是该Class的实例boolean isInstance(Object obj)
  • 使用反射生成并操作对

    • [ ]程序可以通过Method对象来执行相应的方法;

    • [ ]通过Constructor对象来调用对应的构造器创建实例;

    • [ ]通过Filed对象直接访问和修改对象的成员变量值。

创建对象

通过反射来生成对象的方式有两种:

  1. 使用Class对象的newInstance()方法来创建该Class对象对应类的实例(这种方式要求该Class对象的对应类有默认构造器)

  2. 先使用Class对象获取指定的Constructor对象, 再调用Constructor对象的newInstance()方法来创建该Class对象对应类的实例(通过这种方式可以选择指定的构造器来创建实例)

class Person {

    private String name;

    private Integer age;

    public Person() {
        this.name = "system";
        this.age = 99;
    }

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

    public String getName() {
        return name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}


public class Test {

    public static void main(String[] args) throws Exception {
        Class<Person> pClass = Person.class;
        // 通过第1种方式创建对象
        Person p = pClass.newInstance();
        System.out.println(p);
        // 通过第2种方式创建对象
        Constructor<Person> constructor = pClass.getDeclaredConstructor(String.class, Integer.class);
        Person person2 = constructor.newInstance("zhangsan",20);
        System.out.println(person2);
    }
}

调用方法

当获取到某个类对应的Class对象之后, 就可以通过该Class对象的getMethod来获取一个Method数组或Method对象.每个Method对象对应一个方法,在获得Method对象之后,就可以通过调用invoke方法来调用该Method对象对应的方法。

    Person person = new Person();
    // 获取getAge方法
    Method getAgeMethod = person.getClass().getMethod("getAge",null);
    // 调用invoke方法来调用getAge方法
    Integer age = (Integer) getAgeMethod.invoke(person,null);
    System.out.println(age);

访问成员变量

通过Class对象的的getField()方法可以获取该类所包含的全部或指定的成员变量Field,Filed提供了如下两组方法来读取和设置成员变量值.

  1. getXxx(Object obj): 获取obj对象的该成员变量的值, 此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型, 则取消get后面的Xxx;

  2. setXxx(Object obj, Xxx val): 将obj对象的该成员变量值设置成val值.此处的Xxx对应8种基本类型, 如果该成员类型是引用类型, 则取消set后面的Xxx;

 Person person = new Person();
 // 获取name成员变量Field
 Field nameField = person.getClass().getDeclaredField("name");
 // 启用访问控制权限
 nameField.setAccessible(true);
 // 获取person对象的成员变量name的值
 String name = (String) nameField.get(person);
 System.out.println("name = " + name);
 // 设置person对象的成员变量name的值
 nameField.set(person, "lisi");
 System.out.println(person);

操作数组

在Java的java.lang.reflect包中存在着一个可以动态操作数组的类,Array提供了动态创建和访问Java数组的方法。Array允许在执行get或set操作进行取值和赋值。在Class类中与数组关联的方法是:

方法说明
public native Class getComponentType()返回表示数组元素类型的Class,即数组的类型
public native boolean isArray()判定此Class对象是否表示一个数组类

java.lang.reflect.Array中的常用静态方法如下:

  • newInstance(Class<?> componentType, int length)

  • newInstance(Class<?> componentType, int... dimensions)

  • int getLength(Object array)

  • Object get(Object array, int index)

  • void set(Object array, int index, Object value)

实现通用数组复制功能,其代码如下:

public class GenericityArray {

    public static <T> T[] copy(T[] clazz) {
        return (T[]) Array.newInstance(
                        clazz.getClass().getComponentType(), 
                        clazz.length);
}

    public static void main(String[] args) {
        Integer[] array = {1, 2, 3};
        Integer[] copyArray = GenericityArray.copy(array);
        System.out.println(copyArray.length);
    }
}

使用反射获取泛型信息

为了通过反射操作泛型以迎合实际开发的需要, Java新增了java.lang.reflect.ParameterizedTypejava.lang.reflect.GenericArrayTypejava.lang.reflect.TypeVariablejava.lang.reflect.WildcardType

类型含义
ParameterizedType一种参数化类型, 比如Collection
GenericArrayType一种元素类型是参数化类型或者类型变量的数组类型
TypeVariable各种类型变量的公共接口
WildcardType一种通配符类型表达式, 如? extends Number

其中, 我们可以使用ParameterizedType来获取泛型信息.

public class Client {

    private Map<String, Object> objectMap;

    public void test(Map<String, User> map, String string) {
    }

    public Map<User, Bean> test() {
        return null;
    }

    /**
     * 测试属性类型
     *
     * @throws NoSuchFieldException
     */
    @Test
    public void testFieldType() throws NoSuchFieldException {
        Field field = Client.class.getDeclaredField("objectMap");
        Type gType = field.getGenericType();
        // 打印type与generic type的区别
        System.out.println(field.getType());
        System.out.println(gType);
        System.out.println("**************");
        if (gType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) gType;
            Type[] types = pType.getActualTypeArguments();
            for (Type type : types) {
                System.out.println(type.toString());
            }
        }
    }

    /**
     * 测试参数类型
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testParamType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test", Map.class, String.class);
        Type[] parameterTypes = testMethod.getGenericParameterTypes();
        for (Type type : parameterTypes) {
            System.out.println("type -> " + type);
            if (type instanceof ParameterizedType) {
                Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
                for (Type actualType : actualTypes) {
                    System.out.println("\tactual type -> " + actualType);
                }
            }
        }
    }

    /**
     * 测试返回值类型
     *
     * @throws NoSuchMethodException
     */
    @Test
    public void testReturnType() throws NoSuchMethodException {
        Method testMethod = Client.class.getMethod("test");
        Type returnType = testMethod.getGenericReturnType();
        System.out.println("return type -> " + returnType);

        if (returnType instanceof ParameterizedType) {
            Type[] actualTypes = ((ParameterizedType) returnType).getActualTypeArguments();
            for (Type actualType : actualTypes) {
                System.out.println("\tactual type -> " + actualType);
            }
        }
    }
}

使用反射获取注解

使用反射获取注解信息的相关介绍, 请参看Java注解实践

参考资料

  1. 深入理解Java类型信息(Class对象)与反射机制

  2. Java反射

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值