《重学Java系列》之 反射(上)

不诗意的女程序媛不是好厨师~
转载请注明出处,From李诗雨—https://blog.csdn.net/cjm2484836553/article/details/103350829

在这里插入图片描述


今天我们来学习学习反射,如果单纯讲反射的使用的话,其实一点也不复杂,我们只需要对照相应的API进行调用就可以了。但是要想对反射有较深层的认识和感悟,那就需要我们多看源码。从真实的实战中,我们才能体会其精髓。

所以,对于反射的学习我将分为上、中、下 3篇来总结。上篇讲思维导图中的前三点:反射是什么,Class类 和 反射的基本使用。中篇从动态代理的源码中去体会反射,下篇从Retrofit源码中再去体会反射。如此步步深入才能学到点东西。

今天讲的是反射基础,但是不要小瞧基础哦,没有基础什么都白搭。话不多说,就让我们开始吧~ ♥

1.反射是什么?

说到反射,可能大家即陌生又熟悉。为什么这么说呢?
其实,反射在我们自己平时的开发过程中用到的并不多,因此它是陌生的。但是,在我们使用到的很多框架代码中,一旦你要去阅读他们的源码,就会经常发现反射的身影,所以说它又是十分熟悉的。

那到底什么是反射呢?

反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

这么官方的解释是不是听着还有点不太明白?没事,我再来换种方法跟大家说。

咱们先不说反射,咱们来先说 【正】

大家回忆一下,【正】常的 我们怎么来实例化一个对象?
是不是通过 new,直接new一个对象出来,然后我就可访问类的成员、调用类的方法了。

//实例化对象的标准用法,也就是所谓的【正】
        Person person=new Person("夏天",18);
        person.say("我是男神~");

【反】 射 就不一样了,它是一开始并不知道我要初始化的类是什么?既然都不知道类是什么,那当然不能用 new 关键字来 正面的 直接的创建对象了。那我们该怎么办呢?这种情况下我们就可以使用 JDK 提供的反射 API 进行反射调用。

        //反射的使用 【反】
        Class personClass = Person.class;//要先获取对应的Class对象
        Person reflePerson=(Person)personClass.newInstance();//通过class得到类的实例
        reflePerson.say("哈哈哈哈哈哈"); //然后才可以对对象进行其他操作

这个时候你是不是会有疑问:
我们平时写代码都知道是什么类啊,直接new个对象出来就好了。什么时候才会出现不知道new的是什么类呢?

你还别说, 反射在我们平时自己代码中还真的基本用不到,它是在我们写框架代码的时候才会用到。如果是框架的开发人员,或者是在我们阅读源码的时候那反射就用的多了。

大家在用Retrofit网络请求的框架的时候,有没有想过:这个框架它在创建的时候,它是怎么知道将来我们的调用者会创建什么样的类呢?他显然是不知道的。那它怎么来new出我们调用者所需要的对象呢?这里我们就需要用到反射了。

所以反射的定义我这么这个时候再看一下,是不是就明白了许多呢:
反射是在运行时才知道要操作的类是什么,并且在运行时来获取类的完整构造,并调用对应的方法

Reflection(反射) 是Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的內部信息,并能直接操作任意对象的内部属性及方法

我们来归纳一下Java反射机制主要提供的功能

  • 运行时构造任意一个类的对象
  • 运行时获取任意一个类所具有的成员变量和方法
  • 运行时调用任意一个对象的方法(属性)

虽然我说的比较多,但相信经过这样的反复说说说之后,你看一遍就可以懂了。

大家都知道Java 是一门面向对象的语言。在面向对象的世界里,万事万物皆对象。
我就又要问问大家了:既然万事万物皆对象,那我们写的每一个类(比如说Person类),是不是应该也是对象呀。那它又是谁的对象呢?
答案是:它是Class类的对象。

好下面我们就来说说Class类。

2.了解Class类

(1). Class类是什么?

上面我们说了,我们写的每一个类都可以看成一个对象,是java.lang.Class 类的对象。

那么Class到底是什么呢?
Class它是一个类它封装了当前的类里面所包括的所有信息

一个类有哪些信息呢?
一个类中有属性,方法,构造器等。
比如说有一个Person类,一个Fruit类,一个Animal类,这些都是不同的类,现在我们需要一个类,用来描述这些类,这就是Class。所以Class是用来描述类的类。

你可以这样理解:Class类是一个对象照镜子的结果,对象可以看到自己有哪些属性,方法,构造器,实现了哪些接口等等。

