Java基础篇 - 反射机制

简介: Java基础篇 - 反射机制

前言

我们在日常的开发中其实很少写反射,只知道有这么个东西,然而对于为什么用它却知之甚少。实际上,我们的框架中大量用到了这种概念。例如,当我们需要获取某个包下的类,并进行统一处理时,我们就需要知道这些类的名称,通过new 创建实例对象;但是如果我们不知道类有哪些,或者说我们只是在最底层封装的代码,对于用户定义的类名称根本不知道,这个时候,反射就起到作用了。下面让我们一起学习一下Java的动态获取的信息以及动态调用对象的方法的反射机制吧。

一、反射概述

反射: 反射是一种机制,利用该机制可以在程序运行过程中对类进行解剖并操作类中的所有成员(成员变量,成员方法,构造方法)。如果从上面的反射的定义上来看,反射是什么不太容易理解。通俗的讲,我们日常开发中使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

Apple apple = new Apple(); //直接初始化
apple.setPrice(4);

上面这样子进行类对象的初始化,我们可以理解为“正射”。而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用:

Class clz = Class.forName("com.chenshuyi.reflect.Apple");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。

所以说什么是反射?反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

1.1 反射过程

* 反射过程描述:
  首先会有一个java文件,然后会编译成.class文件,字节码文件(会里面包含了当前这个类里面所有数据信息)会加载到内存,
JVM负责管理,为了方便管理,它会将每一个加载进来的class文件生成一个对应的Class对象<对应的类型就是生成字节码之前java文件
对应的类名类型>,这个class对象它就代表对应的类(java文件),那么我们可以通过这个类对应的class对象获得这个类中的数据信息
(成员变量、成员方法、构造方法)

网络异常,图片无法展示
|

结论:我们使用反射,其实就是获得某个类的Class对象,然后通过这个Class对象对这个类中的成员数据进行处理(调用执行)!

1.2 反射前提

我们必须得到这类的字节码文件(.class文件) ====>>> Class对象

Class:它是一个类,Class类的实例表示正在运行的Java应用程序中的类和接口。我们的一个类最终在内存中都是字节码,然后JVM会对每一个类对应的字节码生成一个Class对象!然后通过这个Class对象就可以访问类中的所有信息!

* Java类与Class对象的对应关系:
    一个类           =======>>> 一个Class对象
    类中的一个成员变量 ======>>>> 一个Field对象(在程序运行阶段为类中的成员变量赋值或者获取值)  
    类中的一个成员方法 ======>>>> 一个Method对象(在程序运行阶段执行类中的成员方法)
    类中的一个构造方法 ======>>>> 一个Constructor对象(在程序运行阶段创建当前类的一个实例对象)

二、 如何获得类对应的Class对象

2.1 方式一:通过Class类的静态方法

// 参数:某个类的全限定类名(包含包名和类名)
static Class<?> forName(String className) // 返回与给定字符串名称的类或接口相关联的Class对象。
Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student"); // 给定类的全限定类名书写错误,会导致异常!ClassNotFoundException

2.2 方式二:通过类的对象的getClass方法

// 先获得到某个类的对象
Student student = new Student();
// 通过对象调用方法获得Class对象!
Class<? extends Student> clazz1 = student.getClass(); // getClass()方法是从Object继承过来的

2.3 方式三:通过类名的静态属性class

// 通过类名.class获得Class对象!
Class<Student> clazz = Student.class;

2.4 Class对象的常用功能

* String getSimpleName(); // 获得类名字符串:类名
* String getName();  // 获得类全名:包名+类名
* T newInstance() ;  // 创建Class对象关联类的对象(公有构造方法)
public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student");
    System.out.println(clazz.getSimpleName()); // Student
    System.out.println(clazz.getName()); // com.itheima.demo01_反射.Student
    Object obj = clazz.newInstance(); // com.itheima.demo01_反射.Student@1647C
}

三、通过Class对象操作构造方法

3.1 获得Constructor对象

Constructor<?>[] getConstructors() // 通过Class对象获得类中所有的公有的构造方法对象  
Constructor<?>[] getDeclaredConstructors() // 通过Class对象获得类中所有的构造方法对象(公有私有,全都要)  
Constructor<T> getConstructor(Class<?>... parameterTypes) // 通过Class对象获得类中指定公有的构造方法对象!  【★★★★★】
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) // 通过Class对象获得类中指定的构造方法!(公有私有,全都要)  
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
    // 获得Student类对应的Class对象
    Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student");
    /////////////////////通过Class对象获得Student类中构造方法对应的Constructor对象!///////////////////
    // 获得类中所有的公共的构造方法对应的对象数组
    Constructor<?>[] constructors = clazz.getConstructors();
    System.out.println(Arrays.toString(constructors)); //
    // 获得类中所有的构造方法对应的对象数组(包括私有的!)
    Constructor<?>[] dc = clazz.getDeclaredConstructors();
    System.out.println(Arrays.toString(dc));
    // 获得类中指定构造方法对应的对象
    //Constructor<?> c1 = clazz.getConstructor();
    Constructor<?> c1 = clazz.getDeclaredConstructor();
    System.out.println(c1);
    Constructor<?> c2 = clazz.getDeclaredConstructor(String.class, int.class);
    System.out.println(c2);
}

