文章目录
- 1.什么是反射?
- 2.Class类对象的加载方式
- 3.通过Class类加载对象获得成员变量Field
- 4.获取构造器Constructor
- 5.获取成员方法Method
- 6.反射小案例
前言:本文主要说说反射的一些知识,我在初学反射时总是云里雾里,这是个啥玩意儿,如果你跟我也有过同样的“遭遇”,看看这篇文章或许多你有帮助。
内容主要包括反射的概念、Class类对象的加载方式、获取Method、Field、Constructor以及在最后我们利用反射来手撸一个小“框架”。
1.什么是反射?
反射(reflection)是Java特征之一,它允许执行中的Java程序自行检查或“理解”并操纵程序的内部属性。将类的各个属性封装成对象就是反射机制。 这话可能有点抽象,下面画一幅图来说明:
学过Java的朋友们都知道我们在初学Java的时候肯定用过记事本写简单Java类然后用javac命令去编译它,编译完之后在本地磁盘上就会有对应的类名.class 文件,这个文件就是字节码文件,按照图中的例子来说,这个Student.class字节码文件里面保存了Student类的一些成员变量和构造方法以及普通成员方法(除了这些还有其他的)。然后类加载器ClassLoader将这个字节码文件加载进内存当中,在内存里面会有一个对象来描述这个字节码文件——Class类对象,使用Class类对象来描述所有字节码文件的特征和行为,里面当然就包括了成员变量、构造方法、普通成员方法(当然还有其他的),并将这些封装为不同的对象,如成员变量就封装成Field[] 对象等。然后将来通过这个Class类对象的一些行为就可以来创建对象了。
再回过头来看看这句话“将类的各个属性封装成对象就是反射机制”应该就容易理解了吧。
有人肯定会问把这些成员都封装起来有什么用呢,也就是说反射的好处是什么?
- 可以在程序运行过程中操作这些对象。比如我们开发用的IDE(Eclipse、IDEA),当我们写到类名或对象然后再敲一个点时,IDE就会跳出一个提示框,提示我们这个对象有哪些方法或变量我们可以调用,这就运用到了反射。
- 可以解耦,提高程序的可扩展性。
- 用于调试器和测试工具。
接下里会讲讲反射的使用方法,包括Class类对象的加载方式、获取成员变量Field、获取构造器Constructor和获取成员方法Method。
2.Class类对象的加载方式
Class类对象有三种加载方式:
- Class.forName(“全类名”):将字节码文件加载进内存,返回Class对象。此用法多用于配置文件,将类名定义在配置文件中。读取文件,加载类
- 类名.class:通过类名的属性class获取
- 对象.getClass():getClass()方法在Object类中定义
上代码:
Student.java
package com.reflection.learn;
/**
* Created on 2020/2/29
* Package com.reflection.learn
*
* @author dsy
*/
public class Student {
private String name;
private Integer age;
public String a;
protected String b;
public Student(String name, Integer age) {
this.name = name;
this.age = age;
}
public Student() {
}
public void eat(){
System.out.println("在吃饭呢");
}
public void sing(String name){
System.out.println("正在唱:"+name);
}
//getter 、 setter and toString
}
测试代码:
package com.reflection.learn;
/**
* Created on 2020/2/29
* Package com.reflection.learn
*
* @author dsy
*/
public class ClassThreeMethods {
public static void main(String[] args) throws Exception {
//1.使用Class.forName()方法加载Class类对象
Class cls = Class.forName("com.reflection.learn.Student");
System.out.println(cls);
//2.使用类.class来加载Class类对象
Class cls2 = Student.class;
System.out.println(cls2);
//3.使用对象.getClass()方法来加载Class类对象
Student student = new Student();
Class cls3 = student.getClass();
System.out.println(cls3);
System.out.println(cls==cls2);
System.out.println(cls==cls3);
}
}
打印输出:
class com.reflection.learn.Student
class com.reflection.learn.Student
class com.reflection.learn.Student
true
true
说明:从运行结果的两个true我们可以得出一个结论:同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不管采取何种方式获得Class类对象都是同一个。不了解==运算符的可以看这里
3.通过Class类加载对象获得成员变量Field
Class类中提供了四种获取成员变量的方法:
- Field getField(String name):获取单个指定名称的public修饰符的成员变量
- Field[] getFields():获取一组public修饰符的成员变量
- Field getDeclaredField(String name):获取单个指定名称的成员变量,不考虑修饰符
- Field[] getDeclaredFields():获取一组成员变量,不考虑修饰符
下面通过代码来演示:
package com.reflection.learn;
import java.lang.reflect.Field;
/**
* Created on 2020/2/29
* Package com.reflection.learn
*
* @author dsy
*/
public class GetClassFields {
public static void main(String[] args) throws Exception {
//0.先通过类.class()方法来获得Class类对象
Class clazz = Student.class;
//四种方式:
//Field getField(String name):获取单个指定名称的public修饰符的成员变量
//Field[] getFields():获取一组public修饰符的成员变量
//Field getDeclaredField(String name):获取单个指定名称的成员变量,不考虑修饰符
//Field[] getDeclaredFields():获取一组成员变量,不考虑修饰符
//1.Field getField(String name)
Field field = clazz.getField("a");//此处若传入"b",则会报错,因为成员b不是public修饰的
//获取成员变量a的值
Student student = new Student();
Object value = field.get(student);
System.out.println(value); //String默认值为null
//设置a的值
field.set(student,"hello");
System.out.println(student);
//2.Field[] getFields()
Field[] fields = clazz.getFields();
for (Field field1 :fields){
System.out.println(field1.getName());
}
//3.Field getDeclaredField(String name):获取指定成员变量,不考虑修饰符
Field declaredField = clazz.getDeclaredField("name");
System.out.println(declaredField);
//访问name的值,因为name是private修饰的,虽说可以访问这个成员变量但不能访问其值,访问前要忽略访问修饰符的安全检查
declaredField.setAccessible(true); //此句不加会有异常:IllegalAccessException
Object value1 = declaredField.get(student);
System.out.println(value1);
//4.Field[] getDeclaredFields():获取所有的成员变量,不考虑修饰符
Field[] declaredFields = clazz.getDeclaredFields();
for (Field aa:declaredFields){
System.out.println(aa);
}
}
}
打印输出:
null
Student{name='null', age=null, a='hello', b='null'}
a
private java.lang.String com.reflection.learn.Student.name
null
private java.lang.String com.reflection.learn.Student.name
private java.lang.Integer com.reflection.learn.Student.age
public java.lang.String com.reflection.learn.Student.a
protected java.lang.String com.reflection.learn.Student.b
4.获取构造器Constructor
主要有四种方式:
- Constructor getConstructor(Class<?>… parameterTypes):获得一个public修饰的构造器并指定构造器的参数类型
- Constructor<?>[] getConstructors():获取所有的public构造方法
- Constructor getDeclaredConstructor(Class<?>… parameterTypes):获取一个构造器并指定构造器的参数类型,不限制修饰符
- Constructor<?>[] getDeclaredConstructors():获取所有的构造方法,不限制修饰符
上代码:
我们先在Student类里面加一个private修饰符的构造方法:
package com.reflection.learn;
/**
* Created on 2020/2/29
* Package com.reflection.learn
*
* @author dsy
*/
public class Student {
// ......
private Student(String name){
this.name = name;
}
//......
}
演示代码:
package com.reflection.learn;
import java.lang.reflect.Constructor;
/**
* Created on 2020/2/29
* Package com.reflection.learn
*
* @author dsy
*/
public class GetConstructors {
public static void main(String[] args) throws Exception {
//0.先通过类.class()方法来获得Class类对象
Class<Student> clazz = Student.class;
//四种方式:
//Constructor<T> getConstructor(Class<?>... parameterTypes)
//Constructor<?>[] getConstructors()
//Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
//Constructor<?>[] getDeclaredConstructors()
//1.Constructor<T> getConstructor(Class<?>... parameterTypes):获得一个public修饰的构造器并指定构造器的参数类型
Constructor<Student> constructor = clazz.getConstructor(String.class,Integer.class);
//通过获得的构造器来实例化对象
Student student = constructor.newInstance("小明",22);
System.out.println(student);
//2.Constructor<?>[] getConstructors():获取所有的public构造方法
Constructor[] constructors = clazz.getConstructors();
for (Constructor constructor1 : constructors){
System.out.println(constructor1);
}
//3.Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取一个构造器并指定构造器的参数类型,不限制修饰符
Constructor declaredConstructor = clazz.getDeclaredConstructor(String.class);
//通过获得的构造器来实例化对象
declaredConstructor.setAccessible(true);
Student declaredStudent = (Student) declaredConstructor.newInstance("小明");
System.out.println(declaredStudent);
//4.Constructor<?>[] getDeclaredConstructors():获取所有的构造方法,不限制修饰符
Constructor[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor constructor2 : declaredConstructors){
System.out.println(constructor2);
}
//如果使用空参构造来实例化对象可以使用如下方法
//Student student1 = clazz.newInstance();
}
}
5.获取成员方法Method
主要有四种方式:
- Method getDeclaredMethod(String name, Class<?>… parameterTypes)
- Method[] getDeclaredMethods()
- Method getMethod(String name, Class<?>… parameterTypes)
- Method[] getMethods()
演示代码:
package com.reflection.learn;
import java.lang.reflect.Method;
/**
* Created on 2020/3/1
* Package com.reflection.learn
*
* @author dsy
*/
public class GetMethods {
public static void main(String[] args) throws Exception {
//0.通过类.class方法获取Class类对象
Class clazz = Student.class;
//获取Method的四种方式:
//Method getDeclaredMethod(String name, Class<?>... parameterTypes)
//Method[] getDeclaredMethods()
//Method getMethod(String name, Class<?>... parameterTypes)
//Method[] getMethods()
//1.Method getDeclaredMethod(String name, Class<?>... parameterTypes)
Method eat_Method = clazz.getMethod("eat");
System.out.println(eat_Method);
//执行eat方法
Student student = new Student();
eat_Method.invoke(student);
//获取sing方法并执行
Method sing_Method = clazz.getMethod("sing", String.class);
System.out.println(sing_Method);
sing_Method.invoke(student,"《海阔天空》");
//获取所有public修饰的方法
//打印发现除了Student类里面我们手写的显示的方法还有Object类里面的方法,如:equals、hashCode等等
Method[] methods = clazz.getMethods();
for (Method method:methods){
System.out.println(method);
//获取方法名称
System.out.println(method.getName());
}
//其他方法也类似不再赘述
}
}
6.反射小案例
看了上面那么多的方法演示是不是有点觉得学的不明不白,这些方法用在哪里呢?都说反射是框架的灵魂,接下来我们利用上面的方法来开发一个小“框架”,需求为:在不改变该类的任何代码的前提下,任意创建类的对象,执行其中任意的方法。
如何实现:
- 反射
- 配置文件
步骤:
- 1、将需要创建对象的全类名和需要执行的方法写在配置文件当中。
- 2、在程序中加载读取配置文件
- 3、使用反射技术来加载类文件进内存
- 4、创建对象
- 5、执行方法
1.首先我们创建一个Car类:
package com.example.learn;
/**
* Created on 2020/3/1
* Package com.example.learn
*
* @author dsy
*/
public class Car {
public void stop(){
System.out.println("正在刹车........");
}
}
2.好了之后我们在项目的src下面创建一个config.properties文件:
里面输入config.properties即可,然后打开该文件写上如下配置:
className = com.example.learn.Car
methodName = stop
3.创建Example_Reflection类:
package com.example.learn;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* Created on 2020/3/1
* Package com.example.learn
*
* @author dsy
*/
public class Example_Reflection {
public static void main(String[] args) throws Exception {
//1.加载配置文件
Properties properties = new Properties();
ClassLoader classLoader = Example_Reflection.class.getClassLoader();
InputStream inputStream = classLoader.getResourceAsStream("config.properties");
properties.load(inputStream);
//取出配置文件中的数据
String className = properties.getProperty("className");
String methodName = properties.getProperty("methodName");
//2.利用反射将类文件加载进内存
Class clazz = Class.forName(className);
//3.创建对象
Object object = clazz.newInstance();
//4.获取stop方法
Method method = clazz.getMethod(methodName);
//5.执行方法
method.invoke(object);
}
}
执行main方法并打印输出:
正在刹车........
这样我们的目的就实现了,那肯定有人会说,需求不是创建任何类的对象吗,你现在只创建了一个。别急我们现在Car的同级目录下再创建一个类名为Bird:
package com.example.learn;
/**
* Created on 2020/3/1
* Package com.example.learn
*
* @author dsy
*/
public class Bird {
public void fly(){
System.out.println("flying......");
}
}
我现在要实现在不改变小“框架”Example_Reflection 类的前提下,创建Bird类的对象并执行fly方法怎么办呢?我们只需在配置文件config.properties中稍作改变即可:
className = com.example.learn.Bird
methodName = fly
然后再执行Example_Reflection 的main方法打印输出:
flying......
到此大功告成,这个小“框架”就被我们实现了,我们没有改变该类的任何代码,只改变了配置文件就实现了这个功能。更改代码与更改配置文件的利弊我就不多说了,显然更改配置文件要方便得多。
喜欢本文的话就点个赞吧
参考:
javadoc文档关于Class类 某位老师的讲解