一、前言引入
- 什么是反射?
Java 反射是可以让我们在运行时获取类的函数、属性、父类、接口等 Class内部信息的机制。通过反射还可以让我们在运行期实例化对象,调用方法,通过调用 get / set方法获取变量的值,即使方法或属性是私有的的也可以通过反射的形式调用,这种“看透 class” 的能力被称为内省,这种能力在框架开发中尤为重要。
- 反射能干吗?
有些情况下,我们要使用的类在运行时才会确定,这个时候我们不能在编译期就使用它,因此只能通过反射的形式来使用在运行时才存在的类(该类符合某种特定的规范,例如
JDBC,这是反射用得比较多的场景。Java类通过java反射机制,可以在程序中访问已经装载到JVM中的java对象描述,实现访问、检测和修改描述java对象本身信息的功能。
-
接下来我们要学习什么?
- 认识反射
- 掌握获取Class对象的三种方法
- 了解反射中常用的方法
- 反射小案例
二、具体内容
2.1 认识反射
反射先认识“反”,在正常情况下,一定是先有类,而后有对象,
import java.util.Date; // 先有类,而后有对象
public class TestDemo {
public static void main(String args[]) throws Exception{
Date data = new Date();
System.out.println(data);//产生对象
}
}
所谓的“反”就是指利用对象找到对象的出处,在Object类里面提供有一个方法:
取得Class对象:
方法 | 释义 |
---|---|
public final Class<?> getClass() | 返回此 Object 的运行时类 |
范例:
public class TestDemo {
public static void main(String args[]) throws Exception{
Date date = new Date();
System.out.println(date);//产生对象
System.out.println(date.getClass());
}
}
- 返回:class java.util.Date 调用了getClass()方法后的输出类的完整名称,等于是找到了对象的出处。
2.2、Class 类对象实例化
获取Class对象的三种方式对应图中 的三个阶段
Java.,lang.Class 是一个类,这个类是反射操作的源头,即:所有的反射都要从此类开始
获取Class对象的三种方式:
- 第一种:调用Object类中的getClass()方法;
此种实例化方法的使用可用于代码优化,不太常用。
public class TestDemo {
public static void main(String args[]) throws Exception{
Date date = new Date();
Class<?> cls = date.getClass();
System.out.println(cls);
}
}
结果返回:class java.util.Date
-
第二种:使用 “类.class” 取得,此种方法在讲解Hibernate、MyBatis、Spring时常用;
public class TestDemo { public static void main(String args[]) throws Exception{ Class<?> cls = Date.class; System.out.println(cls); } }
之前是在产生了类的实例化对象之后取得的class类对象,但是此时并没有实例化对象的产生。
- 第三种:调用Class 类提供的一个方法
方法 | 释义 |
---|---|
public static Class<?> forName(String className) throws ClassNotFoundException | 返回与带有给定字符串名的类或接口相关联的 Class 对象 |
范例:
public class TestDemo {
public static void main(String args[]) throws Exception{
Class<?> cls = Class.forName("java.util.Date");
System.out.println(cls);
}
}结果一样
- 此时可以不使用import语句导入一个明确的类,而类名称是采用字符串的形式进行描述。
2.3 反射实例化对象
当拿到一个类的时候,肯定要直接使用关键字new经行实例化操作,这属于习惯性实例化类对象,但是如果有了calss类实例化对象,那么就可以做到利用反射来实现对象实例化的操作:
方法 | 释义 |
---|---|
public T newInstance() throws InstantiationException,IllegalAccessException | 创建此 Class 对象所表示的类的一个新实例 |
范例:利用反射实例化对象
class Book {
public Book() {
System.out.println("*****book类的无参构造方法******");
}
public String toString() {
return "这是一本书";
}
}
public class TestDemo{
public static void main(String args[]) throws Exception{
Class<?> cls = Class.forName("com.javabase.demo.Book");
Object obj = cls.newInstance();//相当于使用new调用无参构造实例化
}
}
public class TestDemo{
public static void main(String args[]) throws Exception{
Class<?> cls = Class.forName("com.javabase.demo.Book");
Object obj = cls.newInstance();//相当于使用new调用无参构造实例化
Book book = (Book) obj;//向下转型
System.out.println(book);
}
}
实例化 Book对象 将 输出第一行 “book类的无参构造”
向下转型 输出 book对象 将调用 toString() 方法输出“这是一本书”
- 有了反射之后,以后对象的实例化操作不再只是单独的依靠关键字new完成了,但是并不表示new关键字就被完全取代了。
2.4 反射中常用的方法
功能 | 方法名 |
---|---|
得到类对象 | 1.类名.class 2.对象名.getClass() 3.Class.forName(“全类名”) |
类对象得到所有构造方法 | 类对象.getConstructors() 类对象.getDeclaredConstructors() |
类对象得到所有成员方法 | 类对象.getMethods() 类对象.getDeclaredMethods() |
类对象得到所有成员变量 | 类对象.getFields()类对象.getDeclaredFields() |
构造方法实例化对象 | 构造对象.newInstance() |
调用普通方法 | 方法对象.invoke() |
成员变量设置/取值 | 成员对象.set() / 成员对象.get() |
暴力反射 | 对象名.setAccessible(true) |
范例: 类对象得到所有成员变量
mport com.itheima.domain.Person;
import java.lang.reflect.Field;
/**
* @Author: kangna
* @Date: 2019/8/8 10:49
* @Version:
* @Description:
*/
public class ReflectPerson {
public static void main(String[] args) throws Exception{
// 1.获取class对象的第一种方式
Class cls1 = Class.forName("com.itheima.domain.Person");
System.out.println(cls1);
// 2.获取class 对象的第二种方式
Class cls2=Person.class;
System.out.println(cls2);
System.out.println("---------------------------------------------");
// 3. 获取class 对象的第三种方式
Class cls3 = new Person().getClass();
System.out.println("类加载器:" + cls3.getClassLoader());
System.out.println(cls3);
System.out.println(cls1 == cls2); // true
System.out.println(cls1 == cls3); // true
System.out.println(cls2 == cls3); // true
System.out.println("---------------------------------------------");
Class personClass = Person.class;
// 1. Field[] getFields() 获取所有public修饰的成员变量
Field[] files = personClass.getFields();
for (Field field : files) {
System.out.println(field); // public java.lang.String com.itheima.domain.Person.a
}
Field a = personClass.getField("a");
// 获取成员变量的 a 的值
Person p = new Person();
Object value = a.get(p);
System.out.println(value);
a.set(p, "张三");
System.out.println(p);
}
}
范例:反射之Constructor 的使用
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/*
* 构造一个学生类
*/
class Student {
private int age;
private String name;
public Student() {
}
public Student(int age, String name) {
super();
this.age = age;
= name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
= name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
public class Demo_03Reflect {
public static void main(String[] args) throws InstantiationException, IllegalAccessException, NoSuchMethodException, SecurityException, IllegalArgumentException, InvocationTargetException {
// 获得 Class 对象
Class cls = Student.class;
// 方式一 : 通过Calss 对象的方法 去 创建
Object obj = cls.newInstance();
// 方式 二 ,通过构造方法 创建
Constructor cons = cls.getConstructor(int.class, String.class);
Object obj2 = cons.newInstance(23, "jack");
System.out.println(obj2);
}
}
2.5 综合案例:
在案例中我们将使用反射中常用的方法
需求:写一个"框架",通过反射帮我们创建任意类的对象,并且执行其中任意方法,例如有配置文件pro.properties,存储在项目的src文件夹下,内容如下。根据配置文件信息创建一个学生对象Student,Student中包含一个sleep方法,最终实现调用该方法。
方式一实现:配置文件
第一步:创建学生类
public class Student {
public void sleep() {
System.out.println("sleeping");
}
public void eat() {
System.out.println("eatting");
}
}
第二步:创建pro.properties配置文件
className=com.itheima.domain.Student
methodName1=sleep
methodName2=eat
第三步:实现 ReflectTest 类
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
/**
* @Author: kangna
* @Date: 2019/8/8 15:08
* @Version:
* @Description: ReflectTest 测试类实现
*/
public class ReflectTest {
public static void main(String[] args) throws Exception {
// 创建 Properties 对象
Properties pro = new Properties();
// 加载class 目录文件下的配置文件
ClassLoader classLoader = ReflectTest.class.getClassLoader();
InputStream is = classLoader.getResourceAsStream("pro.properties");
pro.load(is);
// 获取配置文件定义的数据
String className = pro.getProperty("className");
String methodName1 = pro.getProperty("methodName1");
String methodName2 = pro.getProperty("methodName2");
// 3.加载该类进内存
Class cls = Class.forName(className);
// 4. 创建对象
Object obj = cls.newInstance();
// 5. 创建方法对象
Method method1 = cls.getMethod(methodName1);
Method method2 = cls.getMethod(methodName2);
// 6. 执行方法
method1.invoke(obj);
method2.invoke(obj);
}
}
- 观察运行结果我们可以看到,运用配置文件的方式,使用反射技术 我们成功的运行了Student类中的方法。
方式二:自定义注解使用反射技术实现上面的案例
第一步:自定义注解(代替配置文件)
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
*
* @author kangna
* @Date 2019年8月8日 下午8:49:16
*
* @description 使用 自定义注解 配置 类,使用 java 反射技术 实现 配置文件的 用注解实现的转换
*/
@Target({ ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Pro {
/*
* 在 注解中 定义属性 ,也就是 定义成员方法
* 自定义的注解是当前的接口继承 java.lang.annotation.Annotation 接口
* 使用 反射技术 定义 子类 实现 Pro 接口对象
*/
String className();
String methodName();
}
第二步:定义Demo类
public class Demo {
public void show() {
System.out.println("Demo....show()");
}
}
第三步:ReflectAnnoTest 类
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
*
* @author kangna
* @Date 2019年8月8日 下午9:07:52
*
* @description 使用 注解 定义类的 全路径 然后使用 反射 获取类的 对象
*/
@Pro(className = "com.itheima.day02.Demo", methodName = "show")
public class ReflectAnnoTest {
public static void main(String[] args) throws Exception {
// 1. 解析
// 1.1 获取, 该类的字节码文件对象
Class<ReflectAnnoTest> reflectAnno = ReflectAnnoTest.class;
// 2. 获取上边的 注释对象,, getAnnotation 获取接口对象 class 对象
Pro an = reflectAnno.getAnnotation(Pro.class);
// 3. 调用注释对象中定义的方法, 获取返回值,,, 使用接口对象 调用定义的属性
String className = an.className();
String methodName = an.methodName();
// 输出验证
System.out.println(className);
System.out.println(methodName);
// 加载该类进内存, 使用 forName方法 读取类的全路径
Class cls = Class.forName(className);
// 创建对象
Object obj = cls.newInstance();
// 创建方法对象
Method method = cls.getMethod(methodName);
method.invoke(obj);
}
}
总结
灵活使用反射能让我们代码更加灵活,这里比如JDBC原生代码注册驱动,hibernate 的实体类,Spring 的AOP等等都有反射的实现。但是凡事都有两面性,反射也会消耗系统的性能,增加复杂性等,合理使用才是真!