Java反射总结:入门进阶到使用

未经允许禁止转载,转载请联系作者。

目录

一 反射(Reflect)初识

二 反射的基本使用和常用API

2.1 基本使用

2.2 反射获取一个对象的步骤

2.3 反射常用API

2.3.1 获取反射中的Class对象

2.3.2 通过反射创建类对象

2.3.3 通过反射获取类属性、方法、构造器(初步)

三 反射小结

扫描二维码关注公众号,回复: 9948516 查看本文章

3.1 小结

3.2 反射中类加载器、构造器、Method、Field的进阶操作

3.2.1 对类加载器的操作:

3.2.2 对构造器的操作:

3.2.3 对Method、Field的操作:

3.2.4 对Field的操作:

四 反射进阶之深入剖析

4.1 Java反射机制的起源和入口:Class类

4.1.1 Class类简介

4.1.2 Java中类的加载过程

4.2 反射源码解析

五 反射方法的使用

5.1 通过反射运行配置文件内容

5.2 反射方法的其它使用之---通过反射越过泛型检查


一 反射(Reflect)初识

反射之中包含了一个「反」字,所以了解反射我们先从「正」开始。一般情况下,我们使用某个类时必定知道它是具体的什么类,然后再直接对这个类进行实例化后操作。

Apple apple = new Apple(); //直接初始化,「正射」
apple.setPrice(3);

反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用。

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

Class clz = Class.forName("com.test.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 3);

上面两段代码的执行结果一样,但思路完全不一样:

第一段代码在未运行时就已经确定了要运行的类:Apple;

第二段代码则是在运行时通过字符串值才得知要运行的类:com.test.reflect.Apple。

这就是反射。


二 反射的基本使用和常用API

2.1 基本使用

public class Apple {

    private int price;

    public int getPrice() {
        return price;
    }

    public void setPrice(int price) {
        this.price = price;
    }

    public static void main(String[] args) throws Exception{
        //正常的调用
        Apple apple = new Apple();
        apple.setPrice(3);
        System.out.println("Apple Price:" + apple.getPrice());
        //使用反射调用
        Class clz = Class.forName("com.test.api.Apple");
        Method setPriceMethod = clz.getMethod("setPrice", int.class);
        Constructor appleConstructor = clz.getConstructor();
        Object appleObj = appleConstructor.newInstance();
        setPriceMethod.invoke(appleObj, 3);
        Method getPriceMethod = clz.getMethod("getPrice");
        System.out.println("Apple Price:" + getPriceMethod.invoke(appleObj));
    }
}

从代码中可以看到我们使用反射调用了 setPrice 方法,并传递了 14 的值。之后使用反射调用了 getPrice 方法,输出其价格。上面的代码整个的输出结果是:

Apple Price:3
Apple Price:3

2.2 反射获取一个对象的步骤

通过上面这个简单的例子可以看出,一般情况下我们使用反射获取一个对象的步骤:

  1:获取类的 Class 对象实例

Class clz = Class.forName("com.zhenai.api.Apple");

   2:根据 Class 对象实例获取 Constructor 对象

Constructor appleConstructor = clz.getConstructor();

   3:使用 Constructor 对象的 newInstance 方法获取反射类对象

Object appleObj = appleConstructor.newInstance();

   4:而如果要调用某一个方法,则需要经过下面的步骤:

                获取方法的 Method 对象:

Method setPriceMethod = clz.getMethod("setPrice", int.class);

                利用 invoke 方法调用方法:

setPriceMethod.invoke(appleObj, 14);

2.3 反射常用API

2.3.1 获取反射中的Class对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。

在 Java API 中,获取 Class 类对象有三种方法:

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName("java.lang.String");

第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

Class clz = String.class;

第三种,使用类对象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

2.3.2 通过反射创建类对象

通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

第一种:通过 Class 对象的 newInstance() 方法。

Class clz = Apple.class;
Apple apple = (Apple)clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Apple.class;
Constructor constructor = clz.getConstructor();
Apple apple = (Apple)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clz = Apple.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Apple apple = (Apple)constructor.newInstance("红富士", 15);

2.3.3 通过反射获取类属性、方法、构造器(初步)

我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

Class clz = Apple.class;
Field[] fields = clz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

price

而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class clz = Apple.class;
Field[] fields = clz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}

