# 第 17 章:随堂复习与企业真题(反射机制)


# 一、随堂复习

# 1. 反射的概述(熟悉)

  • Java 给我们提供了一套 API,使用这套 API 我们可以在运行时动态的获取指定对象所属的类,创建运行时类的对象,调用指定的结构(属性、方法)等
  • API:
    • java.lang.Class :代表一个类
    • java.lang.reflect.Method:代表类的方法
    • java.lang.reflect.Field:代表类的成员变量
    • java.lang.reflect.Constructor:代表类的构造器
    • … …
  • 反射的优点和缺点
    • 优点:
      • 提高了 Java 程序的灵活性和扩展性, 降低了耦合性 ,提高 自适应 能力

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

    • 缺点:
      • 反射的 性能较低
        • 反射机制主要应用在对灵活性和扩展性要求很高的系统框架上
      • 反射会 模糊 程序内部逻辑, 可读性较差
  • 反射,平时开发中,我们使用并不多。主要是在框架的底层使用

# 2. Class: 反射的源头

image-20230414145938600
  • Class 的理解 (掌握)

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

    • 类.class
    • 对象.getClass ()
    • (使用较多) Class.forName(String className)
    • (了解) 使用 ClassLoader 的方法 loadClass (String className)
  • Class 可以指向哪些结构。

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

# 3. 类的加载过程、类的加载器(理解)

image-20220417171411631
  • 类的加载过程

    过程1:类的装载(loading)
    将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
    
    过程2:链接(linking)
    > 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
    > 准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
    > 解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
    
    过程3:初始化(initialization)
    执行类构造器<clinit>()方法的过程。
    类构造器<clinit>()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
    
  • 类的加载器

    5.1 作用:负责类的加载,并对应于一个Class的实例。
    
    5.2 分类(分为两种):
    > 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:系统类加载器、应用程序类加载器
                > 我们自定义的类,默认使用的类的加载器。
        > 用户自定义类的加载器
                > 实现应用的隔离(同一个类在一个应用程序中可以加载多份);数据的加密。
    

# 4. 反射的应用 1:创建运行时类的对象(重点)

方法 1:Class 实例的 newInstance () 方法

Class clazz = Person.class;

//创建Person类的实例
Person per = (Person) clazz.newInstance();

System.out.println(per);
要想创建对象成功,需要满足:
条件1:要求运行时类中必须提供一个空参的构造器
条件2:要求提供的空参的构造器的权限要足够。

方法 2:Constructor 实例的 newInstance (Object ... objs) 方法

# 5. 反射的应用 2:获取运行时类所有的结构

(了解)获取运行时类的内部结构1:所有属性、所有方法、所有构造器
(熟悉)获取运行时类的内部结构2:父类、接口们、包、带泛型的父类、父类的泛型等

# 6. 反射的应用 3:调用指定的结构(重点)

  • 属性
  • 方法
  • 构造器
3.1 调用指定的属性(步骤)
步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
步骤2. setAccessible(true):确保此属性是可以访问的
步骤3. 通过Filed类的实例调用get(Object obj) (获取的操作)
                  或 set(Object obj,Object value) (设置的操作)进行操作。

3.2 调用指定的方法(步骤)
步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
步骤2. setAccessible(true):确保此方法是可访问的
步骤3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
     invoke()的返回值即为Method对应的方法的返回值
     特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null

3.3 调用指定的构造器(步骤)
步骤1.通过Class的实例调用getDeclaredConstructor(Class ... args),获取指定参数类型的构造器
步骤2.setAccessible(true):确保此构造器是可以访问的
步骤3.通过Constructor实例调用newInstance(Object ... objs),返回一个运行时类的实例。

# 7. 反射的应用 4:注解的使用(了解)

# 8. 体会:反射的动态性

