Java反射机制

Java反射机制

简介

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助于 Reflection API 取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。加载完类之后,在堆内存的方法区中就产生了一个 Class 类型的对象(一个类只有一个 Class 对象),这个对象就包含了完整的类的结构信息。我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

Java 反射机制研究及应用

Java 反射机制提供的功能:

• 在运行时判断任意一个对象所属的类
• 在运行时构造任意一个类的对象
• 在运行时判断任意一个类所具有的成员变量和方法在运行时获取泛型信息
• 在运行时调用任意一个对象的成员变量和方法
• 在运行时处理注解
• 生成动态代理

反射相关的主要 API

java.lang.Class:代表一个类 java.lang.reflect.Method:代表类的方法
java.lang.reflect.Field:代表类的成员变量 java.lang.reflect.Constructor:代表类
的构造器 … …

反射的优缺点

优点:
• 提高了 Java 程序的灵活性和扩展性,降低了耦合性,提高自适应能力
• 允许程序创建和控制任何类的对象,无需提前硬编码目标类
缺点:
• 反射的性能较低。
– 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
• 反射会模糊程序内部逻辑,可读性较差。

理解 Class 类并获取 Class 实例

要想解剖一个类,必须先要获取到该类的 Class 对象。而剖析一个类或用反射解
决具体的问题就是使用相关 API:
• java.lang.Class
• java.lang.reflect.*
所以,Class 对象是反射的根源。

理解 Class

理论上 在 Object 类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()
以上的方法返回值的类型是一个 Class 类,此类是 Java 反射的源头,实际上所谓反射从程序的运行结果来看也很好理解,即:可以通过对象反射求出类的名称。

Java反射机制_第1张图片

对象照镜子后可以得到的信息:某个类的属性、方法和构造器、某个类到底实现了哪些接口。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。一个 Class 对象包含了特定某个结构(class/interface/enum/annotation/primitive type/void/[])的有关信息。

• Class 本身也是一个类
• Class 对象只能由系统建立对象
• 一个加载的类在 JVM 中只会有一个 Class 实例
• 一个 Class 对象对应的是一个加载到 JVM 中的一个.class 文件
• 每个类的实例都会记得自己是由哪个 Class 实例所生成
• 通过 Class 可以完整地得到一个类中的所有被加载的结构
• Class 类是 Reflection 的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class 对象

内存结构上

Java反射机制_第2张图片

说明:上图中字符串常量池在 JDK6 中存储在方法区;JDK7 及以后,存储在堆空间。

获取 Class 类的实例(四种方法)

方式 1:要求编译期间已知类型

前提:若已知具体的类,通过类的 class 属性获取,该方法最为安全可靠,程序性能最高
实例:

Class clazz = String.class;

方式 2:获取对象的运行时类型

前提:已知某个类的实例,调用该实例的 getClass()方法获取 Class 对象
实例:

Class clazz = "www.atguigu.com".getClass();

方式 3:可以获取编译期间未知的类型

前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法forName()获取,可能抛出 ClassNotFoundException
实例:

Class clazz = Class.forName("java.lang.String");

方式 4:其他方式

前提:可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
实例:

ClassLoader cl = this.getClass().getClassLoader();
Class clazz4 = cl.loadClass("类的全类名");

再举例:

public class GetClassObject {
@Test
public void test01() throws ClassNotFoundException{
Class c1 = GetClassObject.class;
GetClassObject obj = new GetClassObject();
Class c2 = obj.getClass();
Class c3 = Class.forName("com.atguigu.classtype.GetClassObjec
t");
Class c4 = ClassLoader.getSystemClassLoader().loadClass("com.
atguigu.classtype.GetClassObject");
System.out.println("c1 = " + c1);
System.out.println("c2 = " + c2);
System.out.println("c3 = " + c3);
System.out.println("c4 = " + c4);
System.out.println(c1 == c2);
System.out.println(c1 == c3);
System.out.println(c1 == c4);
}
}

哪些类型可以有 Class 对象

简言之,所有 Java 类型!
(1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类 (2)interface:接口 (3)[]:数组 (4)enum:枚举 (5)annotation:注解@interface (6)primitive type:基本数据类型 (7)void

Class 类的常用方法

static Class forName(String name) 返回指定类名 name 的 Class 对象

Object newInstance() 调用缺省构造函数,返回该 Class 对象的一个实例

getName() 返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)名称

Class getSuperClass() 返回当前 Class 对象的父类的 Class 对象

Class [] getInterfaces() 获取当前 Class 对象的接口

ClassLoader getClassLoader() 返回该类的类加载器

Class getSuperclass() 返回表示此 Class 所表示的实体的超类的Class

Constructor[] getConstructors() 返回一个包含某些 Constructor 对象的数组

Field[] getDeclaredFields() 返回 Field 对象的一个数组

类的加载与 ClassLoader 的理解

类的生命周期

类在内存中完整的生命周期:加载–>使用–>卸载。其中加载过程又分为:装载、链接、初始化三个阶段。

Java反射机制_第3张图片

类的加载过程

当程序主动使用某个类时,如果该类还未被加载到内存中,系统会通过加载、链接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM 将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载。

类的加载又分为三个阶段:

(1)装载(Loading)

将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象。此过程由类加载器完成

(2)链接(Linking)

①验证 Verify:确保加载的类信息符合 JVM 规范,例如:以 cafebabe 开头,没有安全方面的问题。
②准备 Prepare:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
③解析 Resolve:虚拟机常量池内的**符号引用(常量名)替换为直接引用(地址)**的过程。

(3)初始化(Initialization)

• 执行类构造器()方法的过程。类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)。
• 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
• 虚拟机会保证一个类的()方法在多线程环境中被正确加锁和同步。

类加载器(classloader)

Java反射机制_第4张图片

类加载器的作用

将 class 文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的 java.lang.Class 对象,作为方法区中类数据的访问入口

类缓存:标准的 JavaSE 类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,它将维持加载(缓存)一段时间。不过 JVM 垃圾回收机制可以回收这些 Class 对象。

类加载器的分类(JDK8 为例)

JVM 支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader)和自定义类加载器(User-Defined ClassLoader)。
从概念上来讲,自定义类加载器一般指的是程序中由开发人员自定义的一类类加载器,但是 Java 虚拟机规范却没有这么定义,而是将所有派生于抽象类ClassLoader 的类加载器都划分为自定义类加载器。无论类加载器的类型如何划分,在程序中我们最常见的类加载器结构主要是如下情况:

Java反射机制_第5张图片

(1)启动类加载器(引导类加载器,Bootstrap ClassLoader)

• 这个类加载使用 C/C++语言实现的,嵌套在 JVM 内部。获取它的对象时往往返回null
• 它用来加载 Java 的核心库(JAVA_HOME/jre/lib/rt.jar 或 sun.boot.class.path 路径下的内容)。用于提供 JVM 自身需要的类。
• 并不继承自 java.lang.ClassLoader,没有父加载器。
• 出于安全考虑,Bootstrap 启动类加载器只加载包名为 java、javax、sun 等开头的类
• 加载扩展类和应用程序类加载器,并指定为他们的父类加载器。

(2)扩展类加载器(Extension ClassLoader)

• Java 语言编写,由 sun.misc.Launcher$ExtClassLoader 实现。
• 继承于 ClassLoader 类
• 父类加载器为启动类加载器
• 从 java.ext.dirs 系统属性所指定的目录中加载类库,或从 JDK 的安装目录的 jre/lib/ext子目录下加载类库。如果用户创建的 JAR 放在此目录下,也会自动由扩展类加载器加载。

(3)应用程序类加载器(系统类加载器,AppClassLoader)

• java 语言编写,由 sun.misc.Launcher$AppClassLoader 实现
• 继承于 ClassLoader 类
• 父类加载器为扩展类加载器
• 它负责加载环境变量 classpath 或系统属性 java.class.path 指定路径下的类库
• 应用程序中的类加载器默认是系统类加载器。
• 它是用户自定义类加载器的默认父加载器
• 通过 ClassLoader 的 getSystemClassLoader()方法可以获取到该类加载器

(4)用户自定义类加载器(了解)