输出结果是:

name
price

与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。


三 反射小结

3.1 小结

反射是什么:

通过上面例子,我们可以大致总结到:反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

Reflection(反射)是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法。

反射提供了什么功能:

  • 在运行时构造任意一个类的对象
  • 在运行时获取任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法/属性

反射为什么慢:

其实反射的效率并不慢,在某些情况下可能达到和直接调用基本相同的效率,但是在首次执行或者没有缓存的情况下还是会有性能上的开销,主要在以下方面

  1. Class.forName()会调用本地方法,我们用到的method和field都会在此时加载进来,虽然会进行缓存,但是本地方法免不了有JAVA到C++在到JAVA得转换开销(详情可见4.2:MethodAccessor的native版本和java版本)。
  2. class.getMethod(),会遍历该class所有的公用方法,如果没匹配到还会遍历父类的所有方法,并且getMethods()方法会返回结果的一份拷贝,所以该操作不仅消耗CPU还消耗堆内存,在代码中应该尽量避免,或者进行缓存。
  3. invoke参数是一个object数组,而object数组不支持java基础类型,而自动装箱也是很耗时的。

3.2 反射中类加载器、构造器、Method、Field的进阶操作

下面举个例子进一步介绍反射中对类加载器、构造器、Method、Field的操作。

首先定义一个自定义对象Person:

public class Person {
    String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
        System.out.println("this is setName()!");
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
        System.out.println("this is setAge()!");
    }

    //包含一个带参的构造器和一个不带参的构造器
    public Person(String name, int age) {
        super();
        this.name = name;
        this.age = age;
    }
    public Person() {
        super();
    }

    //私有方法
    private void privateMethod(){
        System.out.println("this is private method!");
    }
}

3.2.1 对类加载器的操作:

public class TestClassLoader {
    /*类加载器相关*/
    public static void testClassLoader() throws ClassNotFoundException,
            FileNotFoundException {
        //1. 获取一个系统的类加载器(系统类加载器)(可以获取,当前这个类PeflectTest就是它加载的)
        ClassLoader classLoader = ClassLoader.getSystemClassLoader();
        System.out.println(classLoader);


        //2. 获取系统类加载器的父类加载器(扩展类加载器,可以获取).
        classLoader = classLoader.getParent();
        System.out.println(classLoader);


        //3. 获取扩展类加载器的父类加载器(引导类加载器,不可获取).
        classLoader = classLoader.getParent();
        System.out.println(classLoader);


        //4. 测试当前类由哪个类加载器进行加载(系统类加载器):
        classLoader = Class.forName("cn.enjoyedu.refle.more.ReflectionTest")
                .getClassLoader();
        System.out.println(classLoader);


        //5. 测试 JDK 提供的 Object 类由哪个类加载器负责加载(引导类)
        classLoader = Class.forName("java.lang.Object")
                .getClassLoader();
        System.out.println(classLoader);

    }
}


public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        TestClassLoader.testClassLoader();
    }
}

运行结果:

sun.misc.Launcher$AppClassLoader@18b4aac2
sun.misc.Launcher$ExtClassLoader@61bbe9ba
null
sun.misc.Launcher$AppClassLoader@18b4aac2
null

3.2.2 对构造器的操作:

