反射
版权声明
- 本文原创作者:清风不渡
- 博客地址:https://blog.csdn.net/WXKKang
引言
因为框架的学习中会大量的用到反射机制,所以个人感觉要想学习框架首先得弄清楚什么是反射,所以将反射列为这一栏的第一篇以作学习记录,大家一起来学习吧
一、什么是反射
要想理解什么是反射,首先要理解什么是类型信息。Java让我们在运行时可以识别对象和类的信息的主要方式有两种:一种是传统的RTTI(“Runtime Type Information”的缩写,意思是:运行时类型信息),它假定我们在编译时已经知道了所有的类型信息;另一种就是反射机制了,它允许我们在才发现和使用类的信息
1、Class对象
想要理解RTTI在Java中的工作原理,首先需要知道类型信息在运行时是如何表示的,这是由Class对象来完成的,它包含了所有与类有关的信息。Class对象就是用来创建所有“常规”对象的,Java使用Class对象来执行RTTI,即时我们正在执行的是类似类型转换这样的操作。
每个类都会产生一个对应的Class对象,也就是保存在.class文件中,所有类都是在对其第一次进行使用时,动态加载到JVM中的,当程序创建一个对类的静态成员引用时,就会加载这个类。而Class对象仅在需要的时候才会被加载,以static修饰的属性、方法以及静态代码块是在类加载时就进行的,加载过程如下:
当我们使用这个类创建对象的时候,类加载器会首先检查这个类的Class对象是否已经被加载过,如果没有加载的话默认的类加载器则会根据类名找到对应的.class文件
想在运行时使用类型信息,必须获取对象(比如类Student对象)的Class对象的引用,使用Class.forName(“类的全路径”)可获取到它的Class对象,或者使用类名.class也可以,并且值得注意的一点是:当我们使用.class的方式来获取类的Class对象的时候不会自动初始化该Class对象,而使用forName方法的时候则会初始化,为了使用类而做的准备工作一般有以下三步:
- 加载:由类加载器完成,找到对应的字节码文件,并将其加载到JVM中,然后创建一个Class对象
- 链接:验证类中的字节码,为静态域分配空间
- 初始化:如果该类有父类(超类),则对其初始化,执行静态初始化器和静态初始化块
2、反射
如果我们不知道某个对象的确切类型的话,RTTI可以告诉你,但是有一个前提:这个类型在编译时必须已知,这样才能使用RTTI来识别它。Class类与java,lang.reflect类库一起对反射进行了支持,该类库中包含了Field(属性)、Method(方法)、Constructor(构造器)类,这些类的对象由JVM在启动时创建,用以表示未知类里对应的成员。这样的话就可以使用Constructor创建新的对象,用get(),set()方法获取和修改类中与Field对象关联的字段,用invoke()方法调用与Method对象关联的方法,另外还可以通过Class对象调用很多有用的方法,对象的信息可以在运行时被完全确定下来,而在编译时不需要知道关于类的任何事情,说到这里,反射和RTTI之间真正的区别就是:
- RTTI:编译器在编译时打开和检查.class文件
- 反射:运行时打开和检查.class文件
小结:
Java反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它们的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能成为java语言的反射机制
反射就是把java类中的各种成分映射成一个个的Java对象
二、使用反射
1、获取class对象的三种方式
要想使用反射创建对象,最基础的就是需要先获取到类的class对象了,有以下三种获取方式:
第一种:通过Class.forName的方式获取,语法如下:
Class cls = Class.forName("类的全路径名称");
第二种:通过类名.class的方式获取,语法如下:
Class cls = 类名.class;
第三种:通过类的对象.getClass方式获取,语法如下:
Class cls = 类的对象.getClass();
2、通过Class对象创建实例
获取到类的Class对象后,我们就可以通过Class对象创建实例了,主要有以下两种创建方式:
第一种:通过newInstance方法创建,语法如下:
Object obj = cls.newInstance();
通过这个方法创建实例,实际上是使用对应类的默认无参构造器来创建对象,这就要求Class对象的对应类必须要有默认的构造器(即无参构造器)
第二种:通过构造方法创建对象,语法如下:
//获取构造器--->参数为对应类构造器的参数类型的class对象
Constructor constructor = cls.getConstructor(Class<?>...parameterTypes);
//通过构造器创建对象--->参数为实际参数
Object obj = constructor.newInstance(Object...initargs);
使用Class对象获取指定的Constructor对象,调用Constructor对象的newInstructor()方法来创建Class对象对应类的实例,可以使用对应类的任意构造器来创建对象
3、通过Class对象调用方法
要想调用方法首先得获取方法的Method对象,方式有如下两种:
第一种:获取指定方法的对象
//获取指定的公有方法
Method method = cls.getMethod(String name,Class<?>...parameterTypes);
//获取指定的私有方法或者公有方法
Method method = cls.getDeclaredMethod(String name,Class<?>...parameterTypes);
//设置执行权限 setAccessible
method .setAccessible(true);
方法有公有的也有私有的,值得注意的是获取到私有方法后,再调用之前我们需要设置执行权限为true
第二种:获取全部方法的对象数组
//获取所有公有的方法
Method[] methods = cls.getMethods();
//获取所有方法(包括私有)
Method[] declaredMethods = cls.getDeclaredMethods();
获取了方法对象之后我们可使用方法对象名.invoke方法来调用它们,语法如下:
Object invoke = method.invoke(Object obj, Object...args);
注:第一个参数为类的实例对象,之后的可变参数为调用方法需要传入的参数,如果没有则不传,如果方法后返回值则接收,如果没有则可以不接收
4、通过Class对象动态操作属性
动态操作属性,我们可以调用类中的get,set方法来操作,也可以使用Field自有的get,set方法来操作,下面我就来通过一个示例演示一下吧:
Student类代码:
package cn.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
public class Student {
public String name;
private String age;
public Student() {
}
public Student(String name,String age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
第一种:调用对应类中的get,set方法进行操作属性
package cn.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* 反射操作成员变量————get,set方法
* 1、创建类的Class对象 Student.class
* 2、通过类的Class对象创建实例 class1.newInstance()
* 3、通过类的Class对象创建属性对象 class1.getDeclaredFields
* 4、构建set,get方法名称
* 5、创建get,set的Method对象 class1.getMethod
* 6、调用方法 invoke
*/
public class Demo {
public static void main(String[] args) {
Class<Student> class1 = Student.class;
try {
Student student = class1.newInstance();
Field[] fields = class1.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
String name = field.getName();
//4、拼接get,set方法名
String getMethodName = "get"+name.substring(0, 1).toUpperCase()+name.substring(1).toLowerCase();
String setMethodName = "set"+name.substring(0, 1).toUpperCase()+name.substring(1).toLowerCase();
Method getMethod = class1.getMethod(getMethodName);
Method setMethod = class1.getMethod(setMethodName, field.getType());
if (name.equals("name")) {
setMethod.invoke(student, "小明");
String message = (String)getMethod.invoke(student);
System.out.println("姓名:"+message);
}else if(name.equals("age")) {
setMethod.invoke(student, "20");
String message = (String)getMethod.invoke(student);
System.out.println("年龄:"+message);
}
}
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
第二种:使用Field类中的get,set方法进行操作属性
package com.etime01;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.lang.reflect.Field;
/**
* 反射操作成员变量————直接操作
* 1、创建类的Class对象
* 2、通过类的Class对象创建类的实例
* 3、通过类的Class对象获取实例属性
* 4、通过Field的get,set方法操作属性
*/
public class Demo {
public static void main(String[] args) {
Class<Student> class1 = Student.class;
try {
Student student = class1.newInstance();
Field[] fields = class1.getDeclaredFields();
for(Field field : fields) {
field.setAccessible(true);
String name = field.getName();
if(name.equals("name")) {
field.set(student, "小明");
String message = (String)field.get(student);
System.out.println(message);
}else if(name.equals("age")) {
field.set(student, "20");
String message = (String)field.get(student);
System.out.println(message);
}
}
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
四、使用反射读取配置文件(XML文件操作)
我们可以使用反射来读取配置文件,这样大大提高了我们的代码扩展性,现在我们就通过一个例子——工厂模式计算器,来学习这一技能吧:
XML配置文件代码:
<?xml version="1.0" encoding="UTF-8"?>
<root>
<open>
<name>+</name>
<value>cn.com.Add</value>
</open>
<open>
<name>-</name>
<value>cn.com.Minus</value>
</open>
<open>
<name>*</name>
<value>cn.com.Ride</value>
</open>
<open>
<name>/</name>
<value>cn.com.Besides</value>
</open>
</root>
Cal接口代码:
package cn.com;
public interface Cal {
double calculate(double num1,double num2);
}
加法运算实现类代码:
package cn.com;
public class Add implements Cal{
@Override
public double calculate(double num1, double num2) {
return num1+num2;
}
}
减法运算实现类代码:
package cn.com;
public class Minus implements Cal{
@Override
public double calculate(double num1, double num2) {
return num1 - num2;
}
}
乘法运算实现类代码:
package cn.com;
public class Ride implements Cal{
@Override
public double calculate(double num1, double num2) {
return num1*num2;
}
}
除法运算实现类代码:
package cn.com;
public class Besides implements Cal{
@Override
public double calculate(double num1, double num2) {
return num1/num2;
}
}
工厂类代码:
package cn.com;
/*
原创作者:清风不渡
博客地址:https://blog.csdn.net/WXKKang
*/
import java.io.File;
import java.util.Iterator;
import java.util.List;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
public class Factory {
public static Cal getCal(String str) {
Cal cal = null;
String path = getPath(str);
if(!path.equals("")) {
try {
Class forName = Class.forName(path);
cal= (Cal) forName.newInstance();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return cal;
}
//获取对应类路径的第一种方式
public static String getPath(String str) {
String path = "";
File file = new File("src/config.xml");
SAXReader reader = new SAXReader();
Document document = null;
try {
document = reader.read(file);
Element root = document.getRootElement();
Iterator<Element> elementIterator = root.elementIterator("open");
while(elementIterator.hasNext()) {
Element next = elementIterator.next();
if(next.elementText("name").equals(str)) {
Element element = next.element("value");
path = element.getText();
}
}
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
if(document!=null) {
document.clearContent();
}
}
return path;
}
//获取对应类路径的第二种方式
private static String getPath2(String str) {
String path = "";
File file = new File("src/config.xml");
SAXReader reader = new SAXReader();
Document document = null;
try {
document = reader.read(file);
Element rootElement = document.getRootElement();
List<Element> elements = rootElement.elements("open");
for(Element element : elements) {
String name = element.element("name").getText();
if(name.equals(str)) {
path = element.element("value").getText();
}
}
} catch (DocumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
document.clearContent();
}
return path;
}
}
测试类代码:
package cn.com;
public class Test {
public static void main(String[] args) {
double num1 = 1;
double num2 = 2;
String str = "-";
Cal cal = Factory.getCal(str);
if(cal!=null) {
double calculate = cal.calculate(num1, num2);
System.out.println(calculate);
}else {
System.out.println("无效的运行结果");
}
}
}
这样,我们就通过反射读取配置文件的方式优化了工厂模式计算器,当有其他运算时我们只需要编写接口的实现类后在配置文件中进行配置就可以了,是不是很方便呢。
好啦,今天的学习就到这里吧!记录学习,记录生活,我还是那个java界的小学生,一起努力吧!!
欢迎各位看官评论探讨哟 ~ ~ 小生在此谢过了 ~ ~