从初学者到专家:Java反射的完整指南

news2024/11/16 20:44:47

一.反射的概念及定义

Java 的反射( reflection )机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信 息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
反射的用途
  1. 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统 应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
  2. 反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无 论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Beanspring就动态的创建这些类。

反射不同时期的类型

Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型。

例如:Person p = new Student();

  • 编译时类型是在编译时期确定的,它是变量声明时所使用的类型。在上面的示例代码中,变量p的编译时类型是Person,因为它是通过Person p的声明来定义的。
  • 运行时类型是在程序运行时确定的,它是实际分配给对象的类型。在上面的示例代码中,使用new Student()创建了一个Student对象,并将其赋值给变量p,因此在运行时,p的类型是Student

通过反射,可以获取对象的运行时类型。例如,可以使用p.getClass()方法获取p对象的实际类型(运行时类型),并进行相应的操作。

除了获取对象的运行时类型,反射还可以获取类的详细信息,包括类的名称、父类、接口、构造函数、字段和方法等。通过获取类的信息,可以做一些动态的操作,如动态创建对象、调用方法、访问字段等。

注意:Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类 .

二.反射相关的示例

在Java中,反射通过java.lang.reflect包中的类和接口来实现。

反射的基本信息:

  1. ClassClass是反射的核心类之一,它表示一个类或接口的运行时对象。通过Class类可以获取和操作类的信息,如类的名称、父类、接口、构造函数、字段和方法等。

  2. ConstructorConstructor类表示类的构造函数。通过Constructor类可以创建类的实例,调用构造函数并实例化对象。

  3. Field类:Field类表示类的字段(成员变量)。通过Field类可以获取和修改字段的值,以及访问字段的属性信息。

  4. Method类:Method类表示类的方法。通过Method类可以调用类的方法,传递参数并获取返回值。

  5. 获取Class对象:可以使用多种方式获取Class对象,如使用类名调用Class.forName()方法、通过类的实例调用getClass()方法、或者直接通过类字面常量使用SomeClass.class

  6. 实例化对象:通过Class对象和Constructor类可以实例化类的对象。可以使用newInstance()方法创建无参构造函数的实例,或者使用Constructor类的newInstance()方法传递参数创建有参构造函数的实例。

  7. 调用方法:通过Class对象和Method类可以调用类的方法。可以使用invoke()方法传递对象和参数来调用方法,并获取返回值。

  8. 访问字段:通过Class对象和Field类可以访问类的字段。可以使用get()set()方法获取和修改字段的值,以及使用getField()getDeclaredField()方法获取字段对象。


 

 2.1获取Class对象的三种方式

在使用反射时,我们需要先获取要反射的类的  Class 对象,因为  Class 对象提供了许多有用的方法和操作,用于在运行时检查和操作类的结构、属性和方法。
你可以这样理解:当我们使用反射时,需要先创建类的  Class 对象,这个对象相当于是类的身份证。通过这个身份证,我们可以了解这个类的各种信息。

通过 Class 对象,我们可以做以下事情:

  • 创建对象实例:通过 Class 对象的 newInstance() 方法可以动态地创建类的对象实例。

  • 获取类的信息:通过 Class 对象可以获取类的名称、修饰符、包名、父类、接口、字段和方法等信息。

  • 获取和设置字段值:通过 Class 对象和字段名称,可以获取和设置类的字段的值。

  • 调用方法:通过 Class 对象和方法名称,可以调用类的方法。

  • 动态加载类:通过 Class.forName() 方法可以在运行时动态加载类。

  • 进行注解处理:通过 Class 对象可以获取类上的注解信息,并进行相应的处理。

通过先创建类的  Class 对象,我们可以在运行时对类进行检查和操作,而无需提前知道类的具体信息。这使得代码更加灵活,可以在运行时根据需要动态地处理和操作类。
举例子解释:
1.先创建出Student类:
package demo1;

/**
 * @Author 12629
 * @Description:
 */
