反射机制:在Java中的反射机制是指在运行状态中,对于任意一个类都能够知道这个类所有的属性和方法;并且对于任意一个对象,都能够调用它的任意一个方法;这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
反射使Java这种静态编译型的语言具有了动态性。
反射具有看透类的能力,类的信息在反射面前都是透明的(包括private的属性和方法都是可以调用)。
学习反射的意义:
反射使我们在编译的时候不知道类型,而是延迟到运行时获得对象的属性调用对象的方法,使得java具有动态性。
Hibernate、Sping、MyBatis都是基于反射来实现的,可以说没有反射就没有这些框架。
框架:反射、泛型、注解
我们把自然界中的事物(对象)抽象出共同的特征,封装成一个类,如Person类,Student类等。
我们有没有想过,这些类有没有共同特征,能不能抽象成别的东西?
我们来看一下
不看不知道,一看,可不就是吗,基本上普通的类都是这么构成的,这样,我们把这些类,类的属性,构造方法,普通方法分别抽象出来又组成了新的类。
也就是这样
类比学习一下:面向对象抽象过程
众多的人 ----> Person类
众多学生 -----> Stundent类
众多的类 -----> Class类:任何一个类里面都包含这些东西:Field[]、Constructor[]、Method[]
众多的属性 ------> Field类
众多构造方法 ------> Constructor类
众多的普通方法 ------> Method类
而这些类就是Java提供的反射机制
Java反射机制主要提供一下功能:
1、在运行时判断任意一个对象所属的类。
2、在运行时构造任意一个类的对象。
3、在运行时判断任意一个类所具有的成员变量和方法。
4、在运行时调用任意一个对象的方法。
获取class对象的三种方式(重点):
1.Class.forName("类路径")
2.类名.class
3.对象实例.getClass();
public void test() throws ClassNotFoundException {
// 1.Class.forName("类路径")
Class clazz1 = Class.forName("com.situ.day18.Student");
// 2.类名.class
Class clazz2 = Student.class;
// 3.对象.getClass();
Student student = new Student();
Class clazz3 = student.getClass();
System.out.println(clazz1 == clazz2);// true
System.out.println(clazz1 == clazz3);// true
System.out.println(clazz2 == clazz3);// true
// 都是同一个
}
反射中的API:
这些API看起来多,其实就是一两种的变换。理解了其实很简单。
1.Constructor、Method、Field分别是指的对构造方法,普通方法,属性的获取。
2.每种里边又有get和getDeclsred,前者指只能获取public修饰的,后者则忽略修饰符可以获取全部。
3.这样四条就又分为了两种,每种两条,其实就是有没有s的区别,即获取特定一个还是获取全部。
下面我们通过几个例子来演示一下他们:
1.获取Student类中所有的public构造方法并打印
public void testConstructor() {
// 类名.class 获取反射对象
Class clazz = Student.class;
// 获取所有public构造方法
Constructor[] constructors = clazz.getConstructors();
// 遍历输出这些构造方法
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
}
2.获取Student类中所有的构造方法并打印,分别打印名字和访问修饰符
public void testConstructor2() {
Class clazz = Student.class;
// 获取所有构造方法
Constructor[] constructors = clazz.getDeclaredConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
// 打印名字
System.out.println(constructor.getName());
// 打印修饰符,这里的输出1234代表不同的修饰符
System.out.println(constructor.getModifiers());
}
}
3.获取指定参数列表的构造方法,利用反射创建对象并打印
public void testConstructor3() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clazz = Student.class;
// 获得指定参数列表的构造方法
Constructor constructor = clazz.getDeclaredConstructor(Integer.class, String.class);
// 利用获得的构造方法创建对象
// 虽然反射可以访问private但是访问之前要加这个声明否则会报下面的错
// java.lang.IllegalAccessException
constructor.setAccessible(true);
Student student = (Student) constructor.newInstance(1, "zhangsan");
System.out.println(student);// student类中重写了toString方法
}
4.反射调用普通方法
public void testMethod() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class clazz = Student.class;
// 当有无参构造方法时可以直接用 class对象.newInstance() 方法创建,而不需获得构造方法
Student student = (Student) clazz.newInstance();
// 反射调用setName() 方法
Method method = clazz.getMethod("setName", String.class);
method.invoke(student,"lisi");
System.out.println(student);
}
剩下的就不一 一演示了,通过这些例子能够学会反射的基本语法,但是看不出反射在java中的作用,下面我们举一个例子来演示反射的作用。
我们构造以上几个源码。
1.IDB接口,里面只有一个
public abstract void getConnection();
方法
2.MySql、Orical、SqlServer三个类,都实现IDB接口
3.DBDemo是测试类,来测试。
接下来,我们如果想在DBDemo中创建一个对象调用getconnection()方法,可以这么写:
// 声明成接口类型new实现类对象
IDB db = new Oracle();
// 调用getConnection()方法
db.getConnection();
这样就实现了调用Oracle类中实现的方法获得Oracle的连接,如果我们要获得MySql的连接也就是调用MySql实现的方法,就要new MySql对象。
但是这样我们每次改连接都要改这个代码,有没有一种方法可以让我们在这个代码中不出现具体的子类代码呢?
常用的方法就是反射+配置文件
可以看到我们在图中有一个db.properties文件,这就是配置文件,里面的内容是:
className=com.situ.day18.reflect.Oracle
就这么一句话,也就是我们获得类的class对象的路径,这样我们在DBDemo中就可以这样写:
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// 反射+配置文件 property properties
// 字节输入流获取文件
FileInputStream fileInputStream = new FileInputStream("JavaSE/src/com/situ/day18/reflect/db.properties");
// new Properties对象
Properties properties = new Properties();
// 将流获取的加载到Properties 对象中
properties.load(fileInputStream);
// 获取配置文件中的className
String className = properties.getProperty("className");
// 创建获取到目标类的class对象
Class clazz = Class.forName(className);
// 利用反射构造对象并调用getConnection()方法
// 两种方式1.无参构造方法
//Constructor constructor = clazz.getConstructor();
//IDB db = (IDB) constructor.newInstance();
// 2.如果调用的是无参构造方法,可以不需要获得Constructor,直接clazz.newInstance();
IDB db = (IDB)clazz.newInstance();
db.getConnection();
}
这样我们就会发现,代码中没有出现具体的子类代码,我们想要获取MySql或者SqlService的连接只需要修改配置文件即可。