public class ReflectTest {
    // 体会:静态性
    public Person getInstance(){
        return new Person();
    }
    // 体会:反射的动态性
    // 举例 1:
    public <T> T getInstance(String className) throws Exception {
        Class clazz = Class.forName(className);
        Constructor con = clazz.getDeclaredConstructor();
        con.setAccessible(true);
        return (T) con.newInstance();
    }
    @Test
    public void test1() throws Exception {
        Person p1 = getInstance();
        System.out.println(p1);
        String className = "com.atguigu04.other.dynamic.Person";
        Person per1 = getInstance(className);
        System.out.println(per1);
        String className1 = "java.util.Date";
        Date date1 = getInstance(className1);
        System.out.println(date1);
    }
    // 体会:反射的动态性
    // 举例 2:
    public Object invoke(String className,String methodName) throws Exception {
        //1. 创建全类名对应的运行时类的对象
        Class clazz = Class.forName(className);
        Constructor con = clazz.getDeclaredConstructor();
        con.setAccessible(true);
        Object obj = con.newInstance();
        //2. 获取运行时类中指定的方法,并调用
        Method method = clazz.getDeclaredMethod(methodName);
        method.setAccessible(true);
        return method.invoke(obj);
    }
    @Test
    public void test2() throws Exception {
        String className = "com.atguigu04.other.dynamic.Person";
        String methodName = "show";
        Object returnValue = invoke(className,methodName);
        System.out.println(returnValue);
    }
}

# 二、企业真题

# 2.1 反射概述

# 1. 对反射了解吗?反射有什么好处?为什么需要反射?(微 * 银行)

类似问题:
> Java反射的作用是什么?(三*重工、上海*和网络)
> Java反射机制的作用有什么?(上海明*物联网)
> 反射的具体用途?(阿***芝*信用项目组)

Java 的反射机制在程序运行时,能够动态加载并获取任意一个类的内部结构,并能够直接操作任意一个对象的属性和方法。本质上,JVM 得到 class 对象之后,再通过 class 对象进行反编译,从而获取对象的各种信息。

好处可以在运行时动态地:创建对象调用指定结构(属性、方法、构造器、父类、接口等),这样可以大大提高程序的灵活性和扩展性。

Java 中使用反射机制是为了解决以下问题

  1. 在程序运行时动态获取类信息,实现程序的灵活性和扩展性。

  2. 编写通用代码,无需针对每个具体的类编写特定的代码,提高了代码的重用率和通用性。

  3. 实现一些特殊场景的需求,如动态代理、自定义类加载器、工厂模式等。

总之,反射机制允许程序在运行时动态地加载类和对类进行操作,从而增强了 Java 语言的灵活性和功能性,同时也带来一些设计和实现上的风险。

# 2. 反射的使用场合和作用、及其优缺点(* 软国际)

类似问题:
> 反射机制的优缺点(君*科技)
> Java反射你怎么用的?(吉*航空)

反射机制在 Java 中被广泛应用于以下使用场合

  1. 框架开发 :在框架中,需要通过反射机制来加载和使用一些外部未知的类和对象,实现灵活性和扩展性。
  2. 插件式开发 :在 Java 应用程序中,可以使用反射机制动态地加载和调用其他模块的类和方法,从而实现插件化的设计。
  3. 动态代理 :通过反射机制,可以实现动态代理模式,为对象提供运行时的处理逻辑。
  4. 单元测试 :在 JUnit 等测试框架中,可以使用反射机制获取私有成员、构造器、方法,进行单元测试。
  5. JavaBean编程 :在 JavaBean 编程中,可以使用反射机制获取 Bean 的属性和方法,实现通用的数据操作和转换。

反射机制的作用:在运行时获取任意一个类的内部信息,并能够直接操作任意一个对象的内部属性和方法。

  1. 动态加载类 :通过反射机制,可以在运行时动态地加载其他模块或外部类,这对于框架开发、插件式编程等场合非常有用。
  2. 调用构造器、属性和方法 :通过反射机制,可以获取类的构造器、属性和方法,并且直接操作他们,比如修改私有属性、调用私有方法等。
  3. 泛型编程:使用反射机制,可以在运行时获取泛型信息,从而实现指定类型的通用代码编写。
  4. 动态代理:通过反射机制,可以在运行时动态生成代理对象,并在调用代理对象的方法时动态地执行相关逻辑。
  5. 生成动态代码:通过反射机制,可以在运行时动态生成新的类和代码,从而增强应用程序的灵活性和可扩展性。

