深入理解Java反射(一)

Class
在面向对象中,万物皆对象,我们把人、动物等都抽象成类,将类具体化就成了对象,比如new Studen() 学生是个类,类里面指定的某一个学生就是研究的对象。
在java中类用class来表示
比如定义一个类:

public class Studen {
}

万物抽象可以用class来表示,那么class是不是也属于一个类型呢,所以java将所有的class都用一个类型(Class)来表示,Class(注意大小写)就是用来表示类的类型

就像Studen属于class,那么Student虽在的类的类型用Class表示,一句话,Class是所有类的类型,包括Object是一个class,Object对应的类的类型是Class
笔者认为也可以理解为Class是所有类的字节码对象。负责管理字节码的。

java的原文件以.java为后缀,编译后的字节码以.class为后缀

怎么创建一个类?
在java里创建类的常规方式是通过new这个关键字来创建的。

 Studen studen = new Studen();

还可以通过Class来创建,
每个.class文件都必须先由JVM加载到内存,反射中只要能读取到这个.class文件就能创建一个对象。
java中有三种方式可以获得自己的Class

        //1.通过类名.class
        Class clazz1 = Studen.class;
        //2.通过对象引用.getClass()
        Class clazz2 = studen.getClass();
        //3.通过类的完整路劲名
        Class clazz3 = Class.forName("com.jiuletech.app.Studen");

这里clazz1 clazz2 clazz3都是Class的对象,完全相同

System.out.println(clazz1 == clazz2);
System.out.println(clazz2== clazz3);

可以看到控制台打印两个true,说明都是同一个Class,即上面说的同一份字节码,就算你创建N个对象,内存中依旧只有一份字节码,上面三个Class指向的都是同一份字节码,因此为true

控制台打印

得到了Class类类型,就可以通过它来创建对象
修改Student,添加方法printName()

public void printName(String name){
        System.out.println("以后请叫我的外国名字:"+name);
    }

演示反射调用方法

  Class clz = null;
        try {
            clz = Class.forName("com.jiuletech.app.Student");
            Student o = (Student) clz.newInstance();
            Method method = clz.getMethod("printName", String.class);
            method.invoke(o, "沃德天·维森莫·拉莫帅·帅德布耀德");
        } catch (Exception e) {
            e.printStackTrace();
        }
上面代码运行结果

从上面的代码我们可以看到核心的四步:

  1. 通过Class.forName("完整路劲名")获取到Class对象
  2. 调用Class的newInstance()方法
    这个方法会将上一步加载的字节码实例化出来,相当于平时的new
  3. 通过getMethod("方法名","方法参数类型")获取到对象中的方法
  4. 调用invoke("哪个对象","方法参数")执行Student里面的printName方法。

通过简单的四步,在只知道一个对象完整路劲名的情况下就可以随意玩弄这个对象。


体验了反射的强大之处在来扩展反射常用的API


反射之-构造函数

Student添加一个参数的构造函数

 public Student(String name) {
        this.name = name;
        System.out.println("构造函数执行:"+name);
    }

这里我们再用上面的clz.newInstance()就会报错,因为定义了一个有参构造函数,默认的无参构造函数就没有了,就会早不到这个方法。
怎么调用有参的构造函数呢?

try {
            Constructor constructor = clz.getConstructor(String.class);
            Object o = constructor.newInstance("我的外国名字");
        } catch (Exception e) {
            e.printStackTrace();
        }

通过getConstructor()传入参数类型,得到这个构造函数,然后调用newInstance()传入参数,就会创建这个对象,构造函数就会首先执行。
看看打印的日志是不是这样?

打印日志

其他API介绍

首先修改Student类:

/**
 * Created by on 2017/10/31.
 */
public class Student {

    private String name;
    private int age;

    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void printName(String name){
        System.out.println("以后请叫我的外国名字:"+name);
    }

    private void printAge(int age){
        System.out.println("我今年"+age+"岁了");
    }
}

