我们常说面向对象编程的三大特性是“继承封装多态”,但是绝对不是只有这三种“组合抽象反射”,也是面向对象编程的特性。
定义:
- Java的反射(reflection)机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法(无论pubilc还是private);对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
用途(目前只做了解):
1、在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
2、反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Bean,spring就动态的创建这些类。
反射的基本内容:
- Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型,例如Person p = newStudent();这句代码中p在编译时类型为Person,运行时类型为Student。程序需要在运行时发现对象和类的真实信心。而通过使用反射程序就能判断出该对象和类属于哪些类。
反射相关的类(重要):
-
Class类
代表类的实体,在运行的Java应用程序中表示类和接口 -
Field类
代表类的成员变量、类的属性 -
Method类
代表类的方法 -
Constructor类
代表类的构造方法
如何利用反射获取类对象:
- Class类对象
Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例(注意,这个Class对象存放在方法区,不在堆里面的,所以一个类无论在代码中实际被使用了多少次,但对应的类对象是一个单例,一定只有一个,并且被实例化一次,而且同一个类不需要重复加载)。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类 。
三种获取Class对象的方式:
- 第一种,使用 Class.forName(“类的全路径名”)是静态方法。
前提:已明确类的全路径名有包的时候就必须加包的路径,还要有异常处理,优点就是在写代码的时候可以不用知道类的具体名字,它可以自动在运行时获取,也可以文本读入、用户输入等方式获得。
//通过路径获得class对象,注意forName输入的字符串是类的全路径,如果有包必须得加包的路径
//有好处就是在编译的时候不用知道类名
Class c3 = Class.forName("Cat");
- 第二种,使用 .class 方法。
说明:仅适合在编译前就已经明确要操作的 Class,最为安全可靠,程序性能更高 这说明任何一个类都有一个隐含的静态成员变量 class
Class c2 = Cat.class;
- 第三种,使用类对象的 getClass() 方法
说明:每个类都有隐藏的getClass()方法
Cat cat = new Cat("咪咪", 10);
Class c1 = cat.getClass();
- 一个类在 JVM 中只会有一个 Class 实例,即我们对上面获取的 c1,c2,c3进行 == 对象比较,发现都是true
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c2 == c3);
执行结果:
true
true
true
完整代码:
class Cat {
public String name;
public int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println("Eating");
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//第一种或得class对象的方法,根据实例化对象获得class对象
Cat cat = new Cat("咪咪", 10);
Class c1 = cat.getClass();
//第二种,通过类的隐藏静态变量获得class对象,所以每一个类都会有一个隐藏的静态成员变量class
Class c2 = Cat.class;
//通过路径获得class对象,注意forName输入的字符串是类的全路径,如果有包必须得加包的路径
//第三种有好处就是在编译的时候不用知道类名
Class c3 = Class.forName("Cat");
//因为Class对象在方法区存储,所以一个class对象只能有一个
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c2 == c3);
}
}