暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Java反射没整明白,表情包收了一大堆……

贰予三四 2021-11-23
524

点击上方蓝字关注我们






大家好,今天我们来聊一聊反射~




Java编程中,反射可谓如雷贯耳,Java内部运行机制以及许多优秀开源框架都离不开反射。反射是什么?反射的底层原理是怎样的?反射有哪些优缺点?反射在程序中如何应用的?


带着这几个问题,让我们来开启一段反射之旅吧~






反射概念


接触反射前,大家首先来思考一个问题 🤔


当我们已知某个类的路径,并想要以此去创建对象时,你会去怎样编写代码?

例如Spring支持在xml文件里通过配置类路径来创建bean


也许初学Java的你会这样写👇,直接通过入参来枚举对应的实例:


public class Person {
    
    public void getJobInfo(String className){

        if("student".equals(className)){
            Student s = new Student();
            s.getJob();
        }
        
        if("teacher".equals(className)){
            Teacher t = new Teacher();
            t.getJob();
        }
    }
}

class Student{
    public void getJob(){
        System.out.println("I am a student!");
    }
}

class Teacher{
    public void getJob(){
        System.out.println("I am a teacher!");
    }
}


通过运行程序,我们看到了意料中的运行结果:


public class Main {

    public static void main(String[] args) {
        Person p1 = new Person();
        p1.getJobInfo("student");

        Person p2 = new Person();
        p2.getJobInfo("teacher");
    }
}



这样的写法简单易懂,只不过每当我们想要新增一个像 Student.java 这样的 XXX.java 类时,都需要修改 Person.java 代码,代码耦合度较高;


且当类似 Teacher.java 这样的已有类被删除时,如果没有更改 Person.java 相关逻辑,程序会编译失败,维护成本较高;



我们看下面的一种写法👇


public interface PersonInterface {
    void getJob();
}

class Student implements PersonInterface {
    @Override
    public void getJob() {
        System.out.println("I am a student!");
    }
}

class Teacher implements PersonInterface{
    @Override
    public void getJob(){
        System.out.println("I am a teacher!");
    }
}


public class Main {

