Java反射机制
一、什么是反射?
在运行状态中,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。通俗点讲,通过反射,该类对我们来说是完全透明的,想要获取任何东西都可以。
想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象。
JAVA反射机制是在运行状态中,
对于任意一个类,都能够知道这个类的所有属性和方法(动态获取的信息);
对于任意一个对象,都能够调用它的任意一个方法和属性(动态调用对象的方法);
这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
简言之:通过字节码文件对象,去使用该文件中的成员变量、构造方法、成员方法。
获取字节码文件对象的三种方式。
1、Class class1 = Class.forName("全限定类名"); //通过Class类中的静态方法forName,直接获取到一个类的字节码文件对象,此时该类还是源文件阶段,并没有变为字节码文件。
2、Class class2 = Person.class; //当类被加载成.class文件时,此时Person类变成了.class,在获取该字节码文件对象,也就是获取自己, 该类处于字节码阶段。
3、Person p = new Person();
Class class3 = p.getClass(); //通过类的实例获取该类的字节码文件对象,该类处于创建对象阶段
第一种和后两种的区别
后两种你必须明确Person类型。
第一种需要这种类型的字符串就行(开发中用)。
这种扩展更强,不需要知道具体的类,只提供字符串,按照配置文件加载就可以了。
反射的特点:
1.Class类中的静态方法forName()传入的字符串将来会做成配置信息文件,所以以后你不知道程序运行的是谁(是哪个类)。
2.反射是不会看到类的任何信息的。即通过构造方法对象Constructor、成员方法对象Method,调用他们的方法返回值都是Object类型。
(因为任何类型都可以用Object类型接收,基本数据类型会自动装箱为引用数据类型)。
3.反射可以访问私有的东西(前提是class文件未被加密)。
三种获取字节码文件对应的Class类型的对象的方式
要想解剖一个类,必须先要获取到该类的字节码文件对象。
而解剖使用的就是Class类中的方法,所以先要获取到每一个字节码文件对应的Class类型的对象。
.class文件 --> Class类
成员变量 --> Field类
构造方法 --> Constructor类
成员方法 --> Method类
有了字节码文件对象才能获得类中所有的信息,我们在使用反射获取信息时,也要考虑使用上面哪种方式获取字节码对象合理,视不同情况而定。下面介绍Class类的功能。
二、具体操作实例
实例一:
package com.my.fanshe; 2 3 public class Person { 4 private String name; 5 int age; 6 public String address; 7 8 public Person() { 9 } 10 11 private Person(String name) { 12 this.name = name; 13 } 14 15 Person(String name, int age) { 16 this.name = name; 17 this.age = age; 18 } 19 20 public Person(String name, int age, String address) { 21 this.name = name; 22 this.age = age; 23 this.address = address; 24 } 25 26 public void show() { 27 System.out.println("show"); 28 } 29 30 public void method(String s) { 31 System.out.println("method " + s); 32 } 33 34 public String getString(String s, int i) { 35 return s + "---" + i; 36 } 37 38 private void function() { 39 System.out.println("function"); 40 } 41 42 @Override 43 public String toString() { 44 return "Person [name=" + name + ", age=" + age + ", address=" + address + "]"; 45 } 46 47 }
package com.my.fanshe;
/* 4 * 反射:就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。 5 * 6 * Person p = new Person(); 7 * p.使用; 8 * 9 * 要想这样使用,首先你必须得到class文件对象,其实也就是得到Class类的对象。 10 * .class文件 --> Class类 11 * 成员变量 --> Field类 12 * 构造方法 --> Constructor类 13 * 成员方法 --> Method类 14 * 15 * 获取class文件对象的方式: 16 * A:Object类的getClass()方法 17 * B:数据类型的静态属性class(任意数据类型都具备一个class静态属性) 18 * C:Class类中的静态方法(将类名作为字符串传递给Class类中的静态方法forName) 19 * public static Class forName(String className) 20 * 21 * 一般我们到底使用谁呢? 22 * A:自己玩 任选一种,第二种比较方便 23 * B:开发时 第三种 24 * 为什么呢?因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中。 25 */ 26 public class ReflectDemo { 27 public static void main(String[] args) throws ClassNotFoundException { 28 // 方式A 29 Person p = new Person(); 30 Class c = p.getClass(); 31 32 Person p2 = new Person(); 33 Class c2 = p2.getClass(); 34 35 System.out.println(p == p2); // false 36 System.out.println(c == c2); // true 37 38 // 方式B 39 Class c3 = Person.class; 40 // int.class; 41 // String.class; 42 System.out.println(c == c3); // true 43 44 // 方式C 45 // ClassNotFoundException 需要类的全路径(带包名的路径) 46 Class c4 = Class.forName("com.my.fanshe.Person"); 47 System.out.println(c == c4); // true 48 } 49 }
实例二:
package com.my.fanshe; public interface JieKou { public void print(); }
package com.my.fanshe; public class A implements JieKou { private String name; private int age; public A(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } @Override public void print() { System.out.println("实现A接口方法"); } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", age=" + age + '}'; } public int getAge() { return age; } }
package com.my.fanshe; public class B implements JieKou { @Override public void print() { System.out.println("实现B接口方法"); } }
package com.my.fanshe; public class C { public void show(){ System.out.println("实现C方法"); } }
package com.my.fanshe; public class FansheDemo { public static void main(String[] args) throws Exception{ //方法一:通过对象获得Class类类型(静态加载) A a = new A("张三",20); Class c = a.getClass(); System.out.println("Class类类型为:"+c); //方法二:通过类名获得Class类类型 Class c1 = B.class; System.out.println("Class类类型为:"+c1); //方法三:通过动态加载 Class c2 = Class.forName("com.my.fanshe.C"); System.out.println("Class类类型为:"+c2); } }
运行结果: Class类类型为:class com.my.fanshe.A Class类类型为:class com.my.fanshe.B Class类类型为:class com.my.fanshe.C
2、获取类的信息
package com.my.fanshe; public interface JieKou { public void print(); }
package com.my.fanshe; public class A implements JieKou { public String name; private int age; public A(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } @Override public void print() { System.out.println("实现A接口方法"); } @Override public String toString() { return "A{" + "name='" + name + '\'' + ", age=" + age + '}'; } public int getAge() { return age; } }
package com.my.fanshe; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; public class FansheDemo { public static void main(String[] args) throws Exception{ //动态加载类,Class是表示当前类的类类型 //获取字节码文件 Class c = Class.forName("com.my.fanshe.A"); // 通过Class类类型获得类的实例 Constructor constructor = c.getConstructor(String.class,int.class); A a = (A)constructor.newInstance("张三",20); //获取公共属性的成员变量 Field f = c.getField("name"); System.out.println(f.get(a)); //获取私有属性的成员变量 Field f1 = c.getDeclaredField("age"); System.out.println(f1.getType()+" "+f1.getName()); //打开权限 f1.setAccessible(true); System.out.println(f1.get(a)); //获取方法并执行 Method method = c.getMethod("getName"); System.out.println(method.invoke(a)); Method method1 = c.getMethod("getAge"); System.out.println(method1.invoke(a)); Method method2 = c.getMethod("print"); method2.invoke(a); Method method3 = c.getMethod("toString"); System.out.println(method3.invoke(a)); } }
运行结果: 张三 int age 20 张三 20 实现A接口方法 A{name='张三', age=20}
3、反射思维导图
三.反射相关理解的知识
1:反射(理解)
(1)类的加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三步来实现对这个类进行初始化。
加载
就是指将class文件读入内存,并为之创建一个Class对象。
任何类被使用时系统都会建立一个Class对象。
连接
验证:是否有正确的内部结构,并和其他类协调一致。
准备:负责为类的静态成员分配内存,并设置默认初始化值。
解析:将类的二进制数据中的符号引用替换为直接引用。
初始化
就是我们以前讲过的初始化步骤。
注意:Object类的方法:
public final Class getClass() 返回对象的字节码文件对象
Class类的方法:
public String getName() 以 String 的形式返回此 Class 对象所表示的实体名称。(实体包括:类、接口、数组名、基本类型或 void)
即:可以通过Class类中的一个方法,获取对象的真实类的全名称。
(2)类的初始化时机
1.创建类的实例时。
2.访问类的静态变量,或者为静态变量赋值时。
3.调用类的静态方法时。
4.使用反射方式来强制创建某个类或接口对应的java.lang.Class对象时。
5.初始化某个类的子类时。
6.直接使用java.exe命令来运行某个主类时。
(3)类加载器
负责将.class文件加载到内存中,并为之生成对应的Class对象。
虽然我们不需要关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行。
(4)类加载器的组成
Bootstrap ClassLoader 根类加载器
Extension ClassLoader 扩展类加载器
Sysetm ClassLoader 系统类加载器
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载。
比如System类,String类等。在JDK中JRE的lib目录下rt.jar文件中(JDK8以前版本中的位置,JDK9/10位置变化了)。
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载。
在JDK中JRE的lib目录下ext目录。
Sysetm ClassLoader 系统类加载器
负责在JVM启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
一般我们自己写的类通过系统类加载器来加载的。
如若对你有用,记得推荐,如若有误,欢迎指正!
本人原创,转载请说明出处https://www.cnblogs.com/zyx110/