3.2 通过构造方法对象创建类的实例对象

T newInstance(Object... initargs) 使用由此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。  
public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student");
    // 获得Student类中指定的构造方法对象!
    Constructor<?> constructor = clazz.getConstructor();
    // 根据构造方法对象创建Student类的实例对象!
    Object o = constructor.newInstance();
    System.out.println(o); // com.itheima.demo01_反射.Student@14ae5a5
    Student s = (Student) o;
}

注意:一旦类中的构造方法使用private修饰,那么通过构造方法对象创建了的实例对象就要发生变化!

public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student");
    // 获得Student类中指定的构造方法对象!
    Constructor<?> constructor = clazz.getDeclaredConstructor(); // 有变动!
    // 暴力访问!
    constructor.setAccessible(true); // 有变动!
    // 根据构造方法对象创建Student类的实例对象!
    Object o = constructor.newInstance();
    System.out.println(o); // com.itheima.demo01_反射.Student@14ae5a5
    Student s = (Student) o;
}

四、 通过Class对象操作成员方法对象

4.1 获得成员方法的Method对象

Method getMethod(String name, Class<?>... parameterTypes) // 返回指定公有的方法对象! 【★★★★★】
Method[] getMethods() // 返回所有公有的方法对象构成的数组,包括由类或接口声明的对象以及从超类和超级接口继承的类。
Method getDeclaredMethod(String name, Class<?>... parameterTypes)// 返回指定的方法对象!(私有的也可以玩)
Method[] getDeclaredMethods() // 返回所有的方法对象构成的数组,包括public,protected,default(package)访问和私有方法,但不包括继承方法。
public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student");
    // 获得Student类以及父类(接口)中所有的公有成员方法
    Method[] methods1 = clazz.getMethods();
    for (Method method : methods1) {
        System.out.println(method);
    }
    System.out.println("====================================");
    // 获得Student类中所有的成员方法(包含私有)
    Method[] methods2 = clazz.getDeclaredMethods();
    for (Method method : methods2) {
        System.out.println(method);
    }
    System.out.println("====================================");
    // 获得指定方法(公有无参)
    Method show1 = clazz.getMethod("show1");
    // 获得指定方法(私有无参)
    Method show2 = clazz.getDeclaredMethod("show2");
    // 获得指定方法(公有带参数)
    Method method1 = clazz.getMethod("method1", String.class);
    // 创建类的对象
    Object obj = clazz.getConstructor().newInstance();
    //  获得成员方法对象,期目的是为了执行这个成员方法
    Object result = show1.invoke(obj);// 参数1:当前方法所属对象!   参数2:show1方法的参数值(若定义show1方法没有参数,这里就不写内容)
    System.out.println(result); // null  [show1方法定义的时候没有返回值]
    System.out.println("======================");
    // 执行带有参数的公有成员方法
    Object o = method1.invoke(obj, "666");
    System.out.println(o);
}

4.2 通过Method对象执行指定方法

/*
    参数obj:当前Method对象所属的类的对象!【实际的对象值!】
    参数args:为方法对象的形参赋值(若方法对象对应的方法定义没有参数,这里可以空着不写<这里是一个可变参数>) 
    返回值Object:执行完当前方法的返回结果!
*/
Object invoke(Object obj, Object... args)  // 在具有指定参数的指定对象上调用此 方法对象表示的基础方法。  【★★★★★】
public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Class.forName("com.itheima.demo01_反射.Student");
    // 创建类的对象
    Object obj = clazz.getConstructor().newInstance();
    // 获得指定方法(公有无参)
    Method show1 = clazz.getMethod("show1");
    // 获得指定方法(公有带参数)
    Method method1 = clazz.getMethod("method1", String.class);
    //  获得成员方法对象,期目的是为了执行这个成员方法
    Object result = show1.invoke(obj);// 参数1:当前方法所属对象!   参数2:show1方法的参数值(若定义show1方法没有参数,这里就不写内容)
    System.out.println(result); // null  [show1方法定义的时候没有返回值]
    System.out.println("======================");
    // 执行带有参数的公有成员方法
    Object o = method1.invoke(obj, "666");
    System.out.println(o);
}

