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 后记

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


相关文章
|
7天前
|
安全 Java 数据库连接
Java中的异常处理机制及其最佳实践
【8月更文挑战第12天】在Java的世界里,异常处理是维护程序健壮性的关键环节之一。它不仅涉及到错误检测,还关乎如何优雅地恢复或报告错误。本文将深入探讨Java的异常处理机制,包括异常类型、捕获异常的策略以及异常的最佳处理实践。我们将一起学习如何通过有效的异常管理来提升代码质量和用户体验。
|
6天前
|
Java 数据库连接 开发者
Java中的异常处理机制深度解析
【8月更文挑战第13天】本文旨在深入探讨Java编程语言中一个至关重要的组成部分——异常处理机制。我们将从基本概念入手,逐步展开讨论异常处理在Java语言设计中的角色和重要性,以及如何正确利用这一机制来提高代码的健壮性和可维护性。文章将通过分析异常处理的最佳实践,揭示如何在复杂的应用程序中有效地管理和处理错误情况。
|
15天前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
41 1
|
15天前
|
存储 Oracle 安全
揭秘Java并发核心:深入Hotspot源码腹地,彻底剖析Synchronized关键字的锁机制与实现奥秘!
【8月更文挑战第4天】在Java并发世界里,`Synchronized`如同导航明灯,确保多线程环境下的代码安全执行。它通过修饰方法或代码块实现独占访问。在Hotspot JVM中,`Synchronized`依靠对象监视器(Object Monitor)机制实现,利用对象头的Mark Word管理锁状态。
27 1
|
3天前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
5天前
|
Java 程序员 编译器
深入浅出Java异常处理机制
在Java编程的世界中,异常处理就像是我们生活中的急救包,它帮助我们处理程序运行时出现的意外情况。本文将带你了解Java异常处理的基础知识,探索异常类型,学习如何捕获和处理它们,并讨论最佳实践。让我们一起走进Java异常处理的世界,学会如何使用这个强大的工具来保护我们的程序。
|
6天前
|
安全 Java 程序员
Java中的异常处理机制:从基础到高级
【8月更文挑战第13天】在Java编程中,异常处理是一个不可或缺的部分。本文将深入探讨Java的异常处理机制,从基本概念出发,逐步解析try-catch-finally语句块的使用,探讨自定义异常类的设计,以及介绍高级异常处理技术如try-with-resources和异常链。通过这些内容的学习,读者将能够更加有效地管理和控制程序运行时的错误。
|
9天前
|
消息中间件 负载均衡 Java
"深入Kafka核心:探索高效灵活的Consumer机制,以Java示例展示数据流的优雅消费之道"
【8月更文挑战第10天】在大数据领域,Apache Kafka凭借其出色的性能成为消息传递与流处理的首选工具。Kafka Consumer作为关键组件,负责优雅地从集群中提取并处理数据。它支持消息的负载均衡与容错,通过Consumer Group实现消息的水平扩展。下面通过一个Java示例展示如何启动Consumer并消费数据,同时体现了Kafka Consumer设计的灵活性与高效性,使其成为复杂消费场景的理想选择。
37 4
|
9天前
|
消息中间件 负载均衡 Java
"Kafka核心机制揭秘:深入探索Producer的高效数据发布策略与Java实战应用"
【8月更文挑战第10天】Apache Kafka作为顶级分布式流处理平台,其Producer组件是数据高效发布的引擎。Producer遵循高吞吐、低延迟等设计原则,采用分批发送、异步处理及数据压缩等技术提升性能。它支持按消息键值分区,确保数据有序并实现负载均衡;提供多种确认机制保证可靠性;具备失败重试功能确保消息最终送达。Java示例展示了基本配置与消息发送流程,体现了Producer的强大与灵活性。
27 3
|
11天前
|
Java 开发者
深入解析Java中的异常处理机制
在Java的世界中,异常处理是维护程序健壮性的基石之一。本文将通过实例演示和理论分析相结合的方式,探讨Java异常处理机制的工作原理及其最佳实践。我们将从异常的基本概念出发,逐步深入到异常类的层次结构、捕获异常的策略以及自定义异常的使用场景,旨在为读者提供一个全面而深入的视角来理解和应用Java中的异常处理。
11 2