class Student {
    //私有属性name
    private String name = "Classmates";
    //公有属性age
    public int age = 18;
    //不带参数的构造方法
    public Student(){
        System.out.println("Student()");
    }

    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }

    private void eat(){
        System.out.println("i am eat");
    }

    public void sleep(){
        System.out.println("i am pig");
    }

    private void function(String str) {
        System.out.println(str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

第一种,使用 Class.forName("类的全路径名"); 静态方法。(前提:已明确类的全路径名。)

public class Test {

    public static void main(String[] args) {
        //获取Class对象有三种方式之一
        //第一种
        Class<Student> c1 = null;
        try {
            c1 = Class.forName("demo1.Student");
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        
    }
}

这段代码的作用是将字符串 "demo1.Student" 作为参数传递给 Class.forName() 方法,并将返回的 Class 对象赋值给变量 c1

为什么有调异常呢?

这是因为 Class.forName() 方法尝试根据提供的类名加载对应的类,但如果在类路径中找不到该类,就会抛出 ClassNotFoundException 异常。

注意:使用 Class.forName() 方法时,需要提供类的全限定名,即包括包名和类名。


 

第二种:使用 .class 方法。(仅适合在编译前就已经明确要操作的 Class)

public class Test {

    public static void main(String[] args) {
        //第二种
        Class<Student> c2 = Student.class;

    }
}

段代码的作用是通过类字面常量 Student.class 来获取 Student 类的 Class 对象,并将其赋值给变量 c2

使用类字面常量的方式非常简单,只需要在类名后面加上 .class 就可以直接访问该类的 Class 对象。


第三种 ,使用类对象的 getClass() 方法

public class Test {

        //第三种
        Student student = new Student();
Class<? extends Student> c3 = student.getClass();

    }

我们创建了一个 Student 类的对象 student,然后通过 student.getClass() 方法获取该对象的运行时类的 Class 对象,并将其赋值给类型为 Class<? extends Student> 的变量 c3

使用对象的 getClass() 方法可以在运行时获取对象所属类的 Class 对象。这种方式适用于当我们有一个对象实例,想要获取其对应的类信息时。

 


注意:一个类在 JVM 中只会有一个 Class 实例

public class Test {

    /*
    Class对象 只有一个
     */
    public static void main(String[] args) {
        //获取Class对象有三种方式
        //生成的对象只有一个
        //第一种
        Class<Student> c1 = null;
        try {
            c1 = Class.forName("demo1.Student");
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //第二种
        Class<Student> c2 = Student.class;

        //第三种
        Student student = new Student();
Class<? extends Student> c3 = student.getClass();

        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
    }
}

运行例图如下: 


2.2 Class对象的使用 

上一步我们已经创建好 Class 对象了,可以使用它来进行各种操作。

注意:所有和反射相关的包都在 import java.lang.reflect 包下面。

 

使用前我们先了解Class类中的相关的方法有哪些
1.常用获得类相关的方法:

 2.常用获得类中属性相关的方法(以下方法返回值为Field相关):

3. 获得类中构造器相关的方法(以下方法返回值为Constructor相关

4.

4.获得类中方法相关的方法(以下方法返回值为Method相关 

5.获得类中注解相关的方法 (了解即可)

代码案例如下:

package demo1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectClassDemo {

/*reflectNewInstance() 方法使用反射创建一个类的实例,然后输出该实例。

它通过 Class.forName() 方法获取类的 Class 对象,然后使用 newInstance() 方法创建实例。
*/

    public static void reflectNewInstance() {
        Class<?> classStudent = null;
        try {
            // 获取类的Class对象
            classStudent = Class.forName("demo1.Student");
            // 使用newInstance()方法创建类的实例
            Student student = (Student) classStudent.newInstance();
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
/*
reflectPrivateConstructor() 方法演示了如何使用反射调用私有的构造方法。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredConstructor() 方法获取私有构造方法,并通过 setAccessible(true) 设置访问权限。
最后,使用 newInstance() 方法创建实例并输出。
*/

    public static void reflectPrivateConstructor() {
        Class<?> classStudent = null;
        try {
            classStudent = Class.forName("demo1.Student");
            // 获取私有构造方法
            Constructor<?> constructor = classStudent.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true); // 设置私有构造方法可访问
            // 调用私有构造方法创建实例
            Student student = (Student) constructor.newInstance("xiaoming", 15);
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
/*
reflectPrivateField() 方法展示了如何使用反射操作私有字段。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredField() 方法获取私有字段,并通过 setAccessible(true) 设置访问权限。
接下来,使用 set() 方法给字段设置新的值,并输出结果。
*/
    public static void reflectPrivateField() {
        Class<?> classStudent = null;
        try {
            classStudent = Class.forName("demo1.Student");
            // 获取私有字段
            Field field = classStudent.getDeclaredField("name");
            field.setAccessible(true); // 设置私有字段可访问(这个特别要注意,不然会报异常)
            // 创建类的实例
            Student student = (Student) classStudent.newInstance();
            // 设置私有字段的值
            field.set(student, "caocao");
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
/*
reflectPrivateMethod() 方法演示了如何使用反射调用私有方法。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredMethod() 方法获取私有方法,并通过 setAccessible(true) 设置访问权限。
最后,使用 invoke() 方法调用方法并输出结果。
*/
    public static void reflectPrivateMethod() {
        Class<?> classStudent = null;
        try {
            classStudent = Class.forName("demo1.Student");
            // 获取私有方法
            Method method = classStudent.getDeclaredMethod("function", String.class);
            method.setAccessible(true); // 设置私有方法可访问
            // 创建类的实例
            Student student = (Student) classStudent.newInstance();
            // 调用私有方法
            method.invoke(student, "我是一个反射的参数!");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
    // 反射实例化类
     reflectNewInstance();

    // 反射调用私有构造方法
     reflectPrivateConstructor();

    // 反射操作私有字段
     reflectPrivateField();

    //反射调用私有方法
    reflectPrivateMethod();

   }
}

 运行例图如下:

三.总结

反射的优缺点如下:

优点:

  1. 动态性和灵活性:反射允许在运行时动态地获取和操作类的信息,使程序能够根据需要适应不同的情况和需求。它提供了灵活的实例化、字段访问和方法调用,以及动态代理和处理注解等功能,增强了程序的灵活性和可扩展性。

  2. 泛型操作:反射使得可以在运行时获取泛型类型的信息,并进行相应的操作。这对于编写通用代码和框架非常有用,可以在不知道具体类型的情况下进行更多的操作和处理。

  3. 框架和库的开发:反射广泛应用于框架和库的开发中。通过反射,可以在运行时动态地加载和使用类,根据配置文件或用户输入进行相应的操作,使框架和库具有更强的扩展性和适应性。

缺点:

  1. 性能影响:反射的操作通常比直接调用方法或访问字段的性能要低。使用反射会引入额外的开销,包括方法调用和类型检查等。因此,频繁使用反射可能导致程序的性能下降。

  2. 安全性问题:反射可以绕过访问权限的限制,可以访问和修改私有成员,并执行敏感操作。这可能导致安全性问题,特别是在处理不受信任的代码或用户输入时需要格外小心。

  3. 编码复杂性和可读性降低:反射的使用可能会增加代码的复杂性和可读性降低。由于反射是在运行时动态进行的,因此一些问题只能在运行时才能被发现,而不是在编译时。这可能导致调试和维护过程中的困难。

  4. 局限性:反射有一些局限性,例如无法操作编译时不存在的类、字段或方法;无法操作原始类型的字段等。此外,由于反射是基于运行时信息的,因此在某些情况下可能无法获得期望的结果。

反射是Java语言中的一项强大特性,它允许程序在运行时动态地获取、操作和修改类、对象、字段和方法的信息。通过反射,我们可以实现灵活的类实例化、字段访问和方法调用,以及处理注解和实现动态代理等功能。然而,反射的使用应谨慎,需要平衡灵活性、性能和安全性,并注意其局限性和注意事项。总而言之,反射为Java开发者提供了强大的工具,使得程序可以在运行时动态地适应不同的需求和场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1526624.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Jenkins + Docker + ASP.NET Core自动化部署

本来没想着要写这篇博客&#xff0c;但是在实操过程中&#xff0c;一个是被网络问题搞炸了心态&#xff08;真心感觉网络能把人搞疯&#xff0c;别人下个包、下个镜像几秒钟搞定&#xff0c;我看着我的几KB小水管真是有苦说不出&#xff09;&#xff0c;另一个就是这里面坑还是…

C语言---指针的两个运算符:点和箭头

目录 点&#xff08;.&#xff09;运算符箭头&#xff08;->&#xff09;运算符需要注意实际例子 C语言中的指针是一种特殊的变量&#xff0c;它存储了一个内存地址。点&#xff08;.&#xff09;和箭头&#xff08;->&#xff09;是用于访问结构体和联合体成员的运算符。…

手撕算法-二叉树的镜像

题目描述 操作给定的二叉树&#xff0c;将其变换为源二叉树的镜像。数据范围&#xff1a;二叉树的节点数 0≤_n_≤1000 &#xff0c; 二叉树每个节点的值 0≤_val_≤1000要求&#xff1a; 空间复杂度 O(n) 。本题也有原地操作&#xff0c;即空间复杂度 O(1) 的解法&#xff0c…

【设计模式】-工厂模式

工厂模式是一种创建型设计模式&#xff0c;它提供了一种在不指定具体类的情况下创建对象的方法。工厂模式的核心思想是将对象的创建与使用分离&#xff0c;降低系统的耦合度&#xff0c;使系统更加灵活、可扩展。 工厂模式主要分为三种类型&#xff1a;简单工厂模式、工厂方法…

苍穹外卖-day06:HttpClient、微信小程序开发、微信登录(业务流程)、导入商品浏览功能代码(业务逻辑)

苍穹外卖-day06 课程内容 HttpClient微信小程序开发微信登录导入商品浏览功能代码 功能实现&#xff1a;微信登录、商品浏览 微信登录效果图&#xff1a; 商品浏览效果图&#xff1a; 1. HttpClient 1.1 介绍 HttpClient 是Apache Jakarta Common 下的子项目&#xff0c;…

苍穹外卖-day04:项目实战-套餐管理(新增套餐,分页查询套餐,删除套餐,修改套餐,起售停售套餐)业务类似于菜品模块

苍穹外卖-day04 课程内容 新增套餐套餐分页查询删除套餐修改套餐起售停售套餐 要求&#xff1a; 根据产品原型进行需求分析&#xff0c;分析出业务规则设计接口梳理表之间的关系&#xff08;分类表、菜品表、套餐表、口味表、套餐菜品关系表&#xff09;根据接口设计进行代…

C#重新认识笔记_ 点乘,叉乘

C#重新认识笔记_ 点积&#xff0c;叉乘 一、Dot Product 点乘&#xff1a; &#xff08;Ax*Bx&#xff09;&#xff08;Ay*By&#xff09;&#xff08;Az*Bz&#xff09;Dot Product 点积 利用点积&#xff0c;可以了解&#xff0c;两个向量(vector)的相关信息&#xff0c; …

el-form 的表单校验,如何验证某一项或者多项;validateField 的使用

通常对form表单的校验都是整体校验&#xff1a; this.$refs.form.validate( valid > {if (valid) {// 校验通过&#xff0c;业务逻辑代码...} }); 如果需要对表单里的特定一项或几项进行校验&#xff0c;应该如何实现&#xff1f; 业务场景&#xff1a;下图点探测按钮时…

CSS 零基础入门教程

目录 1. div 和 span2. 什么是CSS&#xff1f;3. CSS 引入方式3.1 内部样式表3.2 外部样式表3.3 行内样式 4. 选择器4.1 标签选择器4.2 类选择器4.3 id 选择器4.4 通配符选择器 5. CSS 基础属性6. 谷歌浏览器调试工具 正文开始。 1. div 和 span 在学习 CSS 之前&#xff0c;…

Redis 八种常用数据类型详解

夯实基础&#xff0c;这篇文章带着大家回顾一下 Redis 中的 8 种常用数据类型&#xff1a; 5 种基础数据类型&#xff1a;String&#xff08;字符串&#xff09;、List&#xff08;列表&#xff09;、Set&#xff08;集合&#xff09;、Hash&#xff08;散列&#xff09;、Zse…

redis学习-List类型相关命令以及特殊情况分析

目录 1. lpush key value1 value2 ... 2. lrange key start end 3. lpop key num 4. rpush key value1 value2 ... 5. rpop key num 6. lindex key index 7. llen key 8. lrem key num value 9. rpoplpush key1 key2 10. lset key index value 11. linsert key before/after…

算法---二分查找练习-1(在排序数组中查找元素的第一个和最后一个位置)

在排序数组中查找元素的第一个和最后一个位置 1. 题目解析2. 讲解算法原理3. 编写代码 1. 题目解析 题目地址&#xff1a;点这里 2. 讲解算法原理 算法原理如下&#xff1a; 首先&#xff0c;判断数组是否为空&#xff0c;如果为空&#xff0c;则直接返回{-1, -1}表示没有找到目…

深入解析JVM加载机制

一、背景 Java代码被编译器变成生成Class字节码&#xff0c;但字节码仅是一个特殊的二进制文件&#xff0c;无法直接使用。因此&#xff0c;都需要放到JVM系统中执行&#xff0c;将Class字节码文件放入到JVM的过程&#xff0c;简称类加载。 二、整体流程 三、阶段逻辑分析 3…

PostgreSQL中vacuum 物理文件truncate发生的条件

与我联系&#xff1a; 微信公众号&#xff1a;数据库杂记 个人微信: iiihero 我是iihero. 也可以叫我Sean. iiheroCSDN(https://blog.csdn.net/iihero) Sean墨天轮 (https://www.modb.pro/u/16258) 数据库领域的资深爱好者一枚。 水木早期数据库论坛发起人 db2smth就是俺&am…

世界第一个AI软件工程师问世!

2024年3月13日&#xff0c;科技公司Cognition推出了世界上第一位人工智能软件工程师Devin AI。这项创新有望利用人工智能编码和机器学习的力量加快发展。Devin AI不仅仅是帮助&#xff1b;它是一个成熟的队友&#xff0c;发挥智能编码自动化和自主人工智能编码的魔力&#xff0…

Spring Bean的生命周期流程

前言 Java 中的公共类称之为Java Bean&#xff0c;而 Spring 中的 Bean 指的是将对象的生命周期&#xff0c;交给Spring IoC 容器来管理的对象。所以 Spring 中的 Bean 对象在使用时&#xff0c;无需通过 new 来创建对象&#xff0c;只需要通过 DI&#xff08;依赖注入&#x…

数字化转型导师坚鹏:人工智能在金融机构数字化转型中的应用

人工智能在金融机构数字化转型中的应用 课程背景&#xff1a; 金融机构数字化转型离不开人工智能&#xff0c;在金融机构数字化转型中&#xff0c;人工智能起到至关重要的作用&#xff0c;很多机构存在以下问题&#xff1a; 不清楚人工智能产业对我们有什么影响&#xff1f;…

【数据可信流通,从运维信任到技术信任】

1. 数据可信流通体系 信任的基石&#xff1a; 身份的可确认利益可依赖能力有预期行为有后果 2.内循环——>外循环 内循环&#xff1a;数据持有方在自己的运维安全域内队自己的数据使用和安全拥有全责。 外循环&#xff1a;数据要素在离开持有方安全域后&#xff0c;持有方…

函数-Python

师从黑马程序员 函数初体验 str1"asdf" str2"qewrew" str3"rtyuio" def my_len(data):count0for i in data:count1print(f"字符串{data}的长度是{count}")my_len(str1) my_len(str2) my_len(str3) 函数的定义 函数的调用 函数名&a…

12_Linux内核结构

Linux内核结构 1.内核的主要组成部分 Linux 内核主要的 5 个部分&#xff1a;进程调度、内存管理、虚拟文件系统、网络接口、进程通信。在系统移植的时候&#xff0c;它们是内核的基本元素&#xff0c;这 5 个部分之间的关系&#xff0c;如图所示&#xff1a; 进程调度&#…