public class TestConstructor {
    /*构造器相关*/
    public void testConstructor() throws Exception{
        String className = "cn.enjoyedu.refle.more.Person";
        Class<Person> clazz = (Class<Person>) Class.forName(className);

        System.out.println("获取全部Constructor对象-----");
        Constructor<Person>[] constructors
                = (Constructor<Person>[]) clazz.getConstructors();
        for(Constructor<Person> constructor: constructors){
            System.out.println(constructor);
        }


        System.out.println("获取某一个Constructor 对象,需要参数列表----");
        Constructor<Person> constructor
                = clazz.getConstructor(String.class, int.class);
        System.out.println(constructor);

        //2. 调用构造器的 newInstance() 方法创建对象
        System.out.println("调用构造器的 newInstance() 方法创建对象-----");
        Person obj = constructor.newInstance("Lucas", 25);
        System.out.println(obj.getName());
    }
}


public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        new TestConstructor().testConstructor();
    }
}

 操作结果:

获取全部Constructor对象-----
public cn.enjoyedu.refle.more.Person(java.lang.String,int)
public cn.enjoyedu.refle.more.Person()
获取某一个Constructor 对象,需要参数列表----
public cn.enjoyedu.refle.more.Person(java.lang.String,int)
调用构造器的 newInstance() 方法创建对象-----
Lucas

3.2.3 对Method、Field的操作:

public class TestMethod {
    /*方法相关*/
    public void testMethod() throws Exception{
        Class clazz = Class.forName("cn.enjoyedu.refle.more.Person");

        System.out.println("获取clazz对应类中的所有方法," +
                "不能获取private方法,且获取从父类继承来的所有方法");
        Method[] methods = clazz.getMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");


        System.out.println("获取所有方法,包括私有方法," +
                "所有声明的方法,都可以获取到,且只获取当前类的方法");
        methods = clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.print(" "+method.getName()+"()");
        }
        System.out.println("");
        System.out.println("---------------------------");


        System.out.println("获取指定的方法," +
                "需要参数名称和参数列表,无参则不需要写");
        //  方法public void setName(String name) {  }
        Method method = clazz.getDeclaredMethod("setName", String.class);
        System.out.println(method);
        System.out.println("---------------------------");


        //  方法public void setAge(int age) {  }
        /* 这样写是获取不到的,如果方法的参数类型是int型
        如果方法用于反射,那么要么int类型写成Integer: public void setAge(Integer age) {  }
        要么获取方法的参数写成int.class*/
        method = clazz.getDeclaredMethod("setAge", int.class);
        System.out.println(method);
        System.out.println("---------------------------");


        System.out.println("执行方法,第一个参数表示执行哪个对象的方法" +
                ",剩下的参数是执行方法时需要传入的参数");
        Object obje = clazz.newInstance();
        method.invoke(obje,18);

        /*私有方法的执行,必须在调用invoke之前加上一句method.setAccessible(true);*/
        method = clazz.getDeclaredMethod("privateMethod");
        System.out.println(method);
        System.out.println("---------------------------");
        System.out.println("执行私有方法");
        method.setAccessible(true);
        method.invoke(obje);
    }
}

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        new TestMethod().testMethod();
    }
}

运行结果:

获取clazz对应类中的所有方法,不能获取private方法,且获取从父类继承来的所有方法
 getName() setName() getAge() setAge() wait() wait() wait() equals() toString() hashCode() getClass() notify() notifyAll()
---------------------------
获取所有方法,包括私有方法,所有声明的方法,都可以获取到,且只获取当前类的方法
 getName() setName() getAge() setAge() privateMethod()
---------------------------
获取指定的方法,需要参数名称和参数列表,无参则不需要写
public void cn.enjoyedu.refle.more.Person.setName(java.lang.String)
---------------------------
public void cn.enjoyedu.refle.more.Person.setAge(int)
---------------------------
执行方法,第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数
this is setAge()!
private void cn.enjoyedu.refle.more.Person.privateMethod()
---------------------------
执行私有方法
this is private method!

3.2.4 对Field的操作:

