不诗意的女程序媛不是好厨师~
转载请注明出处,From李诗雨—https://blog.csdn.net/cjm2484836553/article/details/103350829】
《重学Java系列》之 反射(上)
今天我们来学习学习反射,如果单纯讲反射的使用的话,其实一点也不复杂,我们只需要对照相应的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类的信息:
(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);
今天的内容其实很简单,相信大家看了一遍之后就懂了会了,有了这些基础之后,下次我们再看源码就会顺利很多了~
积累点滴,做好自己~