反射机制的优点

  1. 动态性 :通过反射机制,可以在运行时获取、操作任意一个类的内部信息,增强了 Java 语言的动态性和灵活性。
  2. 通用性 :通过反射机制,可以编写通用代码,并且不需要提前知道具体的类名和方法名,提高了代码的通用性和重用性。
  3. 扩展性 :通过反射机制,可以实现一些特殊场景的需求,如自定义类加载器、动态代理等,增强了程序的扩展性和功能性。

反射机制的缺点

  1. 性能损失 :使用反射机制会带来一定的性能损失,比直接调用代码要更慢。
  2. 安全性风险 :通过反射机制,可以打破访问修饰符(如 private、final 等),从而影响程序的安全性。
  3. 代码可读性降低 :使用反射机制,由于不确定操作的实体是什么,因此可能导致代码的可读性降低。

# 3. 实现 Java 反射的类有什么?(君 * 科技)

类似问题:
> Java反射 API 有几类?(北京*蓝)

Java 反射 API 主要包含以下几个核心的类和接口:

java.lang.Class类 :表示一个 Java 类,可以通过该类获取类名、类修饰符、构造器、方法、注解等信息,并实例化对象。

java.lang.reflect包 下:

  • Field类 :表示一个类的属性或字段,可以通过该类获取属性的类型、修饰符、值并进行修改操作。

  • Method类 :表示一个类的方法,可以通过该类获取方法的返回值、参数、修饰符等信息,并且调用方法。

  • Constructor类 :表示一个类的构造器,可以通过该类获取构造器的修饰符、参数、并使用 newInstance 方法来创建对象。

  • Modifier类 :表示 Java 中的修饰符,可以通过该类判断某个成员是否具有某种修饰符,如 public、static、final 等等。

总之,这些类和接口提供了 Java 语言中反射机制的基础 API,开发者可以借助这些接口来操作和查询任意一个类的属性、方法和注解等信息。

# 4. 反射是怎么实现的?(上海立 * 网络)

image-20230414145938600

Java 的反射机制是基于 Java 虚拟机实现的。

具体来说,Java 反射机制主要实现了以下几个步骤:

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

    在 Java 程序运行时,每个类都会被虚拟机加载,并转换成一个 Class 对象,这个 Class 对象保存了这个类的所有信息,包括类名、继承关系、方法、变量等。因此,在运行时可以通过这个 Class 对象来访问和操作类的信息。

    Java 反射机制就是利用这个 Class 对象来实现的。通过反射,我们可以获得一个类的 Class 对象,并访问和操作这个类的所有信息,还可以根据需要动态地创建对象、调用方法和改变变量值等操作。

  2. 获取 Class 对象:使用 Class.forName() 方法或直接使用 运行时类.class 来获取一个类的 Class 对象。

  3. 获取运行时类的信息:通过 Class 对象可以获取类的名称、修饰符、父类、接口、构造函数、成员变量等信息。

  4. 创建对象:使用 Class对象的newInstance() 或 ** Constructor对象的newInstance() ** 方法来创建一个类的对象。

  5. 调用方法:使用 Method对象的invoke() 方法来调用一个对象的方法。

  6. 调用属性值:使用 Field对象的get()/set() 方法来读取 / 修改一个对象的属性值。

总之,Java 反射机制的实现依赖于 Java 虚拟机的能力,使得程序可以在运行时获得类的全部信息并进行操作。但在使用反射时需要注意性能、类型安全和访问权限等问题。

# 2.2 Class 的理解

# 1. Class 类的作用?生成 Class 对象的方法有哪些?(顺 *)

Class 类表示一个 Java 类或接口,存放类的结构信息,是 Java 反射机制的起源和入口提供了获取类信息的相关方法。类信息包括:属性、方法、构造方法、父类、接口、注解、类名、包名等各种元数据信息。

