Java反射机制

简介: Java反射机制

我是南城余!阿里云开发者平台专家博士证书获得者!

欢迎关注我的博客!一同成长!

一名从事运维开发的worker,记录分享学习。

专注于AI,运维开发,windows Linux 系统领域的分享!


反射

Java给我们提供的一套API,使用这套API可以在运行时动态获取指定对象所属的类,创建运行时类的对象,调用指定的结构(属性、方法)等。

Reflection(反射)是被视为动态语言的关键,反射机制允许程序在运行期间借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。

面向对象,调用指定结构(属性、方法)等功能,使用反射与不使用的区别

不使用反射,我们需要考虑封装性。比如出了Person类之后,就不能调用Person类中私有的结构

使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有的属性、方法、构造器。

反射与创建对象调用方法的方式使用场景

》从作为开发者角度,我们开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以在开发中,我们使用非反射的方式多一些。

》因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在涉及框架时,会使用大量的反射。意味着,如果需要学习框架源码时,那么就需要学习反射。

框架 = 注解+反射+设计模式

封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用

反射: 体现的是我们能否调用的问题。因为类的完整结构都加载了内存中,所以我们就有能力进行调用

反射的优缺点

优点:

》提高了Java程序的灵活性和扩展性,降低了耦合性,提高了自适应能力

》允许程序创建个控制任何类的对象,无需提前硬编码目标类

缺点:

》反射的性能较低

反射机制主要应用在对灵活性和扩展性要求很高的系统框架上

》反射会模糊程序内部逻辑,可读性较差

反射,平时开发中,我们使用的并不多。主要是在框架的底层使用

class - 反射的源头

针对于编写好的。java源文件进行编译(使用javac.exe)会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行。在这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例。

比如:加载到内存中的Person类或String类,都作为Class的一个一个的实例

Class clazz1 = Person.class;

Class clazz1 =String.class;


class可以看作是反射的源头

获取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("类的全类名");
Class的实例指向结构

简言,所有的Java类型

》class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类

》interface:接口

》[]:数组

》enum:枚举

》annotation:注解@interface

》primitive type :基本数据类型

》void

类的加载过程(了解)

过程1:类的装载(loading)

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

过程2:链接(linking)

> 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。

> 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。

> 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

过程3:初始化(initialization)

执行类构造器<clinit>()方法的过程。

类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。

关于类的加载器(了解、JDK8版本为例)

作用:负责类的加载,并对应于一个Class的实例。

分类(分为两种):

> BootstrapClassLoader:引导类加载器、启动类加载器

> 使用C/C++语言编写的,不能通过Java代码获取其实例

> 负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)

> 继承于ClassLoader的类加载器

> ExtensionClassLoader:扩展类加载器

> 负责加载从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录 下加载类库

> SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器

> 我们自定义的类,默认使用的类的加载器。

> 用户自定义类的加载器

> 实现应用的隔离(同一个类在一个应用程序中可以加载多份);数据的加密。

以上的类的加载器是否存在继承关系? No!

使用类的加载器获取流,并读取配置文件信息
/*
* 需求:通过ClassLoader加载指定的配置文件
* */
@Test
public void test3() throws IOException {
    Properties pros = new Properties();
    //通过类的加载器读取的文件的默认的路径为:当前module下的src下
    InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");
    pros.load(is);
    String name = pros.getProperty("name");
    String pwd = pros.getProperty("password");
    System.out.println(name + ":" +pwd);
}

反射的应用

1. 创建运行时类的对象

如何实现

通过Class的实例调用newInstance()方法即可

且需要满足以下条件:

》要求运行时必须提供一个空参构造器

》要求提供的空参构造器的权限要足够

JavaBean中要求给当前类提供一个公共的的空参的构造器。

作用:

>场景1:子类对象在实例化时,子类的构造器的首行默认调用父类空参构造器

>场景2:在反射中,经常用来创建运行时类的对象。那么我们要求各个运行时类都提供一个空参构造器,便于我们编写创建运行时类对象的代码。

2. 获取运行时类的内部结构

》获取运行时类的内部结构:所有属性、所有方法、所有构造器

》获取运行时类的内部结构:父类、接口、包、带泛型的父类、父类的泛型等

3. 调用指定的结构:指定的属性、方法、构造器

调用指定的属性步骤

