• Java反射机制浅析


    概念

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

      Class类与java.lang.reflect类库一起对反射的概念进行了支持,该类库包含了FieldMethod以及Constructor类(每个类都实现了Member接口)。这些类型的对象是由JVM在运行时创建的,用以表示未知类里对应的成员。这样开发人员就可以使用Constructor创建新的对象,用get()set()方法读取和修改与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法。另外,还可以调用getFields()getMethods()getConstructors()等很便利的方法,以返回表示字段、方法以及构造器的对象的数组。这样,匿名对象的类信息就能在运行时被完全确认下来,而在编译时不需要知道任何事情。

    Class 

      类是程序的一部分,每个类都有一个Class对象。换言之,每当编写并且编译一个新类,就会产生一个Class对象(更恰当地说,是被保存在一个同名的.class文件中),它包含了与类有关的信息。为了生成这个类的对象,运行这个对象的虚拟机(JVM)将使用被称为“类加载器(ClassLoader)”的子系统。下面测试类的一些最基本信息。 

    public class ClassInfo {
    
        /**
         * @description 输出不同格式类名
         * @param clazz
         */
        public static void printName(Class<?> clazz) {
            System.out.println("getName: " + clazz.getName());
            System.out.println("getCanonicalName: " + clazz.getCanonicalName());
            System.out.println("getSimpleName: " + clazz.getSimpleName());
        }
        
        /**
         * @description 输出类的父类和接口
         * @param clazz
         */
        public static void printClassIntf(Class<?> clazz) {
            Class<?> superClass = clazz.getSuperclass();
            Class<?>[] interfaces = clazz.getInterfaces(); 
            if(superClass != null) {
                System.out.print(clazz.getSimpleName() + " extends " + superClass.getSimpleName());
            }
            if(interfaces.length > 0) {
                System.out.print(" implements ");
                for(int i = 0; i < interfaces.length - 1; i++) {
                    System.out.print(interfaces[i].getSimpleName() + ", ");
                }
                System.out.println(interfaces[interfaces.length - 1].getSimpleName());
            }
        }
    }

    测试类 测试用例:ArrayList

    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    public class ClassInfoTest {
        
        private Class<?> clazz;
        
        private String className = "java.util.ArrayList";
        
        /**
         * forName()是获取Class对象的引用的一种方法。
         * 它是用一个包含目标类的文本名的String作输入参数,返回的是一个Class对象的引用。
         */
        @Before
        public void before() {
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        
        @After
        public void after() {
            clazz = null;
        }
        
        @Test
        public void testGetName() {
            ClassInfo.printName(clazz);
        }
        
        @Test
        public void testPrintClassIntf() {
            ClassInfo.printClassIntf(clazz);
        }
    
    }

    测试结果

    getName: java.util.ArrayList
    getCanonicalName: java.util.ArrayList
    getSimpleName: ArrayList
    ArrayList extends AbstractList implements List, RandomAccess, Cloneable, Serializable

    Constructor

      Constructor类是对Java普通类中的构造器的抽象。通过Class类的getConstructors()方法可以取得表示构造器的对象的数组,通过getConstructor(Class<?>... parameterTypes)可以取得指定参数类型的构造器。通过newInstance(Object... initargs)方法可以构建一个实例对象。需要注意的是:newInstance()方法的参数要和getConstructor()方法参数相对应。例如 getConstructor(String.class) --- getInstance("Jack")

      以下的测试都是假设我们从磁盘上或者网络中获取一个类的字节,得知这个类的包名(reflcet)和类名(Reflect)和相关字段名称和方法名称,并通过热加载已经加载到工程中。

      method.invoke()会在下文Method中讲到。

     待测类 

    package reflect;
    
    /**
     * @description 运行时获取的类
     * @author Administrator
     */
    public class Reflect {
        
        public int id;
        private String name;
        
        public Reflect() {
            this.name = "Tom";
        }
        
        public Reflect(String name) {
            this.name = name;
        }
        
        public Reflect(int id, String name) {
            this.id = id;
            this.name = name;
        }
        
        public void setName(String name) {
            this.name = name;
        }
        
        public String getName() {
            return name;
        }
        
        @SuppressWarnings("unused")
        private void setId(int id) {
            this.id = id;
        }
        
        public int getId() {
            return id;
        }
        
        @Override
        public String toString() {
            return "id:" + id + ", name:" + name;
         }
    
    }

     测试类

    import static org.hamcrest.Matchers.*;
    import static org.junit.Assert.*;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    
    public class ReflectTest {
        
        Class<?> clazz;
        
        /**
         * className = "包名.类名"
         */
        String className = "reflect.Reflect";
        
        @Before
        public void before() {
            try {
                clazz = Class.forName(className);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
        
        @After
        public void after() {
            clazz = null;
        }
        
        @Test
        public void testConstructor() {
            try {
                /**
                 * 获取无参构造器
                 */
                Constructor<?> constructor = clazz.getConstructor();
                Object obj = constructor.newInstance();
                Method method = clazz.getMethod("getName");
                assertThat((String)method.invoke(obj), containsString("Tom"));
                /**
                 * 获取带参构造器
                 */
                constructor = clazz.getConstructor(String.class);
                obj = constructor.newInstance("Jack");
                assertThat((String)method.invoke(obj), containsString("Jack"));
                /**
                 * 获取多个参数构造器
                 */
                constructor = clazz.getConstructor(int.class, String.class);
                obj = constructor.newInstance(6, "Rose");
                method = clazz.getMethod("toString");
                assertThat((String)method.invoke(obj), allOf(containsString("id:6"), containsString("name:Rose")));
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (SecurityException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            
        }
        
    }

    Field

      Field类是对Java普通类中的属性或者称字段的抽象。通过Class类的getFields()方法可以取得表示字段的对象的数组,通过getField(String name)获取给定名称的字段的对象,如果字段修饰符为privateprotected,则getField()方法会抛出java.lang.NoSuchFieldException异常。对于非公有的属性的设定,可以使用getDeclaredField()方法,并调用setAccessible(true),使属性可获得。

    测试方法 

    @Test
    public void testField() {
        try {
            /**
             * Class类的newInstance()方法会调用默认构造函数创建一个实例对象
             */
            Object obj = clazz.newInstance();
            Method method = clazz.getMethod("getName");
            assertThat((String)method.invoke(obj), containsString("Tom"));
            /**
             * 设定private属性的值
             */
            Field field = clazz.getDeclaredField("name");
            field.setAccessible(true);
            field.set(obj, "Jack");
            assertThat((String)method.invoke(obj), containsString("Jack"));
            /**
             * 设定public属性的值
             */
            field = clazz.getField("id");
            field.setInt(obj, 9);
            method = clazz.getMethod("getId");
            assertThat(String.valueOf(method.invoke(obj)), containsString("9"));
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }

    Method

      Method类是对Java普通类中的方法的抽象。通过Class类的getMethods()方法可以取得表示方法的对象的数组,通过getMethod(String name, Class<?>... parameterTypes)方法可以取得指定方法名称以及方法参数类型的方法的对象,如果方法修饰符为private或protected,getMethod()方法会抛出java.lang.NoSuchMethodException异常。对于非公有的方法,可以通过getDeclaredMethod()方法,并调用setAccessible(true),使方法可获得。调用method.invoke(Object obj, Object... args)方法,实现obj对象对方法method的调用,参数为args。和构造器同样的道理,getMethod()方法和invoke方法要相对应。例如getMethod("setName", String.class) --- invoke(obj, "Rose")

    测试方法 

    @Test
    public void testMethod() {
        try {
            /**
             * 调用无参公有方法
             */
            Object obj = clazz.newInstance();
            Method method1 = clazz.getMethod("getName");
            assertThat((String)method1.invoke(obj), containsString("Tom"));
            /**
             * 调用带参公有方法
             */
            Method method2 = clazz.getMethod("setName", String.class);
            method2.invoke(obj, "Jack");
            assertThat((String)method1.invoke(obj), containsString("Jack"));
            /**
             * 调用带参私有方法
             */
            Method method3 = clazz.getDeclaredMethod("setId", int.class);
            method3.setAccessible(true);
            method3.invoke(obj, 5);
            Method method = clazz.getMethod("getId");
            assertThat(String.valueOf(method.invoke(obj)), containsString("5"));
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    类方法提取器

      当开发者学习一个类(比如:ArrayList)时,通过浏览实现了类定义的源代码或是其JDK文档,只能找到在这个类定义中被定义或被覆盖的方法。但对开发者来说,可能有数十个更有用的方法都是继承自基类的。要找出这些方法可能很乏味且费时。幸运的是,反射机制提供了一个方法,使开发者能够编写可以自动展示完整接口的简单工具。工作方式如下: 

    /**
     * @description 类方法提取器
     * @param clazz
     */
    public static void printClassInfo(Class<?> clazz) {
        Pattern pattern = Pattern.compile(("\w+\."));
        Constructor<?>[] constructors = clazz.getConstructors();
        Method[] methods = clazz.getMethods();
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields) {
            System.out.println(pattern.matcher(field.toGenericString()).replaceAll(""));
        }
        for(Constructor<?> constructor : constructors) {
            System.out.println(pattern.matcher(constructor.toGenericString()).replaceAll(""));
        }
        for(Method method : methods) {
            System.out.println(pattern.matcher(method.toGenericString()).replaceAll(""));
        }
    }

     测试方法 测试用例:ArrayList

    @Test
    public void testPrintClassInfo() {
        try {
            ClassInfo.printClassInfo(Class.forName("java.util.ArrayList"));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

     测试结果:

    private static final long serialVersionUID
    private static final int DEFAULT_CAPACITY
    private static final Object[] EMPTY_ELEMENTDATA
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    transient Object[] elementData
    private int size
    private static final int MAX_ARRAY_SIZE
    public ArrayList(Collection<? extends E>)
    public ArrayList()
    public ArrayList(int)
    public boolean add(E)
    public void add(int,E)
    public boolean remove(Object)
    public E remove(int)
    public E get(int)
    public Object clone()
    public int indexOf(Object)
    public void clear()
    public boolean contains(Object)
    public boolean isEmpty()
    public Iterator<E> iterator()
    public int lastIndexOf(Object)
    public void replaceAll(UnaryOperator<E>)
    public int size()
    public List<E> subList(int,int)
    public <T> T[] toArray(T[])
    public Object[] toArray()
    public Spliterator<E> spliterator()
    public boolean addAll(int,Collection<? extends E>)
    public boolean addAll(Collection<? extends E>)
    public void forEach(Consumer<? super E>)
    public E set(int,E)
    public void ensureCapacity(int)
    public void trimToSize()
    public ListIterator<E> listIterator(int)
    public ListIterator<E> listIterator()
    public boolean removeAll(Collection<?>)
    public boolean removeIf(Predicate<? super E>)
    public boolean retainAll(Collection<?>)
    public void sort(Comparator<? super E>)
    public boolean equals(Object)
    public int hashCode()
    public String toString()
    public boolean containsAll(Collection<?>)
    public final void wait() throws InterruptedException
    public final void wait(long,int) throws InterruptedException
    public final native void wait(long) throws InterruptedException
    public final native Class<?> getClass()
    public final native void notify()
    public final native void notifyAll()
    public default Stream<E> stream()
    public default Stream<E> parallelStream()

    JVM

      反射机制并没有什么神奇之处。当通过反射与一个未知类型的对象打交道时,JVM只是简单的检查这个对象。因此,那个类的.class文件对于JVM来说必须是可获取的:要么在本地机器上,要么可以通过网络取得。对已反射机制来说,.class文件在编译时时不可获取的 所以是在运行时打开和检查.class文件。

    应用

      假设开发者从磁盘文件,或者网络连接中获取一串字节,并且被告知这些字节代表了一个类。既然这个类在程序编译后很久才出现,若想使用这个类,就需要采用发射机制。热加载就属于这种场景。

      在运行时获取类的信息的另一个场景,开发者希望提供在跨网络的远程平台上创建和运行对象的能力。这被称为远程方法调用(RMI) 它允许一个Java程序将对象分布到多台机器上。

      在自己写框架时候,开发者肯定会用到反射,很简单的例子就是事件总线和注解框架。

    总结

      反射很灵活,在日常开发中,慎用少用反射,反射会牺牲部分性能。在写框架时,不避讳反射,在关键时利用反射助自己一臂之力。  


    作者:VictorWong
    出处:http://www.cnblogs.com/jwongo
    github:https://github.com/jwongo
    本文版权归作者和博客园共有,欢迎转载。水平有限,恳请读者批评指正。如果觉得对您有点用,就点个赞支持一下吧。

  • 相关阅读:
    .htaccess文件首行options +followsymlinks作用
    Limit结合使用SQL_calc_found_rows来提高子句的灵活性
    正则表达式基础语法
    left jion时,on和where条件的区别
    【解决方案】RTMP推流网关平台EasyRTMPlive在直播商品生产过程中的应用
    【开发记录】视频智能组网平台EasyNTS上云网关流量监控曲线图日期显示优化
    TSINGSEE青犀视频云边端协同解决方案如何查看有多少视频流同时录像或直播?
    RTMP推流组件EasyRTMPAndroid同时推音频流和视频流时为什么会出现画面不动的情况?
    【BUG修复】网络映射/端口穿透/视频组网服务/EasyNTS上云网关前端显示Disconnected问题排查
    【解决方案】互联网直播系统RTMP推流网关平台EasyRTMPlive在幼儿园家长直播中的应用
  • 原文地址:https://www.cnblogs.com/jwongo/p/java-reflect.html
Copyright © 2020-2023  润新知