生成 Class 对象的方法主要有三种:

  1. 运行时类.class :如 String.class 就表示 String 类的 Class 对象,该方法是最简单、最安全、最常见的一种方式。

  2. 运行时类实例.getClass() :例如 Object obj = new String (); Class<?> cls = obj.getClass (); 该方法适用于已经获取到对象的情况下,可以方便地获取该对象所属的类类型。

  3. Class.forName(className) :根据类的完整路径(全类名 = 包名 + 类名)来获取 Class 对象,例如 Class<?> cls = Class.forName ("java.lang.String"); 该方法适用于需要动态加载类的情况下,可以在运行时加载并获取类的 Class 对象

总之,Java 中的 Class 类可以让我们在运行时动态地操作和访问类的变量、方法、构造器等各种元数据信息,是 Java 反射机制的基础。

# 2. Class.forName ("全路径") 会调用哪些方法?会调用构造方法吗?加载的类会放在哪?(上 * 银行外包)

image-20220417171411631

Class.forName ("全路径") 是为了将指定了全类名的类加载到内存中。在类的加载过程中,会经历三个阶段:

  • 装载(Loading)

  • 链接(Linking)

    • 验证(Verify)
    • 准备(Prepare)
    • 解析(Resolve)
  • 初始化(Initialization) :执行类构造器() 方法

    类构造器 <clinit>() 方法 是一种特殊的类方法,用于编译器在编译时自动为每个 Java 类生成并插入的隐式构造代码块。主要有以下几个特点:

    1. 该方法由编译器自动生成;通常称之为 “类构造器” 或 “类初始化方法”,其名称中的 “<” 和 “>" 符号表示该方法不是用户程序员定义的方法。

    2. 该方法的访问修饰符是默认级别的(即 package-private),因为它只能被 JVM 执行,不能被程序显式调用。

    3. () 方法是静态方法,没有参数和返回值。

    4. 在一个类第一次被使用时(加载类、创建实例等操作),JVM 会自动执行一次该类的() 方法,并保证线程安全

    5. 该方法主要作用是 **对类的 static 属性和 static 代码块进行初始化**,确保类在第一次使用时被正确地初始化。

    6. 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化

    总之,Java 中的类构造器() 方法是编译器生成的一个特殊方法,主要用于对一个类的静态属性和静态代码块进行初始化,确保类在第一次使用时被正确地初始化。

    注:类构造器是构造类信息的,不是构造该类对象的构造器,因此不会调用构造方法

加载的类放在 **内存的方法区**。

# 2.3 类的加载

# 1. 类加载流程(汇 ** 通、同 * 顺、凡 * 科技)

image-20220417171411631

Java 类加载流程主要分为装载、链接和初始化三个阶段。

  • 装载(Loading)通过 类加载器 将 .class 字节码文件加载到 JVM 内存中,在这个过程中会生成一个对应的 Class 对象,并方法区 中存储类加载器、类变量以及常量池等信息

  • 链接(Linking)

    • 验证(Verify):确保类文件的字节流符合 JVM 规范,并能够安全地被执行。

    • 准备(Prepare)为类中的 静态变量 分配内存空间,并赋予默认初始值(例如,int 类型变量默认值为 0)。

    • 解析(Resolve)将类文件中的符号引用替换成直接引用,例如将类中使用的其他类的符号引用映射为对应的直接引用。

  • 初始化(Initialization):JVM 在准备完毕后,会按照代码中的定义,对类进行初始化(即 **执行 类构造器(<clinit>() 方法) 的过程),主要是静态属性 进行赋值和 静态代码块 的执行**。

需要注意的是:

  1. 如果父类还没有被初始化,则父类先被初始化

  2. 接口在初始化时不会初始化其父接口

  3. 当一个类被加载、链接和初始化后,就可以把它的实例对象创建出来了(通过 new 关键字)。

总之,Java 类的加载流程是一个复杂而紧密的过程,多个类加载器共同完成,它为 Java 虚拟机提供了动态连接能力,使得程序运行时才装载这些类,从而实现了更高效、安全和灵活的编程模式。

# 2.4 创建对象

# 1. 说一下创建对象的几种方法?(华油 *** 集团、* 科软、凡 * 科技)

类似问题:
> 除了使用new创建对象之外,还可以用什么方法创建对象?(*云网络)

image-20221214145240412