对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个类的有关信息。

而且大家需要知道:一个类(而不是一个对象)在 JVM 中只会有一个Class实例。
我们可以new出这个类的很多个对象,但是这个类在JDK中只会有一个Class对象。
比如说:一个Person类,它可以new出很多个对象。

Person p1=new Person();
Person p2=new Person();
Person p3=new Person();

但是Person类对应的Class类的实例只有唯一一个。

(2). 获取Class对象的3种方式

那么,我们要怎么样才能获取到Class对象呢?
这里有3种方式:

  • 方式1:通过类名获取 --> 类名.class
  • 方式2:通过对象获取 --> 对象名.getClass()
  • 方式3:通过全类名获取 --> Class.forName(全类名,即包名+类名)
    以Person类为例:
        //获取对应的Class对象(有3种方式)
        Class personClass = Person.class;//方式1:通过类名获取  -->  类名.class
        Class personClass2 = person.getClass();//方式2:通过对象获取 --> 对象名.getClass()
        Class personClass3=Class.forName("reflect.Person"); //方式3:通过全类名获取 --> Class.forName(全类名)  可能会出现“ClassNotFoundException”,必须对其进行捕获或声明以便抛出

(3). Class类的常用方法

在这里插入图片描述

3.反射的基本使用

上面我们已经知道了如何拿到class对象。
但是纯粹的拿到class对象,其实是没有什么实际含义的,我们真正的目的是为了能够设置它的属性,调用它的方法。

那我们如何去获取和修改它的属性、调用它的方法呢?
其实像构造器,属性,方法这些信息在Java中都有对应的类来表示。
在这里插入图片描述
下面就让我们分别来看看该怎么用:

首先我们定义一个Person类:

/**
 * @author shiyu
 * @create 2019-12-02 9:50
 */
public class Person {
    //私有字段
    private String name;
    private int age;

    //公有字段
    public String sex;

    //无参构造方法
    public Person() {
    }

    //有参构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = 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 say(String message){
        System.out.println(message);
    }

    //普通私有方法
    private void secret(){
        System.out.println("我是Person的私有方法");
    }
}

让我们来提取一下Person类的信息:
Person类的思维导图

(1).Constructor的基本使用

在这里插入图片描述
使用要点

  • getConstructors() :可以获取该类的所有构造器
  • getConstructor() :可以获取某一个指定参数的构造器,需要参数列表。
    而且需要注意 传的参数列表必须严格的一样,否则会报NoSuchMethodException。
    比如 【clazz0.getConstructor(String.class, int.class);】//如果不传int.class,而传Integer.class是不行的.
  • 得到构造器后,再调用构造器的 newInstance() 方法可以创建对象

直接上代码来演示使用:

//------------------------Constructor的基本使用-----------------------
        String className0 = "reflect.Person";
        Class<Person> clazz0 = (Class<Person>) Class.forName(className0);

        //getConstructors()---获取全部Constructor对象
        Constructor<Person>[] constructors = (Constructor<Person>[]) clazz0.getConstructors();
        for(Constructor<Person> constructor: constructors){
            System.out.println("getConstructors()获取到的构造器:"+constructor);
        }
        System.out.println();

         //getConstructor()----获取某一个Constructor 对象,需要参数列表
        //  //注意此处传的参数列表必须严格的一样,否则会报有可能NoSuchMethodException
        Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class);//如果传Integer.class是不行的.
        System.out.println("getConstructor()获取到的构造器:"+constructor);
        //调用构造器的 newInstance() 方法创建对象
        Person malegod=constructor.newInstance("夏夏夏",26);
        malegod.say("我负责指导诗雨学习!");
        System.out.println();

我们来看下打印结果:
在这里插入图片描述

(2).Field的基本使用

在这里插入图片描述
使用要点:

  • getDeclaredFields():可以获取 自己的 所有字段( 包括私有的),但不能获取父类字段
  • getDeclaredField(name)—获取指定字段
  • 如果获取的字段是私有的,不管是读还是写,都要先 field.setAccessible(true);才可以。否则会报:IllegalAccessException。

老规矩直接上代码:

