反射
反射是Java体系中非常重要的内容,它被称为框架设计的灵魂,这里我们简单介绍下框架的概念。
框架:半成品软件,因为是半成品软件,所以框架不能独立的运行,我们在框架的基础上进行软件的开发,可以简化编码的动作,编写完整的软件。
在掌握反射后学习框架部分时就能有更深入的了解。
反射:将类的各个组成部分封装为其他对象,这就是反射机制。
这句话有点难以理解,我们讲解一下Java代码在计算机汇总经历的三个阶段:
接下来我们对这三个阶段进行逐一讲解:
1.Source源代码阶段:
我们在计算机中编写好了一个Java文件,这个文件中是我们编写好的类Person
类(不是测试类,所以类中不存在主方法),这个类中存在Person
类的成员变量,成员方法以及构造方法,文件以.java
后缀结尾。
当然这个文件还不能运行,在运行前还有一个步骤:编译,我们使用Java中自带的编译器,通过javac
命令来编译以.java
为后缀的Java文件,如果.java
文件中没有语法问题,在编译后会形成一个以.class
后缀的字节码文件,这个字节码文件中包含三部分比较重要的东西:成员变量、成员方法以及构造方法,当然不止这三部分,还有类的名称等等。
但是不论是以.java
后缀还是以.class
后缀的文件,他们本质上都是文件,文件肯定都存在于硬盘之上,并没有进入内存,我们把这个阶段称为Java代码在计算机中的第一个阶段。
2.Runtime运行时阶段:
在我们以前的编程经验中,如果要使用一个已经编写好的类,我们直接创建这个类的对象,通过调用这个对象的成员方法就达到使用这个类的目的。
那么在上述讲解中,如果要使用Person
类,肯定是new Person();
,之后通过对象.方法名
来使程序运行。而此时的对象肯定是在内存中,这样我们才能对对象进行调用,对象在内存中的前提是Person
类的字节码文件被加载进内存,这样我们才能创建Person
类的对象,所以第二个阶段就是字节码文件加载进内存的过程。
3.Class类对象阶段:
通过类加载器可以将Person.class
字节码文件加载进内存中,在内存中如果我们要描述这个字节码文件该如何处理呢?
在Java中万物皆对象,存在一个Class
类,这个类专门用来描述各种字节码文件,因为这个类存在各种字节码文件的一些共同特征:成员变量(获取值或修改值)、成员方法(运行程序)以及构造方法(创建对象)等等,当然我们主要研究的是成员变量、成员方法以及构造方法,这三样东西有他们各自的用途和特征,为了统一描述,我们将这三部分封装为其他的对象,成员变量封装为Field
对象,构造方法封装为Constructor
对象,成员方法封装为Method
对象。一个类中可能有多个成员变量、成员方法和构造方法,所以也可以封装为相应对象的数组,至此我们通过Class
类的对象可以完全的描述一个字节码文件了。
一个Class
类的对象完全对应一个字节码文件,也就是说一个类在计算机中只有一个Class
对象,这个在后面会验证。
在Runtime运行时阶段,我们就可以通过类的Class对象来进行对象的创建等操作,也就是说我们在第三阶段创建的Person
类对象实际上都是通过操作Person
类的Class
对象来完成的,只不过是计算机帮我们完成的,我们以前并没有关注过。
上面就是Java文件在计算机中所经历的三个阶段,反射就是将类的各个组成部分封装为其他对象。
那么使用反射有什么好处呢?
- 在程序运行过程中操作这些对象,比如我们在使用IDE编写程序时,当创建一个对象后,如果再次输入这个对象IDE就会自动给我们进行提示,提示包括这个对象可以执行的成员方法,这其实就是将
Class
对象的Method对象数组
提取出来后将各个Method
对象所包含的成员方法的名字展示出来即可,这就是反射在现实中的一个应用 - 可以解耦,降低程序的耦合性,提高程序的可扩展型
- 等等…
获取字节码Class对象的三种方式
在上面我们讲解了反射的基本概念后,我们就可以获取类的Class
对象,通过这个对象可以进行其他的一些操作
那么如何获取Class
对象呢?有三种方法,每种方法对应Java代码在计算机中的一个阶段:
- 字节码文件在硬盘中,我们需要将字节码文件手动加载进内存中后生成一个该类的Class对象:
Class.forName("全类名");
,返回Class
对象。 - 字节码文件已经加载进内存中,此时已经有了类的
Class
对象,我们直接获取即可:类名.class;
,返回Class
对象。 - 此时已经有了类的对象,我们通过这个对象获取类的
Class
对象:对象.getClass;
,这中方法定义在Object
类中,所有对象都可以使用这种方法获取对应类的Class
对象。
下面我们进行演示:
我们在westos目录下创建一个reflect目录
在这个目录下我们编写一个类ReflectDemo1
,在这个类中我们进行类的Class
对象的获取,我们还需要在westos目录下创建一个pojo目录,这个目录中存放的是我们在测试中需要的一些实体类,在pojo目录下编写Person
类
Person
类的内容为:
package com.westos.pojo;
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
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;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
编写完该类后,这个文件被自动编译为Person.class
字节码文件,但是这个文件依然存在于硬盘之中,我们在ReflectDemo1
中获取Person
类的Class
对象。
package com.westos.reflect;
import com.westos.pojo.Person;
public class ReflectDemo1 {
public static void main(String[] args) throws Exception {
//1.Class.forName(全类名)获取Class对象
Class cls = Class.forName("com.westos.pojo.Person");
System.out.println(cls);
//2.类名.class
Class cls1 = Person.class;
System.out.println(cls1);
//3.对象.getClass()
Person person = new Person();
Class cls2 = person.getClass();
System.out.println(cls2);
}
}
在编写这段代码的过程中可能会抛出异常,我们将异常修改为 Exception
,运行代码,运行结果为:
class com.westos.pojo.Person
class com.westos.pojo.Person
class com.westos.pojo.Person
现在只能证明Person
类的三个Class
对象的字符串表现形式是一致的,并不能表示这三个Class对象是一样的,我们通过算术运算符“==”进行验证
System.out.println(cls == cls1);
System.out.println(cls1 == cls2);
结果为:
true
true
所以可以证明这三个Person
类的Class
类的对象实际上是同一个对象,也就是说一个类在内存中只存在一个Class
类的对象,字节码文件在程序的一次运行过程中只被加载进内存中一次。
获取Class对象的三种方式有自己比较适用的场合:
Class.forName("全类名");
多用于配置文件,把全类名定义在配置文件中,读取文件,加载类类名.class;
多用于参数的传递对象.getClass;
多用于对象的获取字节码的方式
Class对象功能概述
获取Class对象后,我们就可以使用这个对象
- 获取成员变量们
Field[] getFields()
Field getField(String name)
Field[] getDeclaredFields()
Field getDeclaredField(String name)
- 获取成员方法们
Method[] getMethods()
Method getMethod(String name, Class<?>... parameterTypes)
Method[] getDeclaredMethods()
Method getDeclaredMethod(String name, Class<?>... parameterTypes)
- 获取构造方法们
Constructor<?>[] getConstructors()
Constructor<T> getConstructor(Class<?>... parameterTypes)
Constructor<?>[] getDeclaredConstructors()
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
- 获取类名
String getName()
我们对上面的方法进行演示:
获取成员变量
我们在reflect目录下创建ReflectDemo2
类,先测试获取成员变量们
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取成员变量
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field);
}
}
}
运行程序:
没有打印出结果,这是因为这个
getFields()
方法只能获取被public
修饰符修饰的成员变量,getField(String name)
也是只能打印指定的被public
修饰符修饰的成员变量。
我们将Person类的成员变量的权限修饰符进行修改
public String name;
public int age;
运行上述ReflectDemo2
中的主方法,运行结果为:
public java.lang.String com.westos.pojo.Person.name
public int com.westos.pojo.Person.age
我们获取到了Person
类的两个成员变量,在获取到后我们就可以进行设置值和获取值
Object get(Object obj)
void set(Object obj, Object value)
要注意这两个方法都需要一个Object obj
,这是因为成员变量值存在于对象之中,我们要设置成员变量需要指定设置和获取哪个对象的成员变量,所以我们需要给这两个方法中传入对象名,我们对ReflectDemo2
文件进行相应的修改
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
Person person = new Person();
person.setAge(8);
//获取成员变量
Field age = cls.getField("age");
int a = (int) age.get(person);
System.out.println(a);
}
}
运行上面的代码,运行结果为:
8
获取值成功
我们对age
成员变量进行设置,在ReflectDemo2
中添加下面的代码:
age.set(person, 20);
System.out.println(person.age);
运行程序,运行结果为:
8
20
设置值成功
接下来讲解 getDeclaredFields()
方法和getField()
方法的区别:
我们将Person
类中的成员变量的权限修饰符进行修改:
private String name;
private int age;
对ReflectDemo2
也进行修改
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取成员变量
Field[] fields = cls.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
}
}
运行上述程序,查看运行结果
private java.lang.String com.westos.pojo.Person.name
private int com.westos.pojo.Person.age
可以看到我们获取到了被private
权限修饰符修饰的成员变量,所以可以看到 getDeclaredFields()
方法和getField()
方法的区别, getDeclaredFields()
方法可以获得所有的成员变量包括私有的。在获取私有成员变量后我们就可以对这个成员变量进行获取值和设置值的操作。
我们下面进行演示,对ReflectDemo2
中的内容进行修改
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取成员变量
Field age = cls.getDeclaredField("age");
Person person = new Person();
age.set(person, 10);
Object o = age.get(person);
System.out.println(o);
}
}
运行程序,运行结果为:
C:\JAVA\Java\jdk1.8.0_152\bin\java.exe -javaagent:C:\IntelliJIDEA2018.2.6_install\lib\idea_rt.jar=63398:C:\IntelliJIDEA2018.2.6_install\bin -Dfile.encoding=UTF-8 -classpath C:\JAVA\Java\jdk1.8.0_152\jre\lib\charsets.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\deploy.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\access-bridge-64.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\cldrdata.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\dnsns.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\jaccess.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\jfxrt.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\localedata.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\nashorn.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunec.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunjce_provider.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunmscapi.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\sunpkcs11.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\ext\zipfs.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\javaws.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jce.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jfr.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jfxswt.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\jsse.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\management-agent.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\plugin.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\resources.jar;C:\JAVA\Java\jdk1.8.0_152\jre\lib\rt.jar;C:\IdeaProjects\Juntit测试\out\production\Juntit测试;C:\maven\apache-maven-3.6.1\Repository\junit\junit\4.12\junit-4.12.jar;C:\maven\apache-maven-3.6.1\Repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar com.westos.reflect.ReflectDemo2
Exception in thread "main" java.lang.IllegalAccessException: Class com.westos.reflect.ReflectDemo2 can not access a member of class com.westos.pojo.Person with modifiers "private"
at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)
at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)
at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)
at java.lang.reflect.Field.set(Field.java:761)
at com.westos.reflect.ReflectDemo2.main(ReflectDemo2.java:14)
Process finished with exit code 1
程序报错,出现了非法访问异常,所以我们虽然获取到了私有成员变量,但我们并不能对私有成员变量进行设置值和修改值的操作,我们需要干一件事情,在使用私有成员变量之前我们需要忽略访问权限修饰符的安全检查,这就需要一句代码:field.setAccessible(true);
,这也叫做暴力反射,我们在ReflectDemo2
中添加这段代码:
age.setAccessible(true);
运行程序,运行结果为:
10
成功设置和获取值,在反射面前,类的私有成员变量将不再私有
以上就是对反射中获取类的成员变量的讲解
获取构造方法
我们接下来讲解通过反射获取类的构造方法
对ReflectDemo2
中的内容进行更改
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取构造方法
Constructor constructor = cls.getConstructor(String.class, int.class);
System.out.println(constructor);
}
}
运行程序,运行结果为:
public com.westos.pojo.Person(java.lang.String,int)
获取构造方法成功,我们可以通过构造方法来创建对象,查看API文档,获得创建对象的方法为:
T newInstance(Object... initargs)
我们用一用这个方法,对ReflectDemo2
中的内容进行修改
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取构造方法
Constructor constructor = cls.getConstructor(String.class, int.class);
Object o = constructor.newInstance("强静州", 20);
System.out.println(o);
System.out.println(constructor);
}
}
运行上面的程序,运行结果为:
Person{name='强静州', age=20}
public com.westos.pojo.Person(java.lang.String,int)
我们可以看到对象已经创建成功,当然,上面使用的是类的有参构造,我们尝试一下使用空参构造,在ReflectDemo2
中添加下面的代码
Constructor constructor1 = cls.getConstructor();
Object o1 = constructor1.newInstance();
System.out.println(o1);
运行程序,可以看到运行结果为:
Person{name='强静州', age=20}
Person{name='null', age=0}
public com.westos.pojo.Person(java.lang.String,int)
空参构造方法创建对象成功,但是在反射机制中,为空参构造创建对象提供了一种简化操作:Class
对象的newInstance()
方法,我们删除上面添加的代码后添加下面所示的代码:
Object o1 = cls.newInstance();
System.out.println(o1);
运行程序可以看到运行结果为:
Person{name='强静州', age=20}
Person{name='null', age=0}
public com.westos.pojo.Person(java.lang.String,int)
空参构造方法创建对象成功
如果使用私有的构造器来创建对象,我们也需要暴力反射,和成员变量类似,这里不做过多讲解
获取成员方法
我们在Person
类中添加一个方法:
public void eat(){
System.out.println("我正在吃");
}
当然通过反射获取成员方法的方法中有无Declared
和成员变量和构造方法的效果类似
我们对ReflectDemo2
中的内容进行修改:
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取成员方法
Method eat_method = cls.getMethod("eat");
//eat方法没有参数,所以getMethod()方法只需要Person类中的一个方法名也就是eat,如果存在重载,就需要在方法名后指定参数类型的Class对象
//和对成员变量进行获取值和设置值的操作一样,都需要提前指定好一个对象,说明执行的是哪个对象的成员方法
Person person = new Person();
eat_method.invoke(person);
}
}
运行上面的程序,运行结果为:
我正在吃
方法执行成功,我们在Person
类中添加一个eat
方法的重载:
public void eat(String food){
System.out.println("我正在吃" + food);
}
在ReflectDemo2
类中添加几句代码:
Method eat_method1 = cls.getMethod("eat", String.class);
eat_method1.invoke(person, "蛋白粉");
运行程序,运行结果为:
我正在吃
我正在吃蛋白粉
方法执行成功,可以看到方法的重载的处理
这里需要注意一点,查看我们的Person类,只存在我们提前设置好的几个方法:构造方法
、成员变量的get、set方法
、重写的toString方法
以及我们作为测试使用的两个eat方法重载
,但是当我们执行Method[] getMethods()
方法时,我们会看到许多方法我们并没有提前定义好,下面就是测试:
对ReflectDemo2
中的内容进行修改:
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
//获取成员方法们
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
}
}
}
运行上面的代码,运行结果为:
public java.lang.String com.westos.pojo.Person.toString()
public java.lang.String com.westos.pojo.Person.getName()
public void com.westos.pojo.Person.setName(java.lang.String)
public void com.westos.pojo.Person.eat()
public void com.westos.pojo.Person.eat(java.lang.String)
public int com.westos.pojo.Person.getAge()
public void com.westos.pojo.Person.setAge(int)
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
可以看到运行结果中包含了许多我们没在Person
中定义的方法,观察可以发现,这些方法中包含一些Object
类中的方法,也就是说Method[] getMethods()
方法可以获取到类中所有被public
修饰符修饰的成员方法包括继承自Object
类的方法。
如果需要使用私有方法暴力反射同样可以起到作用
获取类名和方法名
在后续的使用中,我们可能需要获取成员方法的名称和类名
,反射机制为我们提供了现成的方法
Class对象.getName();
Method对象.getName();
对ReflectDemo2内容进行修改:
package com.westos.reflect;
import com.westos.pojo.Person;
import java.lang.reflect.Method;
public class ReflectDemo2 {
public static void main(String[] args) throws Exception {
//获取Person类的Class类的对象
Class cls = Person.class;
System.out.println(cls.getName());
//获取成员方法们
Method[] methods = cls.getMethods();
for (Method method : methods) {
System.out.println(method);
System.out.println(method.getName());
}
}
}
运行上面的代码,运行结果为:
com.westos.pojo.Person
public java.lang.String com.westos.pojo.Person.toString()
toString
public java.lang.String com.westos.pojo.Person.getName()
getName
public void com.westos.pojo.Person.setName(java.lang.String)
setName
public int com.westos.pojo.Person.getAge()
getAge
public void com.westos.pojo.Person.eat(java.lang.String)
eat
public void com.westos.pojo.Person.eat()
eat
public void com.westos.pojo.Person.setAge(int)
setAge
public final void java.lang.Object.wait() throws java.lang.InterruptedException
wait
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
wait
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
wait
public boolean java.lang.Object.equals(java.lang.Object)
equals
public native int java.lang.Object.hashCode()
hashCode
public final native java.lang.Class java.lang.Object.getClass()
getClass
public final native void java.lang.Object.notify()
notify
public final native void java.lang.Object.notifyAll()
notifyAll
获得了相应的类名和成员方法名。反射至此讲解完毕