01 反射机制
什么是反射?
学过Java的人估计都知道反射,反射可以说是Java中一种非常强大的技术,它可以做的事情太多太多。
有句话说的很好:反射是框架的灵魂
。
没有反射就没有那么多优秀的框架,理解反射对以后学习框架底层源码会很有帮,很多优秀的开源框架都是通过反射完成的。
JAVA反射机制是在运行状态
中,对于任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息
以及动态调用对象的
方法的功能称为java语言的反射机制。
上面就是对Java的反射机制的描述,读完感觉怎么样,是不是有点懵,那你接着向下看!!!
举例
何为运行状态中动态获取的信息
以及动态调用对象的方法
,我们看两段代码。
-
一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的,于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。
//先创建一个猫的对象 Cat cat=new Cat(); //使用这个对象去调用方法,抓老鼠 cat.catchMouse();
-
而反射则是一开始并
不知道
我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了,这时候,我们使用 JDK 提供的反射 API 进行反射调用//类的全路径 String str="com.aismall.reflection.Cat" // 加载类,返回Class类类型的对象cls Class clz = Class.forName(str); // 通过Class类中的newInstance方法,获取我们传入com.aismall.reflection.Cat类的对象 Object object = cls.newInstance(); //通过Class类中的getMethod方法,获取com.aismall.reflection.Cat类的catchMouse方法对应的对象 Method method = clz.getMethod("catchMouse"); // 通过方法对应的对象来调用该方法类对应的对象 // 传统是:对象.方法() // 反射是:方法.invoke(对象) method.invoke(object);
上面两段代码的执行结果是完全一样的,但是其思路完全不一样:
- 第一段代码在
未运行时
就已经确定了要运行的类(Cat)。 - 第二段代码则是在
运行时
通过字符串值
才得知要运行的类(com.aismall.reflection.Cat),然后调用该类的方法,这就是所谓的在运行时动态获取的信息
以及动态调用对象的方法
。
有的人该问了,为什么要这么麻烦,直接new不是更好吗,高搞得花里胡哨的是害怕我们学会吗????
下面我们就来谈谈反射机制的好处!!!!
反射机制的优点
前面提到反射是框架的灵魂
,学过框架的小伙伴应该知道框架的开闭原则
,就是对扩展开放,对修改关闭,这就体现出反射的好处了。
刚才我们那个Cat类中只有一个方法catchMouse
,现在我们想给这个Cat增加一个功能sayMiao
。
如果我之前代码中通过new的方式创建了Cat的实例,然后调用了这个catchMouse
方法,如下:
//先创建一个猫的对象
Cat cat=new Cat();
//使用这个对象去调用方法,抓老鼠
cat.catchMouse();
因为添加了新功能,我们不想使用哪个功能了,我们现在只想让它sayMiao
,不想让它catchMouse
了
可能是这只猫进城了,被当成宠物喵了,每天只要喵喵叫的卖萌就可以了!!!
这时候是不是要修改源码,如下:
//先创建一个猫的对象
Cat cat=new Cat();
//使用这个对象去调用方法,抓老鼠
//cat.catchMouse();
cat.sayMiao();
这样做是违反开闭原则的,拓展可以,修改源码就不可以
如果引入反射机制,我们就可以通过修改配置文件,不动源码,来实现功能的修改,如下:
-
配置文件内容如下:
classPath=com.aismall.reflection.Cat //methodName=catchMouse //我们把methodName的值修改一下,不动下面的源码,就可以实现功能的修改 methodName=sayMiao
// 读取配置文件 Properties properties= new Properties (); properties.load(new FileInputStream("application.properties")) //获取类的全路径 String classPath=properties.get("classPath").toString(); //获取方法名 String methodName=properties.get("methodName").toString(); Class clz = Class.forName(classPath); Object object = cls.newInstance(); Method method = cls.getMethod(methodName); method.invoke(object);
注意:修改配置文件,不是修改源码
,重申一下
,开闭原则是,对扩展开放(也就是增加功能是可以修改源码的),对修改关闭(其他情况下的修改是不允许的)。
反射机制的缺点
使用反射基本是解释执行,对执行速度有影响
下面让我们一点一点的来深入的学习反射机制吧!!!!!
02 反射机制的功能
- 在运行时判断任意一个对象所属的类
- 在运行时构造任意一个类的对象
- 在运行时得到任意一个类所具有的成员变量和方法
- 在运行时调用任意一个对象的成员变量和方法
- 生成动态代理
03 反射相关的类
java.lang.Class
:代表一个类,Class对象表示某个类加载后在堆中的对象。java.lang.reflect.Constructor
:代表类的构造方法,Constructor对象代表某个类的构造方法。java.lang.reflect.Method
:代表类的方法,Method对象代表某个类的方法。java.lang.reflect.Field
:代表类的成员变量,Field对象代表某个类的成员变量。
说一下几个注意的点:
- Method类的对象一般是无法获取类的私有属性
- 通过Constructor对象的方法不仅可以获取空参构造函数,也可以获取带参数的构造函数。
04 Class类
概述
-
Class也是类,因此也继承Object类,如下图:
-
Class类的对象不是new出来的,而是系统创建的。
-
对于某个类对应的Class类对象,在内存中只有一份,因此类只加载一次(想要详细了解的可以去查:
双亲委派机制
)//我们通过反射的方式对Cat类加载两次 Class cls1=Class.forName("com.aismall.reflection.Cat"); Class cls2=Class.forName("com.aismall.reflection.Cat"); //通过打印的hashCode值可以看出,对应的是同一个值,也就是该类只加载了一次 System.out.println(cls1.hashCode()); //617901222 System.out.println(cls2.hashCode()); //617901222
-
每个类的实例都会记得自己是由那个Class实例所生成
-
通过Class类可以完整得到一个类的完整结构,通过一系列API
-
Class对象是存放在堆区的
-
类的字节码二进制数据是存放在方法区的,有的地方也把方法区称为元数据去,用来存放类的元数据(包括方法代码,类名,方法名,访问权限等)
代码演示
Cat.class
public class Cat {
//属性
public String name;
public int age;
// 无参构造
public Cat() {
}
public void catchMouse(){
System.out.println("猫爪老鼠");
}
}
testCat.class
public class testCat {
public static void main(String[] args) {
//1、获取Class类对象对应的Class(Cat),注意:不是对应的对象,使对应的Class
Class cls=Class.forName("com.aismall.reflection.Cat");
//返回值为是哪个类的Class对象,此处为com.aismall.reflection.Cat类的Class对象
System.out.println(cls); // class com.aismall.reflection.Cat
//2、我们也可以通过下面的方法获取生成该Class对象的类
System.out.println(cls.getClass()); //class java.lang.Class
//3、获取包名
System.out.println(cls.getPackage()); //package com.aismall.reflection
//4、获取全类名
System.out.println(cls.getName()); //com.aismall.reflection.Cat
//5、通过Class对象对应的Class(Cat),创建该Class对应的实例(cat)
Cat cat=(Cat)cls.newInstance();
//6、通过反射获取对象的属性
Field age=cls.getField("age");
System.out.println(age.get(cat)); //0 int类型默认赋值为0
//7、通过反射给属性赋值
age.set(cat,666);
System.out.println(age.get(cat)); //666
//8、获取所有的字段
Field[] fields=cls.getFields();
for (Field field:fields) {
System.out.println(field.getName()); // name age
}
//9、获取无参构造器
Constructor constructor=cls.getConstructor();
Cat cat1=(Cat) constructor.newInstance();
cat1.catchMouse(); //猫爪老鼠
//10、Class类中的方法有很多,可以自己尝试着调用一下看看结果
}
}
获取Class类对象的六种方式
方式一:
- 前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法
forName()
获取,可能抛出ClassNotFoundException
- 实例:
Class cls1=Class.forName(classPath);
- 应用场景:多用于配置文件读取类全路径加载类。
方式二:
- 前提:若已知具体的类,通过类的Class获取,该方式最为安全可靠,程序性能最高
- 实例:
Class cls2=Cat.class;
- 应用场景:多用于参数传递,比如通过反射得到对应构造器对象。
方式三:
- 前提:已知某个类的实例,调用该实例的
getClass()
方法获取Class对象。 - 实例:
Class clazz=对象.getClass();
- 应用场景:通过创建好的对象获取Class对象
方式四:通过类加载器来获取类的对象
方式五:基本数据类型按照如下方式获得Class对象
方式六:基本数据类型对应的包装类,可以通过包装类.TYPE来获取对应的Class对象
public class testCat {
public static void main(String[] args) throws ClassNotFoundException {
// 一般都是通过配置文件读取,我们此处为了方便,直接写死
String classPath="com.aismall.reflection.Cat";
// 方式一
Class cls1=Class.forName(classPath);
System.out.println(cls1); // class com.aismall.reflection.Cat
//方式二
Class cls2=Cat.class;
System.out.println(cls2); //class com.aismall.reflection.Cat
// 方式三
Cat cat=new Cat();
Class cls3=cat.getClass();
System.out.println(cls3); //class com.aismall.reflection.Cat
//方式四:通过类加载器来获取类的对象
// 先拿到Cat的类加载器
ClassLoader classLoader=cat.getClass().getClassLoader();
// 通过类加载器得到对应的对象
Class cls4=classLoader.loadClass(classPath);
System.out.println(cls4); //class com.aismall.reflection.Cat
System.out.println(cls1.hashCode()); //617901222
System.out.println(cls2.hashCode()); //617901222
System.out.println(cls3.hashCode()); //617901222
System.out.println(cls4.hashCode()); //617901222
//方式五:基本数据类型按照如下方式获得Class对象
Class<Integer> cls5=int.class;
System.out.println(cls5);
//方式六:基本数据类型对应的包装类,可以通过包装类.TYPE来获取对应的Class对象
Class cls6=Integer.TYPE;
System.out.println(cls6);
System.out.println(cls5.hashCode()); //1159190947
System.out.println(cls6.hashCode()); //1159190947
}
}