步骤1. 通过Class实例调用getDeclareField(String fieldName),获取运行时类指定名的属性

步骤2. setAccessible(true),确保此属性是可以访问的

步骤3. 通过Field类的实例调用get(Object obj)(获取操作)

或set(Object obj,Object value)(设置的操作)进行操作

调用指定的方法步骤

步骤1. 通过Class实例调用getDeclareField(String methodName,Class ... args),获取运行时类指定的方法

步骤2. setAccessible(true),确保此属性是可以访问的

步骤3. 通过Method实例invoke(Object obj,Object .. objs),即为对Method对应方法的调用

invoke()返回值即为Method对应方法的返回值

特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null

调用指定的构造器步骤

步骤1. 通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数的构造器

步骤2. setAccessible(true):确保此构造器是可访问的

步骤3. 通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例

4. 注解的使用

框架层面

目录
相关文章
|
6天前
|
Java 数据库连接 开发者
Java的Shutdown Hook机制:优雅地关闭应用程序
Java的Shutdown Hook机制:优雅地关闭应用程序
22 1
|
6天前
|
Java 程序员 开发者
深入理解Java并发编程:线程同步与锁机制
【4月更文挑战第30天】 在多线程的世界中,确保数据的一致性和线程间的有效通信是至关重要的。本文将深入探讨Java并发编程中的核心概念——线程同步与锁机制。我们将从基本的synchronized关键字开始,逐步过渡到更复杂的ReentrantLock类,并探讨它们如何帮助我们在多线程环境中保持数据完整性和避免常见的并发问题。文章还将通过示例代码,展示这些同步工具在实际开发中的应用,帮助读者构建对Java并发编程深层次的理解。
|
5天前
|
缓存 安全 Java
7张图带你轻松理解Java 线程安全,java缓存机制面试
7张图带你轻松理解Java 线程安全,java缓存机制面试
|
4天前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
6天前
|
消息中间件 安全 前端开发
字节面试:说说Java中的锁机制?
Java 中的锁(Locking)机制主要是为了解决多线程环境下,对共享资源并发访问时的同步和互斥控制,以确保共享资源的安全访问。 锁的作用主要体现在以下几个方面: 1. **互斥访问**:确保在任何时刻,只有一个线程能够访问特定的资源或执行特定的代码段。这防止了多个线程同时修改同一资源导致的数据不一致问题。 2. **内存可见性**:通过锁的获取和释放,可以确保在锁保护的代码块中对共享变量的修改对其他线程可见。这是因为 Java 内存模型(JMM)规定,对锁的释放会把修改过的共享变量从线程的工作内存刷新到主内存中,而获取锁时会从主内存中读取最新的共享变量值。 3. **保证原子性**:锁
20 1
|
6天前
|
安全 Java 数据安全/隐私保护
Java一分钟之-Java反射机制:动态操作类与对象
【5月更文挑战第12天】本文介绍了Java反射机制的基本用法,包括获取Class对象、创建对象、访问字段和调用方法。同时,讨论了常见的问题和易错点,如忽略访问权限检查、未捕获异常以及性能损耗,并提供了相应的避免策略。理解反射的工作原理和合理使用有助于提升代码灵活性,但需注意其带来的安全风险和性能影响。
24 4
|
6天前
|
Java 数据安全/隐私保护
java中异常处理机制
java中异常处理机制
15 1
|
6天前
|
算法 安全 Java
深入探索Java中的并发编程:CAS机制的原理与应用
总之,CAS机制是一种用于并发编程的原子操作,它通过比较内存中的值和预期值来实现多线程下的数据同步和互斥,从而提供了高效的并发控制。它在Java中被广泛应用于实现线程安全的数据结构和算法。
27 0
|
6天前
|
Java API 开发者
解密Java反射机制与动态代理
解密Java反射机制与动态代理
15 0
|
6天前
|
Java 数据库连接 开发者
Java中的异常处理机制详解
Java异常处理是确保程序健壮的关键,涉及Throwable的Error和Exception子类。Error由JVM抛出,不建议捕获;Exception分为检查异常(需要捕获)和未检查异常。处理异常的关键字有try、catch、finally、throw和throws。最佳实践包括捕获具体异常、不吞没异常、释放资源和避免滥用异常。示例展示了如何在main方法中处理IOException,并在finally块中进行资源清理。
13 1