    public static void main(String[] args) {
        try{
            Class c1 = Class.forName("com.company.Student");
            PersonInterface person1 = (PersonInterface) c1.newInstance();
            person1.getJob();

            Class c2 = Class.forName("com.company.Teacher");
            PersonInterface person2 = (PersonInterface) c2.newInstance();
            person2.getJob();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


程序运行结果:


在这段代码中,我们首先使用Java多态降低了代码耦合度



另外在通过类路径获取 Class 信息并创建对象过程中,无需关注像 Student.java 这样具体的子类是否存在,即使子类被删除,也不会产生编译错误

但是删除后代码会在运行时抛出ClassNotFoundException异常


这里我们实现了需要哪个类的对象就在程序运行时动态创建,体现了Java语言动态特性中反射灵活性




Java反射机制是指在程序的运行状态中,可以构造任意一个类的对象,可以了解任意一个对象所属的类,可以了解任意一个类的成员变量和方法,可以调用任意一个对象的属性和方法


这种动态获取程序信息以及动态调用对象的功能就是Java语言的反射机制,所以反射被视为动态语言的关键。



反射原理


Java程序运行前,首先会通过编译Java代码生成 .class文件;

程序运行过程中使用某个类时,如果该类还未被加载到内存中,系统会将类的.class字节码文件读入内存,同时JVM会产生一个 java.lang.Class 对象代表该.class字节码文件。



通过Class对象可以得到大量的Method、Constructor、Field等对象,这些对象分别代表该类所包括的方法、构造器和属性等,反射的工作原理就是通过这些对象来执行实际的功能,例如调用方法、创建实例等。


(画一张图假装自己讲明白了





反射常用方法


1、获取反射中的Class对象

示例:
//使用 Class.forName() 静态方法
Class clz = Class.forName("java.lang.String");
//需要知道类的全路径名

//使用 .class 方法
Class clz = String.class;
//需要在编译前就知道Class信息

//使用类对象的 getClass()方法
Person p = new Person();
Class clz = p.getClass();


2、通过反射创建类对象
1、通过 Class 对象的 newInstance() 方法
Class clz = Person.class;
Person p = (Person)clz.newInstance();

2、通过 Constructor 对象的 newInstance() 方法
Class clz = Person.class;
Constructor constructor = clz.getConstructor();
Person p = (Person)constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定的构造方法,而通过 Class 对象则只能使用默认的无参构造方法。


3、通过反射获取类属性、方法、构造器
Class clz = Person.class;

//获取自身和父类的公有属性(不包括私有的)
Field[] fields = clz.getFields();
//获取自身的公有和私有属性
Field[] declaredFields = clz.getDeclaredFields();

//获取自身和父类的公有方法(不包括私有的)
Method[] methods = clz.getMethods();
//获取自身的公有和私有方法
Method[] declaredMethods = clz.getDeclaredMethods();

//获取自身和父类的构造方法(不包括私有的)
Constructor[] constructors = clz.getConstructors();
//获取自身的公有和私有的构造方法
Constructor[] declaredConstructors = clz.getDeclaredConstructors();


4、通过反射获取属性值及执行方法
//定义Engineer类
class Engineer{
    private String name = "小明";

    public void getJob(String name){
        System.out.println(name + " is an engineer!");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}


public class Main {

    public static void main(String[] args) {
        try{
            Class clz = Engineer.class;
            Object obj = clz.newInstance();

            Field field = clz.getDeclaredField("name");
            //私有属性或方法,必须设置accessible=true,否则抛出IllegalAccessException异常
            field.setAccessible(true);
            System.out.println("Field: name-" + field.get(obj));

            Method method = clz.getDeclaredMethod("getJob", String.class);
            method.setAccessible(true);
            method.invoke(obj, "小张");
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


运行结果





反射优势与劣势



优势


反射增加了程序的灵活性


例如Spring框架中利用xml配置解析bean的过程就是反射的典型应用场景。

如果我们想修改一个bean的属性,但是代码工程中有几十个地方都引用了这个bean,通过反射我们只需要在xml中修改bean的定义就可以使所有对象生效,而无需去手动一个个修改对象属性;


反射可以让程序有很好的拓展性


反射可以在不知道会运行哪一个类的情况下,获取到类的信息,创建对象以及操作对象;我们无需将类型硬编码写死,方便程序拓展,降低耦合度。


劣势


反射更容易出现运行时错误


使用显式的类和接口,编译器能帮我们做类型检查,但使用反射需要运行时才知道类型,此时编译器也爱莫能助。


反射性能不高


反射是一种解释操作,在访问字段和调用方法前,需要查找到对应的 Field 和 Method,性能要低一些。

因此反射机制主要应用在对灵活性和拓展性要求较高的系统框架上。


反射会带来安全性问题


反射可以随意修改私有属性和访问私有方法,破坏了类的封装,可能导致逻辑错误或者存在安全隐患。




反射的应用场景


Spring实例化


Spring 实例化过程中,如果没有需要动态改变的方法,直接使用反射实例化就好了。

如果有需要覆盖或者动态替换的方法,需要使用cglib 进行动态代理,因为可以在创建代理的同时将动态方法织入类中。


Spring 源码


SimpleInstantiationStrategy.java

  public Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) 
{
    // Don't override the class with CGLIB if no overrides.
    if (!bd.hasMethodOverrides()) {
      //如果有需要覆盖或者动态替换的方法则当然需要使用cglib进行动态代理,因为可以在创建代理的同时将动态方法织入类中,
      //但是如果没有需要动态改变的方法,为了方便直接反射就可以了
      Constructor<?> constructorToUse;
      synchronized (bd.constructorArgumentLock) {
        constructorToUse = (Constructor<?>) bd.resolvedConstructorOrFactoryMethod;
        if (constructorToUse == null) {
          final Class<?> clazz = bd.getBeanClass();
          if (clazz.isInterface()) {
            throw new BeanInstantiationException(clazz, "Specified class is an interface");
          }
          try {
            if (System.getSecurityManager() != null) {
              constructorToUse = AccessController.doPrivileged(
                  (PrivilegedExceptionAction<Constructor<?>>) clazz::getDeclaredConstructor);
            }
            else {
              //通过反射获取构造器
              constructorToUse = clazz.getDeclaredConstructor();
            }
            bd.resolvedConstructorOrFactoryMethod = constructorToUse;
          }
          catch (Throwable ex) {
            throw new BeanInstantiationException(clazz, "No default constructor found", ex);
          }
        }
      }
      //通过反射进行实例化
      return BeanUtils.instantiateClass(constructorToUse);
    }
    else {
      // Must generate CGLIB subclass.
      return instantiateWithMethodInjection(bd, beanName, owner);
    }
  }



动态代理


动态代理中也随处可见反射的身影。在使用 Proxy.newProxyInstance 静态方法来创建代理时,首先获取代理类Class对象,再通过构造函数生成代理对象。

Proxy 源码


public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)

        throws IllegalArgumentException
    
{
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();

        /*
         * Look up or generate the designated proxy class.
         */

        Class<?> cl = getProxyClass0(loader, intfs);
        ...
        final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                cons.setAccessible(true);
                // END Android-removed: Excluded AccessController.doPrivileged call.
            }
            return cons.newInstance(new Object[]{h});
        ...
    }



今天「反射」就讲到这里了,你学废了吗 




点个在看你最好看

文章转载自贰予三四,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论