• 在 Java 的日常应用程序开发中,类的加载几乎是由上述 3 种类加载器相互配合执行的。在必要时,我们还可以自定义类加载器,来定制类的加载方式
• 体现 Java 语言强大生命力和巨大魅力的关键因素之一便是,Java 开发者可以自定义类加载器来实现类库的动态加载,加载源可以是本地的 JAR 包,也可以是网络上的远程资源。
• 同时,自定义加载器能够实现应用隔离,例如 Tomcat,Spring 等中间件和组件框架都在内部实现了自定义的加载器,并通过自定义加载器隔离不同的组件模块。这种机制比 C/C++程序要好太多,想不修改 C/C++程序就能为其新增功能,几乎是不可能的,仅仅一个兼容性便能阻挡住所有美好的设想。
• 自定义类加载器通常需要继承于 ClassLoader

反射的基本应用

有了 Class 对象,能做什么?

应用 1:创建运行时类的对象

这是反射机制应用最多的地方。创建运行时类的对象有两种方式:
方式 1:直接调用 Class 对象的 newInstance()方法
要 求: 1)类必须有一个无参数的构造器。2)类的构造器的访问权限需要足够。
方式 2:通过获取构造器对象来进行实例化
方式一的步骤:
1)获取该类型的 Class 对象 2)调用 Class 对象的 newInstance()方法创建对象

方式二的步骤

1)通过 Class 类的 getDeclaredConstructor(Class … parameterTypes)取得本类的指定形参类型的构造器 2)向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。 3)通过 Constructor 实例化对象。如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)