public class TestField {
    /*域相关*/
    public void testField() throws Exception{
        String className = "cn.enjoyedu.refle.more.Person";
        Class clazz = Class.forName(className);

        System.out.println("获取公用和私有的所有字段,但不能获取父类字段");
        Field[] fields = clazz.getDeclaredFields();
        for(Field field: fields){
            System.out.print(" "+ field.getName());
        }
        System.out.println();
        System.out.println("---------------------------");



        System.out.println("获取指定字段");
        Field field = clazz.getDeclaredField("name");
        System.out.println(field.getName());
        System.out.println("---------------------------");

        Person person = new Person("ABC",12);
        System.out.println("获取指定字段的值");
        Object val = field.get(person);
        System.out.println(field.getName()+"="+val);
        System.out.println("---------------------------");

        System.out.println("设置指定对象指定字段的值");
        field.set(person,"DEF");
        System.out.println(field.getName()+"="+person.getName());
        System.out.println("---------------------------");

        System.out.println("字段是私有的,不管是读值还是写值," +
                "都必须先调用setAccessible(true)方法");
        //     比如Person类中,字段name字段是非私有的,age是私有的
        field = clazz.getDeclaredField("age");
        field.setAccessible(true);
        System.out.println(field.get(person));
    }
}

public class ReflectionTest {
    public static void main(String[] args) throws Exception {
        new TestField().testField();
    }
}

运行结果:

获取公用和私有的所有字段,但不能获取父类字段
 name age
---------------------------
获取指定字段
name
---------------------------
获取指定字段的值
name=ABC
---------------------------
设置指定对象指定字段的值
name=DEF
---------------------------
字段是私有的,不管是读值还是写值,都必须先调用setAccessible(true)方法
12

四 反射进阶之深入剖析

4.1 Java反射机制的起源和入口:Class类

4.1.1 Class类简介

Java 是一门面向对象的语言,我们写的每一个类都可以看成一个对象,是 java.lang.Class 类的对象,那每一个类对应的Class放在哪里呢?当我们写完一个类的Java文件,编译成class文件的时候,编译器都会将这个类的对应的class对象放在class文件的末尾。里面都保存了类的元数据信息。一个类的元数据信息包括什么?有哪些属性,方法,构造器,实现了哪些接口等等,这些信息在Java里都有对应的类来表示,这就是Class类!

Class是用来描述类的类,封装了当前对象所对应的类的信息。

一个类中有属性,方法,构造器等,一个Person类,一个Order类,一个Book类,这些都是不同的类,现在需要一个类,用来描述类,这就是Class。Class类继承自Object类,用于获取与类相关的各种信息, 并提供了获取类信息的相关方法,能够通过对应的方法取出相应的信息:类的名字、属性、方法、构造方法、父类和接口。


对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。 

对象只能由系统建立对象,它是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass 方法自动构造的。

一个类(而不是一个对象)在 JVM 中只会有一个Class实例

获取Class对象的三种方式:

  1.通过类名获取      类名.class   

  2.通过对象获取      对象名.getClass()

  3.通过全类名获取    Class.forName(全类名)

Class类的常用方法

4.1.2 Java中类的加载过程

4.2 反射源码解析

当我们懂得了如何使用反射后,今天我们就来看看 JDK 源码中是如何实现反射的。或许大家平时没有使用过反射,但是在开发 Web 项目的时候会遇到过下面的异常:

java.lang.NullPointerException 
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:497)

可以看到异常堆栈指出了异常在 Method 的第 497 的 invoke 方法中,其实这里指的 invoke 方法就是我们反射调用方法中的 invoke。

Method method = clz.getMethod("setPrice", int.class); 
method.invoke(object, 4);   //就是这里的invoke方法

例如我们经常使用的 Spring 配置中,经常会有相关 Bean 的配置:

<bean class="com.chenshuyi.Apple">
</bean>

当我们在 XML 文件中配置了上面这段配置之后,Spring 便会在启动的时候利用反射去加载对应的 Apple 类。而当 Apple 类不存在或发生启发异常时,异常堆栈便会将异常指向调用的 invoke 方法。