Java 中创建对象的方法有很多种,以下是其中的 5 种方式:

  1. new 关键字
    这是最常见的,也是最简单的创建对象的方式,通过这种方式我们还可以调用任意的构造器(无参的和有参的)。
public class Main {
    public static void main(String[] args) {
        Person person1 = new Person();
        Person person2 = new Person("fsx", 18);
    }
}
  1. Class实例.newInstance()
    这是运用反射创建对象时最常用的方法,只能调用无参构造器,且必须是 public 的
Class clazz = Class.forName("com.example.Person");
Person person = (Person) clazz.newInstance();
  1. Constructor实例.newInstance()
    这是运用反射创建对象时最常用的方法之一,可以调用任何构造器
Constructor<Person> constructor = Person.class.getConstructor(String.class, int.class);
Person person = constructor.newInstance("fsx", 18);
  1. Clone()
    这种方式不调用任何构造器,但是当前类需要实现 Cloneable 接口,并实现 clone (),是通过调用 Object 类中的 clone () 方法来创建一个新的对象。
public class Person implements Cloneable {
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  1. 反序列化
    这种方式是通过将一个对象序列化成二进制流到一个文件或网络中,然后再从文件 / 网络中反序列化出来得到一个新的对象。借助对象流(ObjectOutputStream、ObjectInputStream),以及writeObject()、readObject()方法。
public class Main {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.obj"));
        oos.writeObject(new Person("fsx", 18));
        oos.close();
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.obj"));
        Person person = (Person) ois.readObject();
        ois.close();
    }
}

# 2. 如何找到对象实际类的?(* 度)

可以使用 getClass() 方法来获取对象的实际类。

例如,如果有一个名为 obj 的对象,可以使用以下代码获取它的实际类:

Class clazz = obj.getClass();

# 3. Java 反射创建对象效率高还是通过 new 创建对象的效率高?为什么?(三 * 重工)

通过 new 创建对象的效率比较高。原因是,通过反射时,先获取运行时类的 Class 对象再获取构造函数最后调用构造函数创建对象,这个过程比较繁琐,所以效率较低。

  1. 使用 new 关键字创建对象的示例代码:
public class MyClass {
    private int value;
    
    public MyClass(int value) {
        this.value = value;
    }
    
    // other methods and fields
}
MyClass obj = new MyClass(10);
  1. 使用反射方式创建对象的示例代码:
Class clazz = MyClass.class;
Constructor constructor = clazz.getConstructor(int.class);
MyClass obj = (MyClass) constructor.newInstance(10);

在上面的示例代码中,首先通过反射获取 MyClass 类的 Class 对象,并通过 Class 对象获取参数类型为 int 的构造函数后,再通过反射调用该构造函数创建对象

# 2.5 调用属性、方法

# 1. 如何利用反射机制来访问一个类的方法?(神州 ** 软件)

调用指定的方法(步骤)
步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class ... args),获取指定的方法
步骤2. setAccessible(true):确保此方法是可访问的
步骤3.通过Method实例调用invoke(Object obj,Object ... objs),即为对Method对应的方法的调用。
     invoke()的返回值即为Method对应的方法的返回值
     特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null

利用反射机制来访问一个类的方法,可以通过以下步骤实现:

  1. 获取运行时类的 Class 对象:使用 Class.forName()运行时类.class运行时类的实例.getClass() 方法。

  2. 获取目标方法的 Method 对象:通过 Class 的实例调用 getDeclaredMethod(String methodName,Class ... args) 方法。

  3. 确保方法可访问:如果目标方法是私有方法,还需要通过 setAccessible(true) 设置 Method 对象为可访问的。

  4. 调用目标方法:通过 Method 对象的 invoke(Object obj,Object ... objs) 方法。

下面提供一个示例代码:

// 定义一个测试类
public class Test {
    public void sayHello() {
        System.out.println("Hello");
    }
}
// 调用 Test 类中的 sayHello 方法
Class<?> clazz = Class.forName("Test");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("sayHello");
method.setAccessible(true);
method.invoke(obj);