getMethods()测试

Method[] methods = clz.getMethods();
            for (Method method : methods) {
                System.out.println(method.getName());
            }
上面代码执行结果

通过日志可以看出getMethods()获取了Student中除了私有方法外的所有方法,包括父类的

getDeclaredMethods()测试

Method[] declaredMethods = clz.getDeclaredMethods();
            for (Method declaredMethod : declaredMethods) {
                System.out.println(declaredMethod.getName());
            }

上面代码执行结果

通过上述日志可以看到getDeclaredMethods()获取了Student中所有方法,包括私有方法,但不包括父类中的方法

细心的读者会看到无论是clz.getMethods()还是getDeclaredMethods()都对应有通过参数得到对应的某个方法
例如:得到方法名为setAge,参数类型为int.class的方法

 Student o = (Student) clz.newInstance();
            Method getAge = clz.getMethod("setAge",int.class);
            getAge.invoke(o,23);
            System.out.println(o.getAge());

最后通过打印日志可以看出setAge传入的年龄23生效

上面代码执行结果

查看getMethod(...)源码

getMethod(...)源码

第一参数name需要传入指定方法名称,后面parameterTypes是一个可变参数,按参数的类型和顺序依次传参,getDeclaredMethod(...)方法使用类似,可自行动手练习


获取属性
Student修如下

public class Student {

    private String name;
    private int age;
    protected String hobby;
    public String specialTalents;




    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void printName(String name){
        System.out.println("以后请叫我的外国名字:"+name);
    }

    private void printAge(int age){
        System.out.println("我今年"+age+"岁了");
    }

    protected void testProtected(){
       System.out.println("testProtected");
    }

}

getFields()测试

Field[] fields = clz.getFields();
      for (Field field : fields) {
            System.out.println(field.getName());
         }

上面代码运行结果

通过运行结果可以看出,只有公有属性被获取到了
获取属性和获取方法类似,可以通过传入参数获取到对应的属性。读者可自行练习

怎么获取私有属性和方法
要获取私有的就调用带getDeclaredxxxx(),Declared的意思是申明的,公开的,需要注意的是getDeclaredxxxx()可以拿到所有私有的,能获取但不一定能访问。

 Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                System.out.println(field.getName());
            }
上面代码运行结果

访问name属性

Object o = clz.newInstance();
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                if(field.getName().equals("name")){
                    field.set(o,"我的天");
                }
                System.out.println(field.getName());
            }

上面代码运行结果

异常提示,不能使用私有属性
怎么获取私有属性?

Object o = clz.newInstance();
            Field[] fields = clz.getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                if(field.getName().equals("name")){
                    field.set(o,"我的天");
                    System.out.println(((Student)o).getName());
                }
            }

上面代码运行结果

结果可以看到运行成功。
上面的代码只加了field.setAccessible(true);,表示可以访问这个私有属性,然后强转对象打印姓名,发现更改成功。私有方法的访问也是类似的。


关于通过反射怎么得到属性和方法就介绍到这里。
敬请期待下一篇深入理解Java反射(二)
谢谢!

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

推荐阅读更多精彩内容

  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,560评论 18 399
  • 面向对象主要针对面向过程。 面向过程的基本单元是函数。 什么是对象:EVERYTHING IS OBJECT(万物...
    sinpi阅读 1,041评论 0 4
  • 1.import static是Java 5增加的功能,就是将Import类中的静态方法,可以作为本类的静态方法来...
    XLsn0w阅读 1,207评论 0 2
  • 许三多在最后的火车上说:生活就是一个问题接着一个问题。阿甘在候车椅上说:生活就像巧克力,永远不知道下一块...
    火先锋阅读 320评论 0 2
  • 需求背景:在测试系统的过程中,系统中的调度控件会根据请求发起的IP来计算出就近、资源较好、较闲置等条件的服务器供客...
    雷神VeryYoung阅读 983评论 0 0