从这里可以看出,我们平常很多框架都使用了反射,而反射中最最终的就是 Method 类的 invoke 方法了。

下面我们来看看 JDK 的 invoke 方法到底做了些什么。

进入 Method 的 invoke 方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了 MethodAccessor 类的 invoke 方法进行进一步处理,如下图红色方框所示。

那么 MethodAccessor 又是什么呢?

其实 MethodAccessor 是一个接口,定义了方法调用的具体操作,而它有三个具体的实现类:

  • sun.reflect.DelegatingMethodAccessorImpl
  • sun.reflect.MethodAccessorImpl
  • sun.reflect.NativeMethodAccessorImpl

而要看 ma.invoke() 到底调用的是哪个类的 invoke 方法,则需要看看 MethodAccessor 对象返回的到底是哪个类对象,所以我们需要进入 acquireMethodAccessor() 方法中看看。

从 acquireMethodAccessor() 方法我们可以看到,代码先判断是否存在对应的 MethodAccessor 对象,如果存在那么就复用之前的 MethodAccessor 对象,否则调用 ReflectionFactory 对象的 newMethodAccessor 方法生成一个 MethodAccessor 对象。

在 ReflectionFactory 类的 newMethodAccessor 方法里,我们可以看到首先是生成了一个 NativeMethodAccessorImpl 对象,再这个对象作为参数调用 DelegatingMethodAccessorImpl 类的构造方法。

这里的实现是使用了代理模式,将 NativeMethodAccessorImpl 对象交给 DelegatingMethodAccessorImpl 对象代理。我们查看 DelegatingMethodAccessorImpl 类的构造方法可以知道,其实是将 NativeMethodAccessorImpl 对象赋值给 DelegatingMethodAccessorImpl 类的 delegate 属性。

所以说ReflectionFactory 类的 newMethodAccessor 方法最终返回 DelegatingMethodAccessorImpl 类对象。所以我们在前面的 ma.invoke() 里,其将会进入 DelegatingMethodAccessorImpl 类的 invoke 方法中。

进入 DelegatingMethodAccessorImpl 类的 invoke 方法后,这里调用了 delegate 属性的 invoke 方法,它又有两个实现类,分别是:DelegatingMethodAccessorImpl 和 NativeMethodAccessorImpl。按照我们前面说到的,这里的 delegate 其实是一个 NativeMethodAccessorImpl 对象,所以这里会进入 NativeMethodAccessorImpl 的 invoke 方法。

而在 NativeMethodAccessorImpl 的 invoke 方法里,其会判断调用次数是否超过阀值(numInvocations)。如果超过该阀值,那么就会生成另一个MethodAccessor 对象,并将原来 DelegatingMethodAccessorImpl 对象中的 delegate 属性指向最新的 MethodAccessor 对象。

到这里,其实我们可以知道 MethodAccessor 对象其实就是具体去生成反射类的入口。通过查看源码上的注释,我们可以了解到 MethodAccessor 对象的一些设计信息。

"Inflation" mechanism. Loading bytecodes to implement Method.invoke() and Constructor.newInstance() currently costs 3-4x more than an invocation via native code for the first invocation (though subsequent invocations have been benchmarked to be over 20x faster).Unfortunately this cost increases startup time for certain applications that use reflection intensively (but only once per class) to bootstrap themselves.

Inflation 机制。初次加载字节码实现反射,使用 Method.invoke() 和 Constructor.newInstance() 加载花费的时间是使用原生代码加载花费时间的 3 - 4 倍。这使得那些频繁使用反射的应用需要花费更长的启动时间。

To avoid this penalty we reuse the existing JVM entry points for the first few invocations of Methods and Constructors and then switch to the bytecode-based implementations. Package-private to be accessible to NativeMethodAccessorImpl and NativeConstructorAccessorImpl.

为了避免这种痛苦的加载时间,我们在第一次加载的时候重用了 JVM 的入口,之后切换到字节码实现的实现。

