Java的反射机制

2020/01/10 Java 反射 Class

在Java中,反射(Reflection)机制是非常重要的一部分。一直对这块理解有点模糊,花点时间理了一下反射的内容。


反射的定义

Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性。这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

用一句话总结就是反射可以实现在运行时可以知道任意一个类的属性和方法。

反射机制的实现主要通过使用java.lang.Class对象。

反射的相关操作

  • getDeclaredMethods: 获取该类的所有方法,不包含父类
  • getMethods: 获取该类以及父类的public方法
    // 获取ConfigInfoServiceTest类的Class对象
    Class clz = ConfigInfoServiceTest.class;

    // 获取ConfigInfoServiceTest类的public方法
    Method[] declaredMethods = clz.getDeclaredMethods();

    // for循环输出方法名称
    for (Method method : declaredMethods) {
        System.out.println(method.getName());
    }

    output:
    main
    test_getConfigValue
    test_getResourceContent

  • 获取成员变量信息
  • getDeclaredFields: 获取该类的所有变量,不包含父类
  • getFields: 获取该类以及父类的public变量

    // 获取ConfigInfoServiceTest类的Class对象
    Class clz = ConfigInfoServiceTest.class;

    // 获取该类的所有变量
    Field[] declaredFields = clz.getDeclaredFields();

    // for循环输出变量名称
    for (Field declaredField : declaredFields) {
        System.out.println(declaredField.getName());
    }

    output:
    abTestConfigServiceClient
    resourceService
    testName

  • 获取构造函数
  • getDeclaredConstructors: 获取该类的所有构造函数,不包含父类
  • getConstructors:获取该类和父类的public构造函数
    // 获取ConfigInfoServiceTest类的Class对象
    Class clz = ConfigInfoServiceTest.class;

    // 获取该类的所有构造函数
    Constructor[] constructors = clz.getDeclaredConstructors();

    // for循环输出该类的构造函数
    for (Constructor constructor : constructors) {
        System.out.println(constructor);
    } 

    output:
    public com.yit.urdm.temp.test.ConfigInfoServiceTest(java.lang.String)
    public com.yit.urdm.temp.test.ConfigInfoServiceTest()      

反射的用途

实现工厂模式

定义Aniaml接口

public interface Animal {

    void eat();

    void sleep();
}

定义Dog实现类

public class Dog implements Animal {

    @Override
    public void eat() {
        System.out.println("dog eat");
    }

    @Override
    public void sleep() {
        System.out.println("dog sleep");
    }

}

定义Cat实现类

public class Cat implements Animal {

    @Override
    public void eat() {
        System.out.println("cat eat");
    }

    @Override
    public void sleep() {
        System.out.println("cat sleep");
    }
}

定义AnimalFactory工厂类

public class AnimalFactory {

    public static Animal getAnimal(String className) {
        try {
            return (Animal) Class.forName(className).newInstance();
        } catch (Exception e) {
            return null;
        }
    }

    public static void main(String[] args) {
        Animal dog = AnimalFactory.getAnimal("lyxiang.reflect.factory.Dog");
        dog.eat();
        dog.sleep();

        Animal cat = AnimalFactory.getAnimal("lyxiang.reflect.factory.Cat");
        cat.eat();
        cat.sleep();
    }

output:
    dog eat
    dog sleep
    cat eat
    cat sleep

通过反射实现动态代理

动态代理:无需声明代理类。是使用反射和字节码的技术,在运行期创建指定接口或类的子类(即动态代理类)以及其实例对象的技术。通过动态代理技术可以无侵入地对代码进行增强。

动态代理实现原理主要由两种,JDK动态代理和CGLIB动态代理。这里主要描述JDK动态代理。

public class AnimalProxy implements InvocationHandler {

    private Object object;

    public Object getObject(Object obj){

        this.object = obj;

        // 通过反射,新建一个代理类
        return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        
        // 代理逻辑
        System.out.println("invoke start");
        Object result = method.invoke(object, args);
        System.out.println("invoke end");
        return result;
    }

}

public static void main(String[] args) {

    AnimalProxy proxy = new AnimalProxy();
    Animal animal = (Animal) proxy.getObject(new Dog());
    animal.eat();

}

output:
invoke start
dog eat
invoke end

代理类的所有方法调用都会被转发到AnimalProxy.invoke(),从而实现了代理逻辑。

JDk的动态代理,只能对接口做代理,如果是普通的类,是无法代理的。

普通的类,可以用CGLIB做动态代理,这个后面在细说。

泛型擦除

Java的泛型只存在于源码中,在编译之后,class文件中是不会存在的,这个就叫做泛型擦除。

对于new ArrayList 和 new ArrayList 来说,两个对象在编译之后两者是一样的,通过反射均可以向集合中添加任意类型的对象。

例子:

public static void main(String[] args){

    List<String>  strList = new ArrayList<>();
    strList.add("a");
    strList.add("b");
    strList.add("c");

    List<Integer> intList = new ArrayList<>();
    intList.add(1);
    intList.add(2);
    intList.add(3);

    System.out.println(intList.getClass() == strList.getClass());
    // output-> true

    try{
        strList.getClass().getMethod("add", Object.class).invoke(strList, 1);

        for (Object o : strList) {
            System.out.print(o.toString());
        }
        // output -> abc1

    }catch (Exception e){

    }
}

反射的优缺点

  • 优点:反射提高了程序的灵活性和扩展性,降低耦合性,提高自适应能力。它允许程序创和控制任何类的对象,无需提前硬编码目标类
  • 缺点:
    • 反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免在经常被 执行的代码或对性能要求很高的程序中使用反射。
    • 使用反射技术要求程序必须在一个没有安全限制的环境中运行。
    • 由于反射允许代码执行一些在正常情况下不被允许的操作(比如访问私有的属性和方法),所以使用反射可能会导致意料之外的副作用--代码有功能上的错误,降低可移植性。反射代码破坏了抽象性,因此当平台发生改变的时候,代码的行为就有可能也随着变化。

参考

Search

    Table of Contents