java反射机制
反射概述
什么是反射?
反射是Java的特征之一,是一种间接操作目标对象的机制,核心是JVM在运行的时候才动态加载类,并且对于任意一个类,都能够知道这个类的所有属性和方法,调用方法/访问属性,不需要提前在编译期知道运行的对象是谁,他允许运行中的Java程序获取类的信息,并且可以操作类或对象内部属性.
Java语言反射提供一种动态链接程序组件的多功能方法。它允许程序创建和控制任何类的对象(根据安全性限制),无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。Java reflection 非常有用,它使类和数据结构能按名称动态检索相关信息,并允许在运行着的程序中操作这些信息。
为什么要用反射?
程序中对象的类型一般都是在编译期就确定下来的,而当我们的程序在运行时,可能需要动态的加载一些类,这些类因为之前用不到,所以没有加载到jvm,这时,使用Java反射机制可以在运行期动态的创建对象并调用其属性,它是在运行时根据需要才加载。
一句话概括:
将类的各个组成部分封装为其他对象,这就是反射机制
我们可以通过如下的过程图来进一步理解反射机制:
在以往java基础的学习过程中,我们只关注了图中的源代码阶段和运行时阶段,我们清楚的知道如何构建一个类,以及如何将类实例化,从而应用它。但我们之前忽略了这其中的底层实现原理。我们来思考一个问题:我们编译得到的字节码文件是存在于硬盘的,而我们new 出的对象存在于内存中,那么它是怎么由硬盘到内存中的呢?
这其实就是图中第二个阶段所做的事情。类加载器可以将我们编译得到的字节码文件加载到内存中,并生成Class类对象。它将我们原来创建的类中的成员变量,构造方法,成员方法等内容封装起来。当我们在运行阶段创建对象后,就可以调用。
获取Class对象
在初步了解了反射机制后,我们来学习如何获取Class对象
-
通过Class.forName(“全类名”)的方式。将字节码文件加载进内存,返回CLass对象。
这里的全类名指包名加类名。我们也可以将全类名写入配置文件,然后读取配置文件
-
通过类名.class的方式。通过类名的属性class获取
多用于参数的传递
-
通过对象.getClass()的方式。getClass()方法定义在Object类中,所有的类都继承了Object
多用于对象的获取字节码文件的方式
/**获取Class的实例的方式(前三种常用,第四种了解)
* 1.调用运行时类的属性:.class
* 2.通过运行时类的对象,调用getClass()
* 3.调用Class的静态方法:forName(String classPath)
*
* 4.使用类加载器:ClassLoader
*/
public class Reflection00 {
public static void main(String[] args) throws ClassNotFoundException {
// 1.调用运行时类的属性:.class
Class c1 = Person.class;
System.out.println(c1);
// 2.通过运行时类的对象,调用getClass()
Person person = new Person();
Class c2 = person.getClass();
System.out.println(c2);
// 3.调用Class的静态方法:forName(String classPath)
Class c3 = Class.forName("com.duxinyu.pojo.Person");
System.out.println(c3);
// 4.使用类加载器:ClassLoader
ClassLoader classLoader = Reflection00.class.getClassLoader();
Class c4 = classLoader.loadClass("com.duxinyu.pojo.Person");
System.out.println(c4);
}
}
结论:同一个字节码文件(*.class)在一次程序运行过程中只会被加载一次,不论通过哪种方式获取的Class对象都是同一个
说明
-
Class 没有公共构造方法。Class 对象是在加载类时由 Java 虚拟机以及通过调用类加载器中的defineClass方法自动构造的,因此不能显式地声明一个Class对象。
-
虚拟机为每种类型管理一个独一无二的Class对象。也就是说,每个类(型)都有一个Class对象。运行程序时,Java虚拟机(JVM)首先检查是否所要加载的类对应的Class对象是否已经加载。如果没有加载,JVM就会根据类名查找.class文件,并将其Class对象载入。
-
基本的 Java 类型(boolean、byte、char、short、int、long、float 和 double)和关键字 void 也都对应一个 Class 对象。
每个数组属于被映射为 Class 对象的一个类,所有具有相同元素类型和维数的数组都共享该 Class 对象。 -
一般某个类的Class对象被载入内存,它就用来创建这个类的所有对象。
Class对象的功能
-
在运行时判断任意一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时判断任意一个类所具有的成员变量和方法
-
在运行时调用任一个对象的方法
-
在运行时创建新类对象
-
在使用Java的反射功能时,基本首先都要获取类的Class对象,再通过Class对象获取其他的对象
-
生成动态代理
下面我们来介绍一下Class对象的获取功能:
代码实现
-
获取成员变量
public class Reflection01 { public static void main(String[] args) throws Exception { Class c1=Person.class; //Class对象获取成员变量的功能 // 1.getFields():获取所有public修饰的成员变量 Field[] fields = c1.getFields(); for (Field field : fields) { System.out.println(field); } // 2.getField(String name):获取public修饰的、指定成员变量名的成员变量 Field field1 = c1.getField("name"); System.out.println(field1); System.out.println("============="); // 3.getDeclaredFields():获取所有的成员变量(不考虑权限修饰符,private也可以获取到) Field[] fields1 = c1.getDeclaredFields(); for (Field field2 : fields1) { System.out.println(field2); } System.out.println("================="); // 4.getDeclaredField(String name):获取指定变量名的成员变量(无视权限修饰符) Field id = c1.getDeclaredField("id"); // 若要给非public的属性注入值,则需要暴力反射: 属性名.setAccessible(true); id.setAccessible(true); Person p = new Person(); id.set(p,1); Object o = id.get(p); System.out.println(o); } }
-
获取构造方法
public class Reflection02 { public static void main(String[] args) throws Exception { Class c1= Person.class; //Class对象获取构造方法的功能 // 1.getConstructors():获取所有public 修饰的构造器 Constructor[] cons = c1.getConstructors(); for (Constructor con : cons) { System.out.println(con); } System.out.println("--------------------------------"); // 2.getConstructor(...parameterTypes):获取public 修饰的、指定参数的构造器(括号内为参数的类型) Constructor con1 = c1.getConstructor(String.class,int.class,String.class,int.class); // newInstance():得到构造器后可以创建对象 Object instance = con1.newInstance("张三", 20, "男", 1); System.out.println(instance); System.out.println("================================"); // 3.getDeclaredConstructors():获取所有的构造器 Constructor[] cons1 = c1.getDeclaredConstructors(); for (Constructor con2 : cons1) { System.out.println(con2); } System.out.println("---------------------------"); // 4.getDeclaredConstructor(...parameterTypes):获取指定的构造器 Constructor con2 = c1.getDeclaredConstructor(String.class, int.class, String.class, int.class); System.out.println(con2); // 对于无参构造器,创建对象可简化为: Object instance1 = c1.newInstance(); System.out.println(instance1); } }
-
获取成员方法
public class Reflection03 { public static void main(String[] args) throws Exception { Class<Person> c1 = Person.class; // Class对象获取成员方法的功能 // 1.getMethods():获取所有public修饰的成员方法 Method[] methods = c1.getMethods(); for (Method method : methods) { System.out.println(method); } System.out.println("-------------------------"); // 2.getMethod(String name,...parameterTypes):获取public修饰的、指定方法名和参数的成员方法 Method eat = c1.getMethod("eat"); Person p = new Person(); // invoke(Object obj,Object ...args):执行获取到的方法 eat.invoke(p); Method eat1 = c1.getMethod("eat", String.class); // invoke(Object obj,Object ...args):执行获取到的方法,并传入相应的参数 eat1.invoke(p, "米饭"); // 3.getDeclaredMethods():获取所有的成员方法 Method[] methods1 = c1.getDeclaredMethods(); for (Method method1 : methods1) { System.out.println(method1); } // 4.getDeclaredMethod(String name,...parameterTypes):获取指定方法名和参数的成员方法 Method eat2 = c1.getDeclaredMethod("eat", String.class); eat2.invoke(p,"面"); } }
-
获取类名
public class Reflection04 { public static void main(String[] args) { Class<Person> c1 = Person.class; // Class对象获取类名的功能 // getName():获取类的全类名 String cName = c1.getName(); System.out.println(cName); } }
反射的应用
当我们学到框架的时候,会发现框架的底层实现原理其实就是反射(我也是正在学框架,赶回来补反射的知识!!!)。这里我们用一个简单的例子来初步应用一下反射。
**
* 利用反射实现一个简单的框架,这里面的代码完全不用修改,我们只需要修改对应的配置文件中的信息
* 就可以得到不同的类的实例,并能任意调用其中的方法!
*/
public class ReflectionTemplate {
public static void main(String[] args) throws Exception {
//1.加载配置文件
//1.1创建Properties对象
Properties pro = new Properties();
//1.2通过类加载器,获取class目录下的配置文件
ClassLoader classLoader = ReflectionTemplate.class.getClassLoader();
InputStream in = classLoader.getResourceAsStream("pro.properties");
//1.3以字节流的形式加载该配置文件
pro.load(in);
//2.获取配置文件中定义的数据
String className=pro.getProperty("className");
String methodName=pro.getProperty("methodName");
//3.将该类加载进内存,获取到Class对象
Class<?> aClass = Class.forName(className);
//4.创建对象
Object instance = aClass.newInstance();
//5.获取方法对象
Method method = aClass.getMethod(methodName, String.class);
//6.执行方法
method.invoke(instance,"饭");
// System.out.println(method);
}
}
对应的配置文件:(我建在了根目录下,不要和上面的代码放在同一个包中,会报空指针异常的错误)
className=com.duxinyu.pojo.Person
methodName=eat
取不同的方法时,只需要修改配置文件中的信息,而完全不用修改模板中的代码。(框架的思想)
(该文章是学习尚硅谷与黑马的java反射相关内容所作笔记以及自己的一些理解)