Java 反射演练

反射定义

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,293评论 18 399
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,099评论 18 139
  • 小编费力收集:给你想要的面试集合 1.C++或Java中的异常处理机制的简单原理和应用。 当JAVA程序违反了JA...
    八爷君阅读 4,484评论 1 114
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 6,935评论 0 62
  • 以下内容节选自华东师范大学信息化办公室主任沈富可老师在金智教育线下训练营分享的“大数据时代下高校信息化建设规划与探...
    小智_金智教育阅读 1,052评论 0 1