//示例代码:
package com.atguigu.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestCreateObject {
 @Test
 public void test1() throws Exception{
// AtGuiguClass obj = new AtGuiguClass();//编译期间无法创建
 Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguC
lass");
 //clazz 代表 com.atguigu.ext.demo.AtGuiguClass 类型
 //clazz.newInstance()创建的就是 AtGuiguClass 的对象
 Object obj = clazz.newInstance();
 System.out.println(obj);
 }
 @Test
 public void test2()throws Exception{
 Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguD
emo");
 //java.lang.InstantiationException: com.atguigu.ext.demo.AtGu
iguDemo
 //Caused by: java.lang.NoSuchMethodException: com.atguigu.ex
t.demo.AtGuiguDemo.<init>()
 //即说明 AtGuiguDemo 没有无参构造,就没有无参实例初始化方法
 Object stu = clazz.newInstance();
 System.out.println(stu);
 }
 @Test
 public void test3()throws Exception{
 //(1)获取 Class 对象
 Class<?> clazz = Class.forName("com.atguigu.ext.demo.AtGuiguD
emo");
 /*
 * 获取 AtGuiguDemo 类型中的有参构造
 * 如果构造器有多个,我们通常是根据形参【类型】列表来获取指定的一个
构造器的
 * 例如:public AtGuiguDemo(String title, int num)
 */
 //(2)获取构造器对象
 Constructor<?> constructor = clazz.getDeclaredConstructor(Str
ing.class,int.class);
 //(3)创建实例对象
 // T newInstance(Object... initargs) 这个 Object...是在创建对
象时,给有参构造的实参列表
 Object obj = constructor.newInstance("尚硅谷",2022);
 System.out.println(obj);
 }
}

应用 2:获取运行时类的完整结构

可以获取:包、修饰符、类型名、父类(包括泛型父类)、父接口(包括泛型父接口)、成员(属性、构造器、方法)、注解(类上的、方法上的、属性上的)。

相关 API

//1.实现的全部接口
public Class<?>[] getInterfaces() 
//确定此对象所表示的类或接口实现的接口。
//2.所继承的父类
public Class<? Super T> getSuperclass()
//返回表示此 Class 所表示的实体(类、接口、基本类型)的父类的 Class。
//3.全部的构造器
public Constructor<T>[] getConstructors()
//返回此 Class 对象所表示的类的所有 public 构造方法。
public Constructor<T>[] getDeclaredConstructors()
//返回此 Class 对象表示的类声明的所有构造方法。
//Constructor 类中:
//取得修饰符: 
public int getModifiers();
//取得方法名称: 
public String getName();
//取得参数的类型:
public Class<?>[] getParameterTypes();
//4.全部的方法
public Method[] getDeclaredMethods()
//返回此 Class 对象所表示的类或接口的全部方法
public Method[] getMethods() 
//返回此 Class 对象所表示的类或接口的 public 的方法
//Method 类中:
public Class<?> getReturnType()
//取得全部的返回值
public Class<?>[] getParameterTypes()
//取得全部的参数
public int getModifiers()
//取得修饰符
public Class<?>[] getExceptionTypes()
//取得异常信息
//5.全部的 Field
public Field[] getFields()
//返回此 Class 对象所表示的类或接口的 public 的 Field。
public Field[] getDeclaredFields()
//返回此 Class 对象所表示的类或接口的全部 Field。
//Field 方法中:
public int getModifiers()
//以整数形式返回此 Field 的修饰符
public Class<?> getType() 
//得到 Field 的属性类型
public String getName() 
//返回 Field 的名称。
//6. Annotation 相关
get Annotation(Class<T> annotationClass)
getDeclaredAnnotations()
//7.泛型相关
//获取父类泛型类型:
Type getGenericSuperclass()
//泛型类型:ParameterizedType
//获取实际的泛型类型参数数组:
getActualTypeArguments()
//8.类所在的包
Package getPackage()

应用 3:调用运行时类的指定结构

调用指定的属性
在反射机制中,可以直接通过 Field 类操作类中的属性,通过 Field 类提供的set()和 get()方法就可以完成设置和取得属性内容的操作。
(1)获取该类型的 Class 对象

Class clazz = Class.forName("包.类名");

(2)获取属性对象

Field field = clazz.getDeclaredField("属性名");

(3)如果属性的权限修饰符不是 public,那么需要设置属性可访问

field.setAccessible(true);

(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象

Object obj = clazz.newInstance(); //有公共的无参构造
Object obj = 构造器对象.newInstance(实参...);//通过特定构造器对象创建实例对象

(4)设置指定对象 obj 上此 Field 的属性内容

field.set(obj,"属性值");

如果操作静态变量,那么实例对象可以省略,用 null 表示
(5)取得指定对象 obj 上此 Field 的属性内容

Object value = field.get(obj)

体会反射的动态性

体会 1:

public class ReflectionTest {
 //体会反射的动态性:动态的创建给定字符串对应的类的对象
 public <T> T getInstance(String className) throws Exception {
 Class clazz = Class.forName(className);
 Constructor constructor = clazz.getDeclaredConstructor();
 constructor.setAccessible(true);
 return (T) constructor.newInstance();
 }
 @Test
 public void test1() throws Exception {
 String className = "com.atguigu.java1.Person";
 Person p1 = getInstance(className);
 System.out.println(p1);
 }
}

体会 2:

public class ReflectionTest {
 //体会反射的动态性:动态的创建指定字符串对应类的对象,并调用指定的方法
 public Object invoke(String className,String methodName) throws
Exception {
 Class clazz = Class.forName(className);
 Constructor constructor = clazz.getDeclaredConstructor();
 constructor.setAccessible(true);
 //动态的创建指定字符串对应类的对象
 Object obj = constructor.newInstance();
 Method method = clazz.getDeclaredMethod(methodName);
 method.setAccessible(true);
 return method.invoke(obj);
 }
 @Test
 public void test2() throws Exception {
 String info = (String) invoke("com.atguigu.java1.Person", "sh
ow");
 System.out.println("返回值为:" + info);
 }
}

体会 3:

public class ReflectionTest {
@Test
 public void test1() throws Exception {
 //1.加载配置文件,并获取指定的 fruitName 值
 Properties pros = new Properties();
 InputStream is = ClassLoader.getSystemClassLoader().getResour
ceAsStream("config.properties");
 pros.load(is);
 String fruitStr = pros.getProperty("fruitName");
 //2.创建指定全类名对应类的实例
 Class clazz = Class.forName(fruitStr);
 Constructor constructor = clazz.getDeclaredConstructor();
 constructor.setAccessible(true);
 Fruit fruit = (Fruit) constructor.newInstance();
 //3. 调用相关方法,进行测试
 Juicer juicer = new Juicer();
 juicer.run(fruit);
 }
}
interface Fruit {
public void squeeze();
}
class Apple implements Fruit {
public void squeeze() {
System.out.println("榨出一杯苹果汁儿");
}
}
class Orange implements Fruit {
public void squeeze() {
System.out.println("榨出一杯桔子汁儿");
}
}
class Juicer {
public void run(Fruit f) {
f.squeeze();
}
}

你可能感兴趣的:(Java,java,python,开发语言)