就像注释里说的,实际的 MethodAccessor 实现有两个版本,一个是 Native 版本,一个是 Java 版本。

Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。

Method 类的 invoke 方法整个流程可以表示成如下的时序图:

讲到这里,我们了解了 Method 类的 invoke 方法的具体实现方式。知道了原来 invoke 方法内部有两种实现方式,一种是 native 原生的实现方式,一种是 Java 实现方式,这两种各有千秋。而为了最大化性能优势,JDK 源码使用了代理的设计模式去实现最大化性能。


五 反射方法的使用

5.1 通过反射运行配置文件内容

student类:

public class Student {
    public void show(){
        System.out.println("is show()");
    }
}

配置文件以txt文件为例子(pro.txt):

className = cn.fanshe.Student
methodName = show

测试类:

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Properties;
 
/*
 * 我们利用反射和配置文件,可以使:应用程序更新时,对源码无需进行任何修改
 * 我们只需要将新类发送给客户端,并修改配置文件即可
 */
public class Demo {
    public static void main(String[] args) throws Exception {
        //通过反射获取Class对象
        Class stuClass = Class.forName(getValue("className"));//"cn.fanshe.Student"
        //2获取show()方法
        Method m = stuClass.getMethod(getValue("methodName"));//show
        //3.调用show()方法
        m.invoke(stuClass.getConstructor().newInstance());
        
    }
    
    //此方法接收一个key,在配置文件中获取相应的value
    public static String getValue(String key) throws IOException{
        Properties pro = new Properties();//获取配置文件的对象
        FileReader in = new FileReader("pro.txt");//获取输入流
        pro.load(in);//将流加载到配置文件对象中
        in.close();
        return pro.getProperty(key);//返回根据key获取的value值
    }
}

控制台输出:

is show()

需求:
当我们升级这个系统时,不要Student类,而需要新写一个Student2的类时,这时只需要更改pro.txt的文件内容就可以了。代码就一点不用改动

要替换的student2类:

public class Student2 {
    public void show2(){
        System.out.println("is show2()");
    }
}

配置文件更改为:

className = cn.fanshe.Student2
methodName = show2

控制台输出:

is show2();

5.2 反射方法的其它使用之---通过反射越过泛型检查

泛型用在编译期,编译过后泛型擦除(消失掉)。所以是可以通过反射越过泛型检查的
测试类:

import java.lang.reflect.Method;
import java.util.ArrayList;
 
/*
 * 通过反射越过泛型检查
 * 
 * 例如:有一个String泛型的集合,怎样能向这个集合中添加一个Integer类型的值?
 */
public class Demo {
    public static void main(String[] args) throws Exception{
        ArrayList<String> strList = new ArrayList<>();
        strList.add("aaa");
        strList.add("bbb");
        
    //    strList.add(100);
        //获取ArrayList的Class对象,反向的调用add()方法,添加数据
        Class listClass = strList.getClass(); //得到 strList 对象的字节码 对象
        //获取add()方法
        Method m = listClass.getMethod("add", Object.class);
        //调用add()方法
        m.invoke(strList, 100);
        
        //遍历集合
        for(Object obj : strList){
            System.out.println(obj);
        }
    }
}

控制台输出:

aaa
bbb
100


 

参考文章

https://www.cnblogs.com/chanshuyi/p/head_first_of_reflection.html

https://blog.csdn.net/weixin_42724467/article/details/84311385

https://www.cnblogs.com/yougewe/p/10125073.html

https://blog.csdn.net/sinat_38259539/article/details/71799078

https://juejin.im/post/5da6be7a6fb9a04dee182727

https://www.cnblogs.com/yougewe/p/10125073.html

https://juejin.im/post/5c528abce51d45706845a458#heading-2

发布了160 篇原创文章 · 获赞 399 · 访问量 28万+

猜你喜欢

转载自blog.csdn.net/LucasXu01/article/details/104939546
今日推荐