Java反射
反射
一、框架
后端常见框架有Spring、SpringMVC、Mybatis、SpringBoot等,他们都不约而同或多或少的使用了:
注解+反射+设计模式=框架
显而易见,反射对于学习框架的重要性。
二、Java 反射机制概述
-
Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。
-
加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。
-
举个列子:
这个Java中的反射特别像初中学习光的反射一般。
三、理解Class类并获取Class实例 *
3.1 new关键字
-
new能干什么
- 使用new关键字,我们能在类的外部创建对象,从而使用属性,调用方法。
-
new不能干什么
- 但是在类的外部我们不能通过new创建的对象调用其内部的私有属性和私有方法以及私有的构造器。
3.2 反射作用
-
反射能干什么
- 创建对象【无论是否私有,都可以】
- 使用属性【无论是否私有,都可以】
- 调用方法【无论是否私有,都可以】
...
3.3 反射与封装是否矛盾
根据3.1和3.2我们很容易产生这样的疑问,既然通过反射能调用私有构造器、私有属性、私有方法,那么这与封装的初衷是否矛盾了呢?
答:这两种技术并不矛盾。封装建议我去调什么,反射是调你能调的事。
3.4 理解Class类型的对象
如何理解这句话“加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。”中的“Class类型的对象”?
我们先来明确几点:
-
对象是由类产生的。
- 那么Class类型的对象,对应的类必然是Class。就好比Animal类型的Dog,对应的类是Animal。
-
Java是纯面向对象语言,万事万物皆对象。类也是对象
- Java的API给我们提供了很多类,如File类、Date类...。同时我们自己使用时也自定义了很多类如Person类、Order类、Goods类...
- 想一想,这些都是类,是不是有很多共同的特征,如都有属性、方法...。我们是不是可以把这些共同的东西抽离出来,成为一个类。没错这个类就是Class类。
3.5 获取Class实例
有四种方式
-
方式一:调用运行时类的属性:.class
//方式一:调用运行时类的属性:.class Class<Person> personClass = Person.class; System.out.println(personClass);
-
方式二:通过运行时类的对象,调用getClass()
//方式二:通过运行时类的对象,调用getClass() Person person = new Person(); Class personClass1 = person.getClass(); System.out.println(personClass1);
-
方式三:调用Class的静态方法:forName(String classPath)
//方式三:调用Class的静态方法:forName(String classPath) Class personClass2 = Class.forName("com.noah.Person"); System.out.println(personClass2);
-
方式四:使用类的加载器:ClassLoader (了解)
//方式四:使用类的加载器:ClassLoader (了解) ClassLoader classLoader = ReflectionTest.class.getClassLoader(); Class personClass3 = classLoader.loadClass("com.noah.Person"); System.out.println(personClass3);
3.6 理解类的加载
-
类的加载过程:
程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
运行时类,就作为Class的一个实例。 这也就是为什么让这些实例对象进行地址比较为true的原因System.out.println(personClass==personClass1);// true System.out.println(personClass==personClass2);// true System.out.println(personClass==personClass3);// true System.out.println(personClass2==personClass3);// true
-
换句话说,Class的实例就对应着一个运行时类。
-
加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
来获取此运行时类。
3.5 理解Class类
这个类的实例(对象)不能通过new关键字创建,因为3.6中类的加载“加载到内存中的类,我们就称为运行时类,此
行时类,就作为Class的一个实例。 ”在内存中已经存在了,并且每个类只有一个实例对象。我们可以通过4种方式去获取Class类的对象,而不是去创建。
四、创建运行时类的对象 *
目前创建对象有2种方式,一种是new关键字,另一种是反射【注意:本质上也是调用构造器来创建的】。那么我们通过反射如何创建对象呢?
-
调用newInstance()方法
@Test public void test2() throws IllegalAccessException, InstantiationException { // 注意:这里指定泛型,下面就不用强转了 Class<Person> personClass = Person.class; // 通过反射的方式去创建Person对象 // newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。 /* * 要想此方法正常的创建运行时类的对象,要求: 1.运行时类必须提供空参的构造器 2.空参的构造器的访问权限得够。通常,设置为public。 在javabean中要求提供一个public的空参构造器。原因: 1.便于通过反射,创建运行时类的对象 2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器 * */ Person person = personClass.newInstance(); person.show(); }
五、调用运行时类的指定结构 *
主要是属性和方法
5.1 获取属性
// 调用运行时类的指定结构---获取属性
@Test
public void test3() throws IllegalAccessException, InstantiationException, NoSuchFieldException {
// 获取Class实例
Class<Person> personClass = Person.class;
// 创建person实例
Person person = personClass.newInstance();
// 1. 使用反射的方式获取指定的属性
Field declaredName = personClass.getDeclaredField("name");
// 2. 保证当前的属性是能访问的
declaredName.setAccessible(true);
// 3. 设置属性的值
declaredName.set(person,"大黄");
// 打印属性的值
System.out.println(declaredName.get(person));
}
5.2 获取方法
// 调用运行时类的指定结构---获取方法
@Test
public void test4() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
// 一、非静态的方法
// 1.获取Class实例
Class<Person> p = Person.class;
// 2.创建person对象
Person person = p.newInstance();
// 3. 获取指定的方法
// private String showName(String name)
/**
* getDeclaredMethod方法参数:
* 参数1: 方法的名字
* 参数2: 指明获取方法的形参列表
*/
Method showName = p.getDeclaredMethod("showName", String.class);
// 4. 保证当前的方法是可访问的
showName.setAccessible(true);
/**
* invoke():
* 参数一:方法的调用者
* 参数二:给方法形参赋值的实参
* invoke的返回值即为对应类中调用的方法的返回值
*/
// 5. 调用invoke方法
Object returnValue = showName.invoke(person, "孙悟空");
System.out.println((String)returnValue);
System.out.println("------------------");
// 一、调用静态的方法
// private static void desc()
Method desc = p.getDeclaredMethod("desc");
desc.setAccessible(true);
desc.invoke(Person.class);
}
使用反射重点的在于动态性。