反射---泛型的类型擦除

123 阅读2分钟

我们需要知道,编译器在编译的时候是会将泛型擦除的,譬如原来的<Person> ---> <Object>

PersonChild1都有setValue方法

public class Person<T> {
    public AtomicInteger updateCount = new AtomicInteger(0);

    private T value;

    //重写toString,输出值和值更新次数
    @Override
    public String toString() {
        return String.format("value: %s updateCount: %d", value, updateCount.get());
    }

    //设置值
    public void setValue(T value) {
        System.out.println("Person.setValue called");
        this.value = value;
        updateCount.incrementAndGet();
    }

}
public class Child1 extends Person{
    //@Override
    public void setValue(String value) {
        System.out.println("Child1.setValue called");
        super.setValue(value);

    }
}
public class Application {
    @Test
    public void test() {
        Child1 child1 = new Child1();
        Arrays.stream(child1.getClass().getMethods())
                .filter(method -> "setValue".equals(method.getName()))
                .forEach(method -> {
                    try {
                        method.invoke(child1, "123");
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                });
        System.out.println(child1.toString());
    }
}

这里使用的是getMethods方法去获取类方法,但是要清楚这个方法获取的方法是子类和父类的所有方法;

可以很显然看出来这个父类的setValue方法被调用了两次,但是我们的期望是调用一次,那就证明或者debug发现调用了父类的setValue(Object)方法,那这事为啥呢?前面提及到这个getMethods()方法可以获取父类和子类的所有方法,但是这个getDeclaredMethods只能获取当前类的方法,换一下看看。可以看到换了后就可以了;

但是这并不是最后的解决方式,因为我们需要考虑到泛型是jdk1.5出现的,那么jdk1.5之前的怎么办呢?这个时候就会出现了桥接

修改Person的子类:给子类继承的父类加上泛型,

public class Child2 extends Person<String>{
    //@Override
    public void setValue(String value) {
        System.out.println("Child1.setValue called");
        super.setValue(value);
    }
}

emmm,又出了两次调用,但是这次的调用跟上面的不一样,这个很显然是调用了两次子类的setValue方法这是为什么呢,提到过一个桥接,我也没弄明白这个东西,大致知道这个东西在子类中又创建了一个方法public void setValue(Object obj)

这里有个博客可以稍微看:blog.csdn.net/mhmyqn/arti…

因此在做过滤的时候需要过滤那个桥接方法,增加这个!method.isBridge()这个过滤条件。

    @Test
    public void test() {
        Child2 child2 = new Child2();
        Arrays.stream(child2.getClass().getDeclaredMethods())
                .filter(method -> "setValue".equals(method.getName()) && !method.isBridge())
                .forEach(method -> {
                    try {
                        method.invoke(child2, "123");
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                });
        System.out.println(child2.toString());
    }