//-------------Field的基本使用--------------------
        Person goddess=new Person("贾玲",18);//先new个女神

        String className = "reflect.Person";
        Class clazz = Class.forName(className);

        //getDeclaredFields()---获取 自己的 公有和私有的所有字段,但不能获取父类字段
        Field[] fields = clazz.getDeclaredFields();
        for(Field field: fields){
            System.out.println("getDeclaredFields()获取的字段:"+ field.getName());
        }
        System.out.println();

        //getDeclaredField()---获取指定字段,传入字段的名称
        Field field=clazz.getDeclaredField("name");
        Field field2 = clazz.getDeclaredField("sex");

        //获取的字段是私有的,不管是读还是写都要先设置 field.setAccessible(true);
        field.setAccessible(true);
        System.out.println("getDeclaredField()获取的字段:"+field.getName());//field.getName()是读操作,所以在此之前要设置field.setAccessible(true)
        System.out.println("getDeclaredField()获取的字段:"+field2.getName());

        //"获取指定字段的值"
        Object val = field.get(goddess);
        Object val2 = field2.get(goddess);
        System.out.println(field.getName()+"="+val);
        System.out.println(field2.getName()+"="+val2);
        //修改指定字段的为指定的值
        field.set(goddess,"郭德纲"); //相当于是写操作
        System.out.println(field.getName()+"="+goddess.getName());//这个时候再打印,女神的名字就变成了郭德纲了

看一下打印结果:
在这里插入图片描述

(3).Method的基本使用

在这里插入图片描述
使用要点:

  • getMethods():可以获取clazz对应类的所有公有方法,包括从父类继承来的方法,私有方法不能得到。
  • getDeclaredMethods():只获取当前类的所有方法,包括私有方法
  • getDeclaredMethod():获取指定的方法,需要参数名称和参数列表,无参则不需要写。
    也要注意 传入的参数类型要严格一致。
  • 私有方法的执行,必须在调用invoke之前加上一句method.setAccessible(true)

上代码:

//-------------Method的基本使用--------------------
        Person goddess=new Person("贾玲",18);//有一个女神

        String className = "reflect.Person";
        Class clazz = Class.forName(className);

        //getMethods()---获取clazz对应类所有公有方法,包括从父类继承来的方法
        Method[] methods= clazz.getMethods();
        for(Method method:methods){
            System.out.println("getMethods()获取的方法: "+method.getName()+"()");
        }
        System.out.println("");

        //getDeclaredMethods()---只获取当前类的所有方法,包括私有方法
        methods = clazz.getDeclaredMethods();
        for(Method method:methods){
            System.out.println(" getDeclaredMethods()获取的方法:"+method.getName()+"()");
        }
        System.out.println("");

        //getDeclaredMethod()---获取指定的方法,需要参数名称和参数列表,无参则不需要写
        //注意 是 参数写成int.class
        Method method = clazz.getDeclaredMethod("setAge", int.class);//获取的是方法public void setAge(int age) {  }
        System.out.println("getDeclaredMethod()获取的指定方法:"+method);
        System.out.println("");

        //第一个参数表示执行哪个对象的方法,剩下的参数是执行方法时需要传入的参数"
        method.invoke(goddess,25);

        System.out.println("此时女神的年龄是:"+goddess.getAge());
        System.out.println();

        /*私有方法的执行,必须在调用invoke之前加上一句method.setAccessible(true);*/
        method = clazz.getDeclaredMethod("secret");
        System.out.println(method);
        method.setAccessible(true);//在访问私有域时同样
        method.invoke(goddess);//执行私有方法

打印结果大家可以对照Person类的思维导图看:
在这里插入图片描述在这里插入图片描述

小结与回顾

通过上面的学习我们知道了

获取class对象有3种方式:

  • 1:通过类名获取 --> 类名.class
 Class personClass = Person.class;
  • 2.通过对象获取 --> 对象名.getClass()
Class personClass2 = person.getClass()
  • 3.通过全类名获取 --> Class.forName(全类名)
Class personClass3=Class.forName("reflect.Person"); 

那么请问大家 获取一个类的实例对象方法 有几种呢?
其实是有3种的:

  • 直接new出来
Person person=new Person("夏天",18);
  • 2.获取class对象后,调用newInstance()方法
 Class personClass = Person.class;//要先获取对应的Class对象
 Person reflePerson=(Person)personClass.newInstance();//通过class得到类的实例

3.通过反射拿到这个类的构造器之后,再通过构造器的newInstance获得。

 Constructor<Person> constructor = clazz0.getConstructor(String.class, int.class);
 Person malegod=constructor.newInstance("夏夏夏",26);

今天的内容其实很简单,相信大家看了一遍之后就懂了会了,有了这些基础之后,下次我们再看源码就会顺利很多了~

积累点滴,做好自己~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值