五、 通过Class对象操作成员变量

5.1 获得成员变量的Field对象

public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Student.class;
    // 获得类的实例
    //Object obj = clazz.newInstance();
    Student s = (Student) clazz.newInstance();
    // 获得公有成员变量对应的Field对象(很多)
    Field[] fields = clazz.getFields();
    System.out.println(Arrays.toString(fields));
    // 获得指定公有成员变量
    Field name = clazz.getField("name");
    System.out.println(name);
    // 获得所有的成员变量对应的Field对象(公有私有均可)
    Field[] declaredFields = clazz.getDeclaredFields();
    System.out.println(Arrays.toString(declaredFields));
    // 获得指定成员变量(公有私有均可)
    Field age = clazz.getDeclaredField("age");
    System.out.println(age);
}

5.2 通过Field对象获取成员变量的值或者设置值

public static void main(String[] args) throws Exception{
    // 获得Student类对应的Class对象
    Class<?> clazz = Student.class;
    // 获得类的实例
    //Object obj = clazz.newInstance();
    Student s = (Student) clazz.newInstance();
    /////////////////////////////////操作公有的成员变量////////////////////////
    // 获得指定公有成员变量
    Field name = clazz.getField("name");
    // 为公有的成员变量设置值
    System.out.println(s.getName()); // null
    name.set(s,"jack");
    // 获得指定公有成员变量的值
    System.out.println(name.get(s)); // jack
    System.out.println(s.getName()); // jack
    /////////////////////////////////操作私有的成员变量////////////////////////
    // 获得指定成员变量(公有私有均可)
    Field age = clazz.getDeclaredField("age");
    // 暴力访问
    age.setAccessible(true);
    // 获取值
    System.out.println(age.get(s)); // 0
    // 设置值
    age.set(s,38);
    System.out.println(age.get(s)); // 38
}

6 后记

开发过程中 我们不会通过反射的机制来获取对象,主要是因为反射的代码阅读性不好,不易理解。其次,不易于代码维护。我们会在系统架构时将其用于底层代码封装或者自定义工具类,除此之外,反射在开发中基本很少用到!


相关文章
|
5月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
247 32
AQS:Java 中悲观锁的底层实现机制
|
3月前
|
人工智能 缓存 安全
Java中的反射机制:深入探索与应用
Java反射机制是程序运行时动态获取类信息并操作类成员的特性,具备高度灵活性,但也伴随性能与安全风险。本文详解反射的基本用法、高级应用及最佳实践,助你掌握这一强大工具的正确使用方式。
136 0
|
5月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
159 0
|
3月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
186 4
|
3月前
|
人工智能 安全 Java
掌握Java反射:在项目中高效应用反射机制
Java反射是一种强大功能,允许程序在运行时动态获取类信息、创建对象、调用方法和访问字段,提升程序灵活性。它在框架开发、动态代理、注解处理等场景中广泛应用,如Spring和Hibernate。但反射也存在性能开销、安全风险和代码复杂性,应谨慎使用。
|
4月前
|
人工智能 Java
Java中的反射机制:深入探索与应用
本文介绍了Java反射机制的基本概念、用途及其实现方式。反射机制允许程序在运行时动态获取类的属性和方法,并调用它们,适用于处理私有成员或权限受限的情况。文章详细讲解了`Class`类的功能,包括获取类的方法、属性、注解、构造器等信息,以及通过四种方式获取`Class`对象的示例代码。此外,还探讨了类加载器、继承关系判断、动态代理等高级内容,展示了如何在运行时创建接口实例并处理方法调用。文末提供了完整的代码示例以加深理解。
Java中的反射机制:深入探索与应用
|
5月前
|
Java 区块链 网络架构
酷阿鲸森林农场:Java 区块链系统中的 P2P 区块同步与节点自动加入机制
本文介绍了基于 Java 的去中心化区块链电商系统设计与实现,重点探讨了 P2P 网络在酷阿鲸森林农场项目中的应用。通过节点自动发现、区块广播同步及链校验功能,系统实现了无需中心服务器的点对点网络架构。文章详细解析了核心代码逻辑,包括 P2P 服务端监听、客户端广播新区块及节点列表自动获取等环节,并提出了消息签名验证、WebSocket 替代 Socket 等优化方向。该系统不仅适用于农业电商,还可扩展至教育、物流等领域,构建可信数据链条。
|
5月前
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
104 0
|
7月前
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
277 7
理解的Java中SPI机制
|
6月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
470 1