在上述示例代码中,首先通过 Class.forName() 方法获取 Test 类的 Class 对象,然后通过该对象获取 sayHello 方法的 Method 对象。由于 sayHello 方法是公共方法,因此无需设置 Method 对象为可访问的。最后通过 Method 对象的 invoke() 方法调用 sayHello 方法并输出结果。

# 2. 如何利用反射机制来访问一个类的私有属性?

利用反射机制来访问一个类的私有属性,可以通过以下步骤实现:

  1. 获取运行时类的 Class 对象:使用 Class.forName()object.getClass() 方法。

  2. 获取指定属性的 Field 对象:通过 Class 对象的 getDeclaredField(String fieldName) 等方法。

  3. 设置 Field 对象为可访问的:使用 setAccessible() 方法设置为 true。

  4. 获取或者修改属性的值:使用 Field 对象的 get(Object obj)set(Object obj,Object value) 方法。

下面是一个示例代码,用于演示如何使用反射机制访问一个类的私有属性。

import java.lang.reflect.Field;
public class MyClass {
    private String name;
    public MyClass(String name) {
        this.name = name;
    }
    // other methods and fields
}
public class MainClass {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 创建需要反射操作的对象
        MyClass obj = new MyClass("test");
        // 获取 MyClass 类中的 name 私有字段
        Field field = obj.getClass().getDeclaredField("name");
        // 设置访问标志位,使得私有字段可访问
        field.setAccessible(true);
        // 获取 name 字段的值
        String nameValue = (String) field.get(obj);
        // 输出 name 字段的值
        System.out.println(nameValue);
        // 修改 name 字段的值
        field.set(obj, "new_test");
        // 获取修改后的 name 字段值
        nameValue = (String) field.get(obj);
        // 输出修改后的 name 字段值
        System.out.println(nameValue);
    }
}

在上述示例代码中,我们首先创建一个名为 MyClass 的类,并定义了一个私有的属性 name。在 MainClass 中,我们首先通过 getClass() 方法获取对象所属类的 Class 对象,然后使用 getDeclaredField() 方法获取 name 属性,由于 name 是私有属性,所以需要设置 setAccessible(true) 使其可被访问。接着,我们调用 get() 方法获取 name 属性的值,并输出验证是否正确获取。最后,我们修改 name 属性的值,再次使用 get() 方法获取修改后的 name 属性值,输出验证是否修改成功。需要注意的是,访问私有属性需要抛出 NoSuchFieldException 和 IllegalAccessException 异常。

# 3. 说一下 Java 反射获取私有属性,如何改变值?(阿 **** 麻信用项目组)

调用指定的属性(步骤)
步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性
步骤2. setAccessible(true):确保此属性是可以访问的
步骤3. 通过Filed类的实例调用get(Object obj) (获取的操作)
                       或 set(Object obj,Object value) (设置的操作)进行操作。
针对于核心源码的api,内部的私有的结构在jdk17中就不可以通过反射调用了。

使用 Java 反射机制可以获取私有属性,并通过反射修改其值。下面是一个示例代码,可以帮助理解反射如何获取私有属性并修改属性值。

  • 首先通过对象的 getClass() 方法获取对象所属类的 Class 对象
  • 然后使用 getDeclaredField() 方法获取 name 字段
  • 由于 name 是私有字段,所以需要设置 setAccessible(true) 来使其可被访问
  • 接着,我们调用 set() 方法对 name 字段进行赋值操作
  • 最后,我们再使用 get() 方法获取修改后的 name 字段值,输出验证是否修改成功

需要注意的是,访问私有属性需要抛出 NoSuchFieldException 和 IllegalAccessException 异常。

class MyClass {
    private String name = "test";
    // getter and setter methods
}
public class MainClass {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        // 创建需要反射操作的对象
        MyClass obj = new MyClass();
        // 获取 MyClass 类中的 name 私有字段
        Field field = obj.getClass().getDeclaredField("name");
        // 设置访问标志位,使得私有字段可访问
        field.setAccessible(true);
        // 修改 name 字段的值
        field.set(obj, "new_test");
        // 获取修改后的 name 字段值
        String nameValue = (String) field.get(obj);
        // 输出修改后的 name 字段值
        System.out.println(nameValue);
    }
}