反射
今天我们来学学java另一个很重要的机制–反射;
Java反射机制
在学习之前我们要先了解Java的反射机制是什么?
Java 反射机制是在运行状态中,对于任意一个类,都能够获得这个类的所有属性和方法,对于任意一个对象都能够调用它的任意一个属性和方法。这种在运行时动态的获取信息以及动态调用对象的方法的功能称为 Java 的反射机制。
说的简单一点就是:Java为我们提供了一种在运行期间,可以获取对象的类型、类型的方法、类型的属性、类型构造方法等等的机制,这种方法可以让对象在运行期间认识到自身的结构,并通过方法调用自身,这适合与我们不明确某个Java程序的内部情况而需要使用它时,我们就可以通过反射,一步步获得它的内部信息,并调用它,以完成我们需要的功能;
获取类对象(字节码)
每个类在运行时都会产生一个对应的Class对象,也就是保存在.class文件。所有类都是在对其第一次使用时,动态加载到JVM的,当程序创建一个对类的静态成员的引用时,就会加载这个类。
而要想解剖一个类,必须先要获取到该类的字节码文件对象。就是他的 .class文件 而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象;
Java中给我们提供了三种获取字节码文件对象的方法:
-
通过 Object 类(对象)中的 getClass() 方法;
Object .getClass(); -
通过给定类的字符串名称就可以获取该类的字节码对象;
Class.forName(“类名”);
必须要指定类的全限定名,如果我们写错了类的路径会报ClassNotFoundException 的异常。 -
通过确定的类名,静态的属性.class 来获取对应的 Class 对象;
类名.class;
这种方法要明确到类,然后才能调用类中的静态成员。这里我们定义了一个User类,我们假设不知道它的类型,也不清楚它的内部情况;
通过上面的三种方法我们来调用获取它的字节码对象(类对象);
//方法一
System.out.println(new User().getClass());
// 方法2
System.out.println(Class.forName("reflect.User"));
// 方法3
System.out.println(User.class);
System.out.println(new User().getClass() == Class.forName("reflect.User"));
首先我们通过三种反射方法得到的类对象都一致,即class reflect.User;在最后我们对其中两种方法获取的类对象判断他们是不是同一份类对象;结果是返回true;这就表明在jvm虚拟机中 这个类型信息在jvm仍表现为一个对象,而且只有一份;
反射方法创建对象
我们现在获取了类对象,那我们用它可以创建对象;
- 正常方法创建对象
首先我们正常的方法创建对象的方法为:new 类名();
就直接new类名,获取新对象,那么反射方法是如何创建对象的呢? - 反射方法创建对象
这时我们需要调用一个新的方法newInstance(),帮我们创建对象;
用上面我们获取的类对象,调用newinstance()方法,就可以创建新的对象;
类对象.newInstance();
但是这个方法也是有限制的
限制1: 要求对象有无参构造
限制2: 构造方法不能私有
获取方法信息
我们创建出了对象,也不知道它能做什么,我们需要调用它的方法才能完成想要的操作,可如果我们对这个类的内部信息不明确时,不知道它有什么方法,也不知道它的方法能做什么;这时候我们就需要获取这个类中的所有方法;
- 反射获取方法信息
类对象.getMethods();
这个方法我们可以获取某个类的所有的公共方法,包括它继承的方法,即它的父类方法; 获取的方法会存储到一个方法数组中;
Method[] methods1 = User.class.getMethods();
for(Method a:methods1){
System.out.println(a);
}
我们看到它获取到的方法不仅仅是自身的方法,还有好多Object类的方法,这些方法是我们不需要的,所以我们可以用
类对象.getDeclaredMethods();
只获取本类的所有方法;(public, private, protected,等等)
这里我们获取的就是它本类的所有方法,包括私有的;
同时我们也可以通过方法名,和参数类型获得某个特定方法;
类对象.getMethod(方法名, 参数类型); // 找公共方法, 包括继承的
类对象.getDeclaredMethod(方法名, 参数类型); // 找本类的,不包括继承的
获取构造方法
和上面的获取方法一样,也是调用方法获得,具体就不做赘述了;
类对象.getConstructors(); // 获取所有公共的构造方法
类对象.getDeclaredConstructors(); // 获取本类所有构造方法(private public protected 不加)
类对象.getConstructor(int.class); // 获取int参数类型的构造
类对象.getConstructor(); // 获得无参构造
Constructor<?>[] constructors = User.class.getDeclaredConstructors();
for (Constructor<?> c : constructors) {
System.out.println(c);
}
获取属性信息
类对象.getFields(); // 获取所有公共属性(包括继承的)
类对象.getDeclaredFields(); // 获取本类所有属性(private public protected 不加)(不包括继承的)
类对象.getField(“属性名”);
类对象.getDeclaredField(“属性名”);
Field[] fields = User.class.getDeclaredFields();
for (Field field : fields) {
System.out.println(field);
}
反射调用方法
从上面的各种方法,我们对不明确的类User类有了一个大致的了解;
正常调用方式: 对象.方法名(参数)
反射调用方式: 方法.invoke(对象,参数)
那我们现在开始试着调用它的方法;
User u = User.class.newInstance();
Method m1 = User.class.getDeclaredMethod("m1");
m1.setAccessible(true); // 设置这个方法可以被访问,可以突破访问修饰符的限制
m1.invoke(u); // 反射调用方法(性能低)
反射调用方法需要invoke()方法,
上面我们看到有一个私有的m1无参数方法
private void reflect.User.m1();
这时我们如果要调用它,在正常方法时,我们是不能调用User的私有方法的,可如果是通过反射,我们就可以破解它的私有限制;
m1.setAccessible(true);
这句就是破解private私有限制的语句,参数true,就是设置它私有可被调用,然后
方法 . invoke(对象)
调用方法m1;
调用有参m1的方法时:
public void reflect.User.m1(int,int)
User u1 = User.class.newInstance();
Method m13 = User.class.getDeclaredMethod("m1", int.class, int.class);
m13.invoke(u1, 4, 5);
调用私有的构造方法时:
private reflect.User()
Constructor<User> cons = User.class.getDeclaredConstructor();
cons.setAccessible(true);
User s = cons.newInstance();
System.out.println(s);
- 反射调用方法的优缺点:
缺点:调用复杂,效率低
优点:可以调用私有方法,突破正常方法的限制, 经常用在开源框架中