一.概念:
- 引入:
- 万事万物皆对象,那你创建的类型,这个类型是不是对象?
- 如果这个类型也是对象,那这个类型的类型是谁?Class
- 概念:
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象.
- JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
- 通常情况下代码编写阶段到程序运行阶段,主要有以下几个状态
看完这段文字,就会清楚的知道为什么需要反射了!
说完通常情况了,就该说一下特殊情况了。假设有这么一个需求,要求编写一段代码,这个代码的作用是,对于给定的任意一个类,要求调用这个类的任意某个方法。
这个要怎么做呢?
和上面的不同点在于,在开发人员层面,编写上面示例代码时,开发者是明确知道Person类的,知道Person类的所有信息,也知道要调用Person类的某个具体的方法。但是现在这个需求,开发人员什么都不知道了,不知道到底要调用类的哪个方法了,甚至不知道到底要调用哪个类的方法,对于这个类的信息是一无所知,只知道在程序运行期间会给你一个类。
从编译层面来看,在编译阶段,是没有这个类的信息的,编译器也是对这个类一无所知的。这个类的信息是在运行阶段才会有的。
那么就没有办法完成了吗?办法当然是有的,就算是开发人员在开发时不知道这个类是啥,就算是编译器在编译阶段不知道这个类的信息,但是在程序的运行阶段,是可以想办法获取这个类的信息的。只要在程序的运行阶段,我们获取了这个类的所有信息,那么我们想干啥就干啥,想执行它的啥方法就可以执行它的啥方法了。这就是反射。换言之,反射就是在程序的运行期动态获取类的信息。
如下图所示,虽然在代码开发和编译阶段可能不知道某个类的详细信息,但是在运行阶段,当这个类被加载到JVM中了就会生成一个Class对象,我们通过这个Class对象,在运行期一样可以获得这个类对应的信息,这就是反射。
二,创建类的三种方式(抽取成了一个方法):
private static void get_class() throws ClassNotFoundException {
//1、获取对象的字节码
//创建/得到Class类型的三种方式,第三种方式最合适
//1.User.class
Class c1 = User.class;
//获取类型的名字
String name = c1.getSimpleName();
//全名称
String name1 = c1.getName();
System.out.println(name+","+name1);
//2.通过user对象来获取
User us = new User();
Class c2 = us.getClass();
System.out.println(c2.getName());
//3.根据字符串来得到class类型,有可能会报找不到类异常,这种最合适,因为运行的时候可以动态来处理,可以用变量的形式
// Class c3 = Class.forName("com.pro.domain.User");
String xx = "com.pro.domain.User";
Class c3 = Class.forName(xx);
System.out.println(c3.getName());
}
三.操作类当中的属性和方法
private static void get_method() throws Exception {
//buffer线程安全,builder更灵活
/*StringBuffer b1;
StringBuilder b2;
String b3;*/
//getModifiers,如果User类是公共的public,值为1,不是为0
/*System.out.println(c.getModifiers());*/
Class c = Class.forName("com.pro.domain.User");
StringBuffer bf = new StringBuffer();
int modifiers = c.getModifiers();//获取User类的访问修饰符
String cmod = Modifier.toString(modifiers);
bf.append(cmod+" class "+c.getSimpleName()+"{\n");
//----------------操作所有的属性-------------------
Field[] fields = c.getDeclaredFields();//获取到类里面的所有字段
for (Field field : fields) {
//获取属性的访问修饰符
String fmod = Modifier.toString(field.getModifiers());
//获取属性的类型名称
Class<?> ftype = field.getType();
String ftypeName = ftype.getSimpleName();
//属性名
String fname = field.getName();
bf.append("\t"+fmod+" "+ftypeName+" "+fname+";\n");
}
//----------------操作所有的方法-------------------
/*
* 获取成员方法并调用:
*
* 1.批量的:
* public Method[] getMethods():获取所有"公有方法";(包含了父类的方法也包含Object类)
* public Method[] getDeclaredMethods():获取所有的成员方法,包括私有的(不包括继承的)
* 2.获取单个的:
* public Method getMethod(String name,Class<?>... parameterTypes):
* 参数:
* name : 方法名;
* Class ... : 形参的Class类型对象
* public Method getDeclaredMethod(String name,Class<?>... parameterTypes)
*
* 调用方法:
* Method --> public Object invoke(Object obj,Object... args):
* 参数说明:
* obj : 要调用方法的对象;
* args:调用方式时所传递的实参;
):
*/
//getDeclaredMethods对访问修饰符有限定,通过这种方式,对私有方法也可以访问,不带declare就不能访问私有,需要配置才行
Method[] methods = c.getDeclaredMethods();
for (Method method : methods) {
//方法的修饰符
String mmod = Modifier.toString(method.getModifiers());
//方法的返回类型名
String mreturnTypeName = method.getReturnType().getSimpleName();
//方法名
String mname = method.getName();
bf.append("\t"+mmod+" "+mreturnTypeName+" "+mname+"(");
//-------------方法的参数--------------
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
//判断是否是最后一个参数
if(i==parameterTypes.length-1){
bf.append(parameterType.getSimpleName());
}else{
bf.append(parameterType.getSimpleName()+",");
}
}
bf.append("){\n");
bf.append("\t}\n");
}
//-------------构造函数,写起来和普通方法一样,只是方法名不同------------
Constructor cs = c.getDeclaredConstructor();
// cs.
bf.append("}");
System.out.println(bf);
}
四.操作自定义方法
public class XxTest {
public static void main(String[] args) throws Exception {
//拿到类,有三种方式
Class c = Class.forName("com.pro.domain.User");
//newInstance创建类是这个类必须已经加载过且已经连接(Class.forName(“A”)这个过程)
// new创建类是则不需要这个类加载过
//获取c的对象
Object o = c.newInstance();
//通过反射获取方法名,第一个参数是方法名,后面的参数是方法参数
Method say = c.getDeclaredMethod("say", String.class, int .class);
//这个say方法是o对象的
//第一个参数为实例对象,第二个参数为实参。总结:通过反射获取方法名,然后invoke方法注入方法对象和实参
Object result = say.invoke(o, "pkb", 18);
System.out.println(result);
}
}
五.反射再理解:
- 用途
通过前面的叙述,就算是开发人员在开发代码时可以不知道某些类的相关信息,编译器在编译阶段也可以不知道类的相关信息,但是通过在运行期动态获取类的相关信息,依然可以完成许多事情。许多的Java框架比如Spring、mybatis等等,都是基于反射实现的。
总之,最重要的就是要理解一点,对于java类,通常情况下是开发人员自己编写的java文件通过编译得到对应的class文件,或者直接引用别人的jar包得到对应的class文件,在这些方式里面,程序在运行之前就已经获取到类对应的信息了,就无需用到反射。
而反射的作用场景就在于,在程序运行之前,是获取不到对应的class文件的。这些class文件有可能是程序运行中从其他地方获取到的,有可能是动态生成的。在这种情况下,就需要通过反射在运行期来动态获取对应类的相关信息。
- 理解:
在Java语言当中,一切都是类与对象。
比如说有三辆具体的车,每一辆车都有各自的属性(比如颜色)和相同方法(比如前进),我们可以抽象出来车这样一个类,三辆车中的每一辆都是车这个类的一个对象(实例)。
在生活中,既可以有车这个类,还可以有人这个类,也可以有树这个类,总之,我们可以有无穷多个类。那么其实我们可以站在更高的一个角度来看待问题,把各个类都抽象出来形成一个新的类别,这个类别名字就叫作“类”(Class),就像我们可以把几辆车抽象出来形成的一个类叫“车”一样。车这个类是抽象出来的新的“类”的一个实例,人这个类也是抽象出来的新的这个“类”的一个实例。
为了形象化一点,我们可以这样假想以便于理解:对于每一个类,比如车这个类Car,会有对应的Car.class,我们具有无数个类也就具有无数个class文件。想象一下其实每个class文件都是一个对象,一个叫做“类”的这个类的对象。
总而言之:车对应的类型是Car,可以有无数辆具体的车,但只会有一个Car类别;类对应的类型是Class类,可以有无数个具体的类(对应无数个class文件),但只会有一个Class类别。