Java 反射演练

作者: mm_cuckoo | 来源:发表于2017-06-28 19:23 被阅读61次

    反射定义

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

    反射能帮我们做什么?

    • 在运行时检测对象的类型;
    • 动态构造某个类的对象;
    • 检测类的属性和方法;
    • 任意调用对象的方法;
    • 修改构造函数、方法、属性的可见性

    反射使用

    在介绍反射使用之前先贴出几段在被反射的代码:

    package com.sfox.sf.base;
    public interface UserInfoInterface {
        void printMsg(String msg);
    }
    
    package com.sfox.sf.base;
    public interface UserInterface1 {
        String hello = "hello";
    }
    
    package com.sfox.sf.base;
    public class UserBase {
        public static final String FTAG = "tag";
        public String addr;
        private String email;
        public void isFatherMethod(String name){}
        public String getFatherMethod() {
            return "这是父类方法";
        }
    }
    
    package com.sfox.sf.base;
    public class UserInfo extends UserBase implements UserInfoInterface , UserInterface1{
        public static final String TAG = "tag";
        private int age;
        public String name;
        protected String sex;
        public UserInfo(){
            age = 10;
            name = "小明";
            sex = "男";
        }
    
        public UserInfo(int age ,String name) {
            this.age = age;
            this.name = name;
            sex = "女";
        }
    
        public UserInfo(int age ,String name ,String sex) {
            this.age = age;
            this.name = name;
            this.sex = sex;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getSex() {
            return sex;
        }
    
        public void setSex(String sex) {
            this.sex = sex;
        }
    
        @Override
        public void printMsg(String msg) {
            System.out.println("UserInfo 输出信息:::" + msg);
        }
    
        public void printMsg(){
            printMsg(" 这是内部方法");
        }
    
        public UserInfo getUserInfo(UserInfo info) {
            return this;
        }
    
        @Override
        public String toString() {
            return "UserInfo{" + "age=" + age + ", name='" + name + '\'' + ", sex='" + sex + '\'' + '}';
        }
    }
    
    

    上面几段代码中两个接口一个父类,都被UserInfo类继承和实现。

    还有一个通用的输出类,这个类被继承输出:

    public class PrintMsg {
        public static void println(String str) {
            System.out.println(str);
        }
    }
    

    下面我们就基于这样一个实例类进行反射的介绍。

    通过反射获取类名的两种方法:

    1. 获取一个带路径的类名
      UserInfo.class.getName()

      结果:com.sfox.sf.base.UserInfo

    2. 获取一个不带路径的类名
      UserInfo.class.getSimpleName()

      结果:UserInfo

      eg:

      public class Main extends PrintMsg{
          public static void main(String[] args) throws Exception {
              //获取一个类的路径
              println(UserInfo.class.getName());
              //获取一个类的名字
              println(UserInfo.class.getSimpleName());
          }
      }
      

      输出结果:

      com.sfox.sf.base.UserInfo
      UserInfo
      

    获取指定路径下的类(三种方法)

    //常用这种方法
    Class<?> clazz1 = Class.forName("com.sfox.sf.base.UserInfo");
    
    Class<?> clazz2 = new UserInfo().getClass();
    
    Class<?> clazz3 = UserInfo.class;
    
    

    获取父类

    public class Main extends PrintMsg{
        public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
            //获取父类
            Class<?> parentClass = clazz.getSuperclass();
            println("父类的全路径名:" + parentClass.getName());
            println("父类名 :" + parentClass.getSimpleName());
        }
    }
    
    

    输出结果:

    父类的全路径名:com.sfox.sf.base.UserBase
    父类名 :UserBase
    

    获取反射类的所有接口

    public class Main extends PrintMsg{
        public static void main(String[] args) throws Exception {
             Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
    
            //获取借口
            Class<?>[] interfaces = clazz.getInterfaces();
            for (int i = 0 ; i < interfaces.length ; i ++) {
                println("接口[" + i + "]:" + interfaces[i].getName());
            }
        }
    }
    

    输出结果:

    接口[0]:com.sfox.sf.base.UserInfoInterface
    接口[1]:com.sfox.sf.base.UserInterface1
    

    说明:获取接口是通过getInterfaces() 方法,获取的是反射类的所有接口,所以返回的是一个Class 的数组。

    实例对象

    实例对象分为两种,一种构造函数无参,一种构造函数有参数。

    1. 实例无参构造函数对象

      public class Main extends PrintMsg{
          public static void main(String[] args) throws Exception {
               Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
      
              UserInfo userInfo = (UserInfo) clazz.newInstance();
              userInfo.setAge(20);
              userInfo.setName("小红");
              userInfo.setSex("女");
              println(userInfo.toString());
          }
      }
      

      输出结果:

      UserInfo{age=20, name='小红', sex='女'}
      

      说明:实例无参的构造函数调用newInstance()方法即可完成实例,如上例中所示。

    2. 实例有参参构造函数对象

      public class Main extends PrintMsg{
          public static void main(String[] args) throws Exception {
               Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
      
              /**
               * 查询类中所有有构造函数及参数
               */
              Constructor<?>[] constructors = clazz.getConstructors();//获取构造函数数目
              for (int i = 0 ; i < constructors.length ; i ++) {
                  //取出构造函数
                  Constructor<?> constructor = constructors[i];
                  //获取构造函数中参数个数
                  Class<?>[] parms = constructor.getParameterTypes();
                  println("======================");
                  for (int j = 0 ; j < parms.length ; j ++) {
                      //取出构造函数中的参数
                      Class<?> cla = parms[j];
                      println(cla.getName());
                  }
              }
      
              /**
               * 带参实造函数实例对象
               * 通过获取的构造函数进行带参实例对象
               * 注意在获取构造函数时,要获取对应的参数的构造函数
               */
              UserInfo userInfo1 = (UserInfo) constructors[1].newInstance(30,"小明");
              println(userInfo1.toString());
          }
      }
      

      输出结果:

      ======================
      int
      java.lang.String
      java.lang.String
      ======================
      int
      java.lang.String
      ======================
      UserInfo{age=30, name='小明', sex='女'}
      

      说明:上面代码中通过getConstructors()获取类中的构造函数个数,一个类可以有多个构造函数嘛。下面那个 for 循环是遍历构造函数中的参数类型,实例对象是通过constructors[1].newInstance(30,"小明");进行实例的,通过这行代码我们可以知道实例一个带参的对象要先拿到被反射类的带参构造,然后通过拿到的构造调用newInstance(30,"小明"); 方法进行实例,传入实例时参数。

    获取类变量和常量

    获取类变量两种方法,两种方法略有不同?

    1. 通过getDeclaredFields() 获取类中的变量和常量。

      public class Main extends PrintMsg{
          public static void main(String[] args) throws Exception {
              Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
              /**
               * 获取本类的变量
               * 这种方法只能获取本类所有的变量和常量,
               * 父类、接口中的变量无法获取
               */
              Field[] fields = clazz.getDeclaredFields();
              for (int f = 0 ;f < fields.length ; f ++) {
                  Field field = fields[f];
                  //获取权限修饰符
                  int mo = field.getModifiers();
                  String prv = Modifier.toString(mo);
      
                  println("权限修饰符:" + prv);
      
                  Class<?> type = field.getType();
                  println("类型:" + type.getName());
                  println("变量名:" + field.getName());
              }
          }
      }
      

      运行结果:

      权限修饰符:public static final
      类型:java.lang.String
      变量名:TAG
      权限修饰符:private
      类型:int
      变量名:age
      权限修饰符:public
      类型:java.lang.String
      变量名:name
      权限修饰符:protected
      类型:java.lang.String
      变量名:sex
      

      说明:这种方法只能获取本类所有的变量和常量,父类和接口中的变量和常量无法获取。

    2. 通过getFields() 获取类中的变量和常量。

      public class Main extends PrintMsg{
          public static void main(String[] args) throws Exception {
              Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
              /**
               * 该方法只能能获取到public修饰的所有变量,
               * 包括类本身、父类、接口中的变量和常量,非public 修饰的无法通过该方法获取
               */
              Field[] fields1 = class5.getFields();
              for (int ff = 0 ; ff < fields1.length ; ff ++) {
                  Field field = fields1[ff];
                  int mo = field.getModifiers();
                  String prv = Modifier.toString(mo);
                  println("权限修饰符:" + prv);
      
                  Class<?> type = field.getType();
                  println("类型:" + type.getName());
                  println("变量名:" + field.getName());
              }
          }
      }
      

      运行结果:

      权限修饰符:public static final
      类型:java.lang.String
      变量名:TAG
      权限修饰符:public
      类型:java.lang.String
      变量名:name
      权限修饰符:public static final
      类型:java.lang.String
      变量名:hello
      权限修饰符:public static final
      类型:java.lang.String
      变量名:FTAG
      权限修饰符:public
      类型:java.lang.String
      变量名:addr
      

      说明:该方法只能能获取到public修饰的所有变量,包括类本身、父类、接口中的变量和常量,非public 修饰的无法通过该方法获取

    获取指定类和接口中所有方法

    说明:通过反射只能获取类中的public修饰的方法

    public class Main extends PrintMsg{
         public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
            //获取类中方法(public修饰的方法)
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                println("++++++++++++++++++++++++++++++++++++");
                //获取返回类型
                Class<?> retuenType = method.getReturnType();
                println("方法名 :" + method.getName());
                println("返回类型:" + retuenType.getTypeName());
    
                int mo = method.getModifiers();
                String prv = Modifier.toString(mo);
                println("方法权限修饰符" + prv);
    
                //获取方法参数
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (Class methodParamType : parameterTypes) {
                    println("方法参数类型:" + methodParamType.getName());
    
                }
            }
        }
    }
    

    运行结果:

    ++++++++++++++++++++++++++++++++++++
    方法名 :toString
    返回类型:java.lang.String
    方法权限修饰符public
    ++++++++++++++++++++++++++++++++++++
    方法名 :getName
    返回类型:java.lang.String
    方法权限修饰符public
    ++++++++++++++++++++++++++++++++++++
    方法名 :setName
    返回类型:void
    方法权限修饰符public
    方法参数类型:java.lang.String
    ++++++++++++++++++++++++++++++++++++
    方法名 :getUserInfo
    返回类型:com.sfox.sf.base.UserInfo
    方法权限修饰符public
    方法参数类型:com.sfox.sf.base.UserInfo
    ++++++++++++++++++++++++++++++++++++
    方法名 :setSex
    返回类型:void
    方法权限修饰符public
    方法参数类型:java.lang.String
    ++++++++++++++++++++++++++++++++++++
    方法名 :setAge
    返回类型:void
    方法权限修饰符public
    方法参数类型:int
    ++++++++++++++++++++++++++++++++++++
    方法名 :getAge
    返回类型:int
    方法权限修饰符public
    ++++++++++++++++++++++++++++++++++++
    方法名 :getSex
    返回类型:java.lang.String
    方法权限修饰符public
    ++++++++++++++++++++++++++++++++++++
    方法名 :printMsg
    返回类型:void
    方法权限修饰符public
    方法参数类型:java.lang.String
    ++++++++++++++++++++++++++++++++++++
    方法名 :printMsg
    返回类型:void
    方法权限修饰符public
    ++++++++++++++++++++++++++++++++++++
    方法名 :isFatherMethod
    返回类型:void
    方法权限修饰符public
    方法参数类型:java.lang.String
    ++++++++++++++++++++++++++++++++++++
    方法名 :getFatherMethod
    返回类型:java.lang.String
    方法权限修饰符public
    ++++++++++++++++++++++++++++++++++++
    

    通过反射执行类中方法

    public class Main extends PrintMsg{
         public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
            //获取类中的方法,在getMethod中传入方法名
            Method method0 = clazz.getMethod("printMsg");
            //执行方法,传入类的实例
            method0.invoke(clazz.newInstance());
            //获取有参数数方法,在getMethod 中传入方法名,在后面传入参数的类型
            Method method1 = clazz.getMethod("printMsg",String.class);
            //执行方法,传入类的实例,后面传入方法中传入的参数。
            method1.invoke(clazz.newInstance(),"这里是传入的参数");
            
            UserInfo userInfo = new UserInfo();
            Method userMetho = UserInfo.class.getMethod("printMsg",String.class);
            userMetho.invoke(userInfo,"你好,我是实例方法调用");
        }
    }
    

    说明:在上面代码中通过getMethod("方法名",paramType...) 方法中传入方法名来获取类中指定方法,在方法名后面传入方法参数类型。如getMethod("printMsg",String.class);,执行方法使用invoke方法,在invoke方法中传入类的实例对象和方法传入的参数。如下:

    Method method0 = clazz.getMethod("printMsg");
    method0.invoke(clazz.newInstance());
    
    Method method1 = clazz.getMethod("printMsg",String.class);
    method1.invoke(clazz.newInstance(),"这里是传入的参数");
    

    被执行的方法代码:

    public void printMsg(String msg) {
        System.out.println("UserInfo 输出信息:::" + msg);
    }
    
    public void printMsg(){
        printMsg(" 这是内部方法");
    }
    

    执行结果:

    UserInfo 输出信息::: 这是内部方法
    UserInfo 输出信息:::这里是传入的参数
    UserInfo 输出信息:::你好,我是实例方法调用
    

    操作实例中的非private修饰参数

    public class Main extends PrintMsg{
         public static void main(String[] args) throws Exception {
            UserInfo userInfo = new UserInfo();
    
            Field field = UserInfo.class.getField("name");
            field.set(userInfo,"操作实例中的参数");
            println(userInfo.toString());
            println(field.get(userInfo).toString());
        }
    }
    
    

    运行结果:

    UserInfo{age=10, name='操作实例中的参数', sex='男'}
    操作实例中的参数
    

    操作实例中的private 修饰的参数

    public class Main extends PrintMsg{
         public static void main(String[] args) throws Exception {
            Class<?> clazz = Class.forName("com.sfox.sf.base.UserInfo");
            Field fieldPrv = clazz.getDeclaredField("age");
            fieldPrv.setAccessible(true);
            fieldPrv.set(obj,123);
            println(String.valueOf(fieldPrv.get(obj)));
        }
    }
    

    运行结果:

    123
    

    说明:操作类的private 参数时,必须要使用getDeclaredField 方法,同时必须使用fieldPrv.setAccessible(true);方法将其设置成true

    通过反射操作一个List 实例

    public class Main extends PrintMsg{
         public static void main(String[] args) throws Exception {
            List<String> list = new ArrayList<>();
            Method method = List.class.getMethod("add",Object.class);
            method.invoke(list,"good");
            method.invoke(list,"hello");
            println("List 内容:" + list.toString());
        }
    }
    

    运行结果:

    List 内容:[good, hello]
    

    修改数组信息

    public class Main extends PrintMsg{
         public static void main(String[] args) throws Exception {
            int[] tmp = {1,2,3,4};
            Class tmpType = tmp.getClass().getComponentType();
            println("数组类型:" + tmpType.getName());
            println("数组长度:" + Array.getLength(tmp));
            println("获取数组第一个数据:" + Array.get(tmp,0));
            //数组数组第一个数据
            Array.set(tmp,0,100);
            println("获取设置后数组第一个数据1:" + tmp[0]);
            println("获取设置后数组第一个数据2:" + Array.get(tmp,0));
        }
    }
    

    运行结果:

    数组类型:int
    数组长度:4
    获取数组第一个数据:1
    获取设置后数组第一个数据1:100
    获取设置后数组第一个数据2:100
    

    相关文章

      网友评论

        本文标题:Java 反射演练

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