美文网首页java基础
Java 反射机制

Java 反射机制

作者: 天空在微笑 | 来源:发表于2017-09-10 00:52 被阅读36次

    [1]. java反射详解
    [2]. Java Reflection(反射机制)详解
    [3]. 深入理解Java类型信息(Class对象)与反射机制

    1. 在Java中用来表示运行时类型信息的对应类就是Class类,Class类也是一个实实在在的类,存在于JDK的java.lang包中:
    public final
        class Class<T> implements java.io.Serializable,
                                  java.lang.reflect.GenericDeclaration,
                                  java.lang.reflect.Type,
                                  java.lang.reflect.AnnotatedElement {
        @CallerSensitive
        public static Class<?> forName(String name, boolean initialize,ClassLoader loader)throws ClassNotFoundException{
            if (loader == null) {
                loader = BootClassLoader.getInstance();
            }
            Class<?> result;
            try {
                result = classForName(name, initialize, loader);
            } catch (ClassNotFoundException e) {
                Throwable cause = e.getCause();
                if (cause instanceof LinkageError) {
                    throw (LinkageError) cause;
                }
                throw e;
            }
            return result;
        }
    
        /** Called after security checks have been made. */
       static native Class<?> classForName(String className, boolean shouldInitialize,
                ClassLoader classLoader) throws ClassNotFoundException;
    
    ...
    }
    
    

    每个类有一个Class对象(我的理解是,这个对象就是一个描述信息,将该对象加载到堆内存,进行一些处理就成了实例对象),当第一次引用类的静态成员变量或者第一次new一个实例时,加载注册到Class类中。Java类加载机制

    1. 分析一个类有哪些东西

    类修饰符 类名 接口 父类
    成员变量修饰符 成员变量类型 成员变量名
    方法修饰符 方法返回值 方法名 方法参数
     - 构造方法
     - 普通成员方法

    3.实例化Class类对象

     @Test
        public void testEnum() {
    //        logManager.info(enumColor.getName());
            Class<?> demo1=null;
            Class<?> demo2=null;
            Class<?> demo3=null;
            try{
                //一般尽量采用这种形式
                demo1=Class.forName("com.company.combine.model.User");
            }catch(Exception e){
                e.printStackTrace();
            }
            demo2=new User().getClass();
            demo3=User.class;
    
            System.out.println("类名称   "+demo1.getName());
            System.out.println("类名称   "+demo2.getName());
            System.out.println("类名称   "+demo3.getName());
        }
    

    类名称 com.company.combine.model.User
    类名称 com.company.combine.model.User
    类名称 com.company.combine.model.User

    通过Class实例化其他类的对象

     @Test
        public  void test1() {
            Class<?> demo=null;
            try{
                demo=Class.forName("com.company.combine.model.User");
            }catch (Exception e) {
                e.printStackTrace();
            }
            User user=null;
            try {
                user=(User)demo.newInstance();
            } catch (InstantiationException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            user.setUsername("张三");
            user.setAddress("北京");
            System.out.println(user);
        }
    

    User{id=null
    , username='张三
    , username1='null
    , birthday=null
    , idList=null
    , ordersList=null
    , ids=null
    , sex='null
    , enumSex='null
    , address='北京
    , password='null
    }

    但是注意一下,当我们把Person中的默认的无参构造函数取消的时候,比如自己定义只定义一个有参数的构造函数之后,会出现错误:

    比如我定义了一个构造函数:

      public User(String username, String address) {
            this.username = username;
            this.address = address;
        }
    

    然后继续运行上面的程序,会出现:

    java.lang.InstantiationException: Reflect.Person

    at java.lang.Class.newInstance0(Class.java:340)
    
    at java.lang.Class.newInstance(Class.java:308)
    
    at Reflect.hello.main(hello.java:39)
    

    Exception in thread "main" java.lang.NullPointerException

    at Reflect.hello.main(hello.java:47)
    

    所以编写使用Class实例化其他类的对象的时候,一定要自己定义无参的构造函数

    可以通过 Class 对象来访问一个类的修饰符, 即public,private,static 等等的关键字,你可以使用如下方法来获取类的修饰符:

    Class  aClass = ... //获取Class对象,具体方式可见Class对象小节
    int modifiers = aClass.getModifiers();
    

    修饰符都被包装成一个int类型的数字,这样每个修饰符都是一个位标识(flag bit),这个位标识可以设置和清除修饰符的类型。 可以使用 java.lang.reflect.Modifier 类中的方法来检查修饰符的类型:

    Modifier.isAbstract(int modifiers);
    Modifier.isFinal(int modifiers);
    Modifier.isInterface(int modifiers);
    Modifier.isNative(int modifiers);
    Modifier.isPrivate(int modifiers);
    Modifier.isProtected(int modifiers);
    Modifier.isPublic(int modifiers);
    Modifier.isStatic(int modifiers);
    Modifier.isStrict(int modifiers);
    Modifier.isSynchronized(int modifiers);
    Modifier.isTransient(int modifiers);
    Modifier.isVolatile(int modifiers);
    

    获取包信息

    Class  aClass = ... //获取Class对象,具体方式可见Class对象小节
    Package package = aClass.getPackage();
    

    获取父类

    Class superclass = aClass.getSuperclass();
    

    可以看到 superclass 对象其实就是一个 Class 类的实例,所以你可以继续在这个对象上进行反射操作。

    获取实现的接口

    Class  aClass = ... //获取Class对象,具体方式可见Class对象小节
    Class[] interfaces = aClass.getInterfaces();
    

    由于一个类可以实现多个接口,因此 getInterfaces(); 方法返回一个 Class 数组,在 Java 中接口同样有对应的 Class 对象。 注意:getInterfaces() 方法仅仅只返回当前类所实现的接口。当前类的父类如果实现了接口,这些接口是不会在返回的 Class 集合中的,尽管实际上当前类其实已经实现了父类接口。

    构造器
    我们可以通 过 Class 对象来获取 Constructor 类的实例:

    Class aClass = ...//获取Class对象
    Constructor[] constructors = aClass.getConstructors();
    

    返回的 Constructor 数组包含每一个声明为公有的(Public)构造方法。 如果你知道你要访问的构造方法的方法参数类型,你可以用下面的方法获取指定的构造方法,这例子返回的构造方法的方法参数为 String 类型:

    Class aClass = ...//获取Class对象
    Constructor constructor =
    aClass.getConstructor(new Class[]{String.class});
    

    如果没有指定的构造方法能满足匹配的方法参数则会抛出:NoSuchMethodException。

    构造方法参数
    你可以通过如下方式获取指定构造方法的方法参数信息:

    Constructor constructor = ... //获取Constructor对象
    Class[] parameterTypes = constructor.getParameterTypes();
    

    利用 Constructor 对象实例化一个类

    Constructor constructor = MyObject.class.getConstructor(String.class);
    MyObject myObject = (MyObject)
     constructor.newInstance("constructor-arg1");
    

    constructor.newInstance()方法的方法参数是一个可变参数列表,但是当你调用构造方法的时候你必须提供精确的参数,即形参与实参必须一一对应。在这个例子中构造方法需要一个 String 类型的参数,那我们在调用 newInstance 方法的时候就必须传入一个 String 类型的参数。

    方法
    可以通过 Class 对象获取 Method 对象,如下例:

    Class aClass = ...//获取Class对象
    Method[] methods = aClass.getMethods();
    

    返回的 Method 对象数组包含了指定类中声明为公有的(public)的所有变量集合。
    如果你知道你要调用方法的具体参数类型,你就可以直接通过参数类型来获取指定的方法,下面这个例子中返回方法对象名称是“doSomething”,他的方法参数是 String 类型:

    Class  aClass = ...//获取Class对象
    Method method = aClass.getMethod("doSomething", new Class[]{String.class});
    

    如果根据给定的方法名称以及参数类型无法匹配到相应的方法,则会抛出 NoSuchMethodException。 如果你想要获取的方法没有参数,那么在调用 getMethod()方法时第二个参数传入 null 即可,就像这样:

    Class  aClass = ...//获取Class对象
    Method method = aClass.getMethod("doSomething", null);
    

    方法参数以及返回类型
    你可以获取指定方法的方法参数是哪些:

    Method method = ... //获取Class对象
    Class[] parameterTypes = method.getParameterTypes();
    

    通过 Method 对象调用方法

    //获取一个方法名为doSomesthing,参数类型为String的方法
    Method method = MyObject.class.getMethod("doSomething", String.class);
    Object returnValue = method.invoke(null, "parameter-value1");
    

    传入的 null 参数是你要调用方法的对象,如果是一个静态方法调用的话则可以用 null 代替指定对象作为 invoke()的参数,在上面这个例子中,如果 doSomething 不是静态方法的话,你就要传入有效的 MyObject 实例而不是 null。 Method.invoke(Object target, Object … parameters)方法的第二个参数是一个可变参数列表,但是你必须要传入与你要调用方法的形参一一对应的实参。就像上个例子那样,方法需要 String 类型的参数,那我们必须要传入一个字符串。

    调用其他类的set和get方法

    class hello {
        public static void main(String[] args) {
            Class<?> demo = null;
            Object obj=null;
            try {
                demo = Class.forName("Reflect.Person");
            } catch (Exception e) {
                e.printStackTrace();
            }
            try{
             obj=demo.newInstance();
            }catch (Exception e) {
                e.printStackTrace();
            }
            setter(obj,"Sex","男",String.class);
            getter(obj,"Sex");
        }
     
        /**
         * @param obj
         *            操作的对象
         * @param att
         *            操作的属性
         * */
        public static void getter(Object obj, String att) {
            try {
                Method method = obj.getClass().getMethod("get" + att);
                System.out.println(method.invoke(obj));
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
     
        /**
         * @param obj
         *            操作的对象
         * @param att
         *            操作的属性
         * @param value
         *            设置的值
         * @param type
         *            参数的属性
         * */
        public static void setter(Object obj, String att, Object value,
                Class<?> type) {
            try {
                Method method = obj.getClass().getMethod("set" + att, type);
                method.invoke(obj, value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    运行结果:

    变量
    你可以通过如下方式访问一个类的成员变量:

    Field[] method = aClass.getFields();
    ```在通常的观点中从对象的外部访问私有变量以及方法是不允许的,但是 Java 反射机制可以做到这一点。使用这个功能并不困难,在进行单元测试时这个功能非常有效。本节会向你展示如何使用这个功能。
    
    注意:这个功能只有在代码运行在单机 Java 应用(standalone Java application)中才会有效,就像你做单元测试或者一些常规的应用程序一样。如果你在 Java Applet 中使用这个功能,那么你就要想办法去应付 SecurityManager 对你限制了。但是一般情况下我们是不会这么做的,所以在本节里面我们不会探讨这个问题。
    
    访问私有变量
    要想获取私有变量你可以调用 Class.getDeclaredField(String name)方法或者 Class.getDeclaredFields()方法。
    
    Class.getField(String name)和 Class.getFields()只会返回公有的变量,无法获取私有变量。下面例子定义了一个包含私有变量的类,在它下面是如何通过反射获取私有变量的例子:
    
    ```java
    public class PrivateObject {
    
      private String privateString = null;
    
      public PrivateObject(String privateString) {
        this.privateString = privateString;
      }
    }
    PrivateObject privateObject = new PrivateObject("The Private Value");
    
    Field privateStringField = PrivateObject.class.
                getDeclaredField("privateString");
    
    privateStringField.setAccessible(true);
    
    String fieldValue = (String) privateStringField.get(privateObject);
    System.out.println("fieldValue = " + fieldValue);
    

    这个例子会输出”fieldValue = The Private Value”,The Private Value 是 PrivateObject 实例的 privateString 私有变量的值,注意调用 PrivateObject.class.getDeclaredField(“privateString”)方法会返回一个私有变量,这个方法返回的变量是定义在 PrivateObject 类中的而不是在它的父类中定义的变量。 注意 privateStringField.setAccessible(true)这行代码,通过调用 setAccessible()方法会关闭指定类 Field 实例的反射访问检查,这行代码执行之后不论是私有的、受保护的以及包访问的作用域,你都可以在任何地方访问,即使你不在他的访问权限作用域之内。但是你如果你用一般代码来访问这些不在你权限作用域之内的代码依然是不可以的,在编译的时候就会报错。

    访问私有方法
    访问一个私有方法你需要调用 Class.getDeclaredMethod(String name, Class[] parameterTypes)或者 Class.getDeclaredMethods() 方法。 Class.getMethod(String name, Class[] parameterTypes)和 Class.getMethods()方法,只会返回公有的方法,无法获取私有方法。下面例子定义了一个包含私有方法的类,在它下面是如何通过反射获取私有方法的例子:

    public class PrivateObject {
    
      private String privateString = null;
    
      public PrivateObject(String privateString) {
        this.privateString = privateString;
      }
    
      private String getPrivateString(){
        return this.privateString;
      }
    }
    PrivateObject privateObject = new PrivateObject("The Private Value");
    
    Method privateStringMethod = PrivateObject.class.
            getDeclaredMethod("getPrivateString", null);
    
    privateStringMethod.setAccessible(true);
    
    String returnValue = (String)
            privateStringMethod.invoke(privateObject, null);
    
    System.out.println("returnValue = " + returnValue);
    

    这个例子会输出”returnValue = The Private Value”,The Private Value 是 PrivateObject 实例的 getPrivateString()方法的返回值。 PrivateObject.class.getDeclaredMethod(“privateString”)方法会返回一个私有方法,这个方法是定义在 PrivateObject 类中的而不是在它的父类中定义的。 同样的,注意 Method.setAcessible(true)这行代码,通过调用 setAccessible()方法会关闭指定类的 Method 实例的反射访问检查,这行代码执行之后不论是私有的、受保护的以及包访问的作用域,你都可以在任何地方访问,即使你不在他的访问权限作用域之内。但是你如果你用一般代码来访问这些不在你权限作用域之内的代码依然是不可以的,在编译的时候就会报错。

    通过反射操作属性

    class hello {
        public static void main(String[] args) throws Exception {
            Class<?> demo = null;
            Object obj = null;
     
            demo = Class.forName("Reflect.Person");
            obj = demo.newInstance();
     
            Field field = demo.getDeclaredField("sex");
            field.setAccessible(true);
            field.set(obj, "男");
            System.out.println(field.get(obj));
        }
    }
    

    通过反射取得并修改数组的信息:

    import java.lang.reflect.*;
    class hello{
        public static void main(String[] args) {
            int[] temp={1,2,3,4,5};
            Class<?>demo=temp.getClass().getComponentType();
            System.out.println("数组类型: "+demo.getName());
            System.out.println("数组长度  "+Array.getLength(temp));
            System.out.println("数组的第一个元素: "+Array.get(temp, 0));
            Array.set(temp, 0, 100);
            System.out.println("修改之后数组第一个元素为: "+Array.get(temp, 0));
        }
    }
    

    运行结果:

    数组类型: int
    数组长度 5
    数组的第一个元素: 1
    修改之后数组第一个元素为: 100

    通过反射修改数组大小

    class hello{
        public static void main(String[] args) {
            int[] temp={1,2,3,4,5,6,7,8,9};
            int[] newTemp=(int[])arrayInc(temp,15);
            print(newTemp);
            System.out.println("=====================");
            String[] atr={"a","b","c"};
            String[] str1=(String[])arrayInc(atr,8);
            print(str1);
        }
         
        /**
         * 修改数组大小
         * */
        public static Object arrayInc(Object obj,int len){
            Class<?>arr=obj.getClass().getComponentType();
            Object newArr=Array.newInstance(arr, len);
            int co=Array.getLength(obj);
            System.arraycopy(obj, 0, newArr, 0, co);
            return newArr;
        }
        /**
         * 打印
         * */
        public static void print(Object obj){
            Class<?>c=obj.getClass();
            if(!c.isArray()){
                return;
            }
            System.out.println("数组长度为: "+Array.getLength(obj));
            for (int i = 0; i < Array.getLength(obj); i++) {
                System.out.print(Array.get(obj, i)+" ");
            }
        }
    }
    

    运行结果:

    数组长度为: 15
    1 2 3 4 5 6 7 8 9 0 0 0 0 0 0 =====================
    数组长度为: 8
    a b c null null null null null

    结合属性文件的工厂模式

    apple=Reflect.Apple
    orange=Reflect.Orange
    
    package Reflect;
     
    import java.io.*;
    import java.util.*;
     
    interface fruit{
        public abstract void eat();
    }
     
    class Apple implements fruit{
        public void eat(){
            System.out.println("Apple");
        }
    }
     
    class Orange implements fruit{
        public void eat(){
            System.out.println("Orange");
        }
    }
     
    //操作属性文件类
    class init{
        public static Properties getPro() throws FileNotFoundException, IOException{
            Properties pro=new Properties();
            File f=new File("fruit.properties");
            if(f.exists()){
                pro.load(new FileInputStream(f));
            }else{
                pro.setProperty("apple", "Reflect.Apple");
                pro.setProperty("orange", "Reflect.Orange");
                pro.store(new FileOutputStream(f), "FRUIT CLASS");
            }
            return pro;
        }
    }
     
    class Factory{
        public static fruit getInstance(String ClassName){
            fruit f=null;
            try{
                f=(fruit)Class.forName(ClassName).newInstance();
            }catch (Exception e) {
                e.printStackTrace();
            }
            return f;
        }
    }
    class hello{
        public static void main(String[] a) throws FileNotFoundException, IOException{
            Properties pro=init.getPro();
            fruit f=Factory.getInstance(pro.getProperty("apple"));
            if(f!=null){
                f.eat();
            }
        }
    }
    

    运行结果:

    Apple

    泛型
    运用泛型反射的经验法则

    下面是两个典型的使用泛型的场景:
    1、声明一个需要被参数化(parameterizable)的类/接口。
    2、使用一个参数化类。
    当你声明一个类或者接口的时候你可以指明这个类或接口可以被参数化, java.util.List 接口就是典型的例子。你可以运用泛型机制创建一个标明存储的是 String 类型 list,这样比你创建一个 Object 的l ist 要更好。当你想在运行期参数化类型本身,比如你想检查 java.util.List 类的参数化类型,你是没有办法能知道他具体的参数化类型是什么。这样一来这个类型就可以是一个应用中所有的类型。但是,当你检查一个使用了被参数化的类型的变量或者方法,你可以获得这个被参数化类型的具体参数。总之:你不能在运行期获知一个被参数化的类型的具体参数类型是什么,但是你可以在用到这个被参数化类型的方法以及变量中找到他们,换句话说就是获知他们具体的参数化类型。 在下面的段落中会向你演示这类情况。泛型方法返回类型如果你获得了 java.lang.reflect.Method 对象,那么你就可以获取到这个方法的泛型返回类型信息。如果方法是在一个被参数化类型之中(译者注:如 T fun())那么你无法获取他的具体类型,但是如果方法返回一个泛型类(译者注:如 List fun())那么你就可以获得这个泛型类的具体参数化类型。你可以在“Java Reflection: Methods”中阅读到有关如何获取Method对象的相关内容。下面这个例子定义了一个类这个类中的方法返回类型是一个泛型类型:

    public class MyClass {
    
      protected List<String> stringList = ...;
    
      public List<String> getStringList(){
        return this.stringList;
      }
    }
    

    我们可以获取 getStringList()方法的泛型返回类型,换句话说,我们可以检测到 getStringList()方法返回的是 List 而不仅仅只是一个 List。如下例:

    Method method = MyClass.class.getMethod("getStringList", null);
    
    Type returnType = method.getGenericReturnType();
    
    if(returnType instanceof ParameterizedType){
        ParameterizedType type = (ParameterizedType) returnType;
        Type[] typeArguments = type.getActualTypeArguments();
        for(Type typeArgument : typeArguments){
            Class typeArgClass = (Class) typeArgument;
            System.out.println("typeArgClass = " + typeArgClass);
        }
    }
    
    

    这段代码会打印出 “typeArgClass = java.lang.String”,Type[]数组typeArguments 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了 Type 接口。

    泛型方法参数类型

    你同样可以通过反射来获取方法参数的泛型类型,下面这个例子定义了一个类,这个类中的方法的参数是一个被参数化的 List:

    public class MyClass {
      protected List<String> stringList = ...;
    
      public void setStringList(List<String> list){
        this.stringList = list;
      }
    }
    
    

    你可以像这样来获取方法的泛型参数:

    method = Myclass.class.getMethod("setStringList", List.class);
    
    Type[] genericParameterTypes = method.getGenericParameterTypes();
    
    for(Type genericParameterType : genericParameterTypes){
        if(genericParameterType instanceof ParameterizedType){
            ParameterizedType aType = (ParameterizedType) genericParameterType;
            Type[] parameterArgTypes = aType.getActualTypeArguments();
            for(Type parameterArgType : parameterArgTypes){
                Class parameterArgClass = (Class) parameterArgType;
                System.out.println("parameterArgClass = " + parameterArgClass);
            }
        }
    }
    

    这段代码会打印出”parameterArgType = java.lang.String”。Type[]数组 parameterArgTypes 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了Type接口。泛型变量类型同样可以通过反射来访问公有(Public)变量的泛型类型,无论这个变量是一个类的静态成员变量或是实例成员变量。你可以在“Java Reflection: Fields”中阅读到有关如何获取 Field 对象的相关内容。这是之前的一个例子,一个定义了一个名为 stringList 的成员变量的类。

    method = Myclass.class.getMethod("setStringList", List.class);
    
    Type[] genericParameterTypes = method.getGenericParameterTypes();
    
    for(Type genericParameterType : genericParameterTypes){
        if(genericParameterType instanceof ParameterizedType){
            ParameterizedType aType = (ParameterizedType) genericParameterType;
            Type[] parameterArgTypes = aType.getActualTypeArguments();
            for(Type parameterArgType : parameterArgTypes){
                Class parameterArgClass = (Class) parameterArgType;
                System.out.println("parameterArgClass = " + parameterArgClass);
            }
        }
    }
    

    这段代码会打印出”fieldArgClass = java.lang.String”。Type[]数组 fieldArgClass 只有一个结果 – 一个代表 java.lang.String 的 Class 类的实例。Class 类实现了 Type 接口。

    相关文章

      网友评论

        本文标题:Java 反射机制

        本文链接:https://www.haomeiwen.com/subject/impmjxtx.html