java反射机制

反射 (reflective)是可以在运行时加载、使用编译期间完全未知的类。

  • 程序在运行状态中,可以动态加载一个只有名称的类

  • 对于任意一个已经加载的类,都能够知道这个类的所有属性和方法; 对于任意一个对象,都能调用他的任意一个方法和属性

  • 加载完类之后, 在堆内存中会产生一个java.lang.Class类型的对象(一个类只有一个Class对象), 这个对象包含了完整的类的结构信息,通过该Class对象就可以访问到JVM中的这个类

Class

在程序运行期间,Java运行时系统始终为所有的对象维护一个被称为运行时的类型标识。

这个信息跟踪着每个对象所属的类。

虚拟机利用运行时类型信息选择相应的方法执行。

我们也可以通过专门的Java类访问这些信息,这个类为java.lang.Class。

类Class的实例表示正在运行的Java应用程序中的类和接口。 (Java有 5种引用类型/对象类型:类 接口 数组 枚举 标注)

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

得到一个Class对象

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

1、通过Class的静态方法forName(String className)获取:体现反射的动态性,className需要是类的全限定名
Class<?> clazz = Class.forName("com.codersm.study.jdk.reflect.Person");

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

2、通过一个类的对象的getClass()方法
Class<?> clazz = new Person().getClass();

3、Class字面常量,调用运行时类本身的class属性

Class<Person> clazz = Person.Class;

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

4、通过类的加载器

String className = "java.util.commons";

ClassLoader classLoader = this.getClass().getClassLoader();

Class clazz = classLoader.loadClass(className);

Class类的方法

Class类提供了大量的实例方法来获取该Class对象所对应的详细信息,Class类大致包含如下方法,其中每个方法都包含多个重载版本

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

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

  • 是否是注解类型
    boolean isAnnotation()
  • 是否使用了该注解修饰
    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对象直接访问和修改对象的成员变量值。

Method Constructor Field这些类都实现了java.lang.reflect.Member接口,程序可以通过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);//静态方法invoke的一个参数是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类中与数组关联的方法是:

  • 返回表示数组元素类型的Class,即数组的类型
    public native Class<?> getComponentType()
  • 判定此Class对象是否表示一个数组类
    public native boolean isArray()

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.ParameterizedType、 java.lang.reflect.GenericArrayType、java.lang.reflect.TypeVariable、java.lang.reflect.WildcardType。

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

其中, 我们可以使用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();

        // 打印getType与getGenericType的区别
 //getType() 和 getGenericType()的区别 :
//1.首先是返回的类型不一样,getType返回一个 Class 对象,getGenericType返回一个 Type接口。
//2.如果属性是一个泛型,从getType()只能得到这个属性的接口类型。但从getGenericType()还能得到这个泛型的参数类型。
//3.getGenericType()如果当前属性有签名属性类型就返回,否则就返回 Field.getType()。

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

        }

    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,468评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,620评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,427评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,160评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,197评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,334评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,775评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,444评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,628评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,459评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,508评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,210评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,767评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,850评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,076评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,627评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,196评论 2 341

推荐阅读更多精彩内容