第一章 反射(2023版本IDEA)

学习目标

  • 1.1 Java反向概述
    • 1.1.1 什么是反射
    • 1.1.2 Java反射常用的API
  • 1.2 反射的应用
    • 1.2.1 获取类的信息
      • 1.获取Class实例
      • 2.从Class实例获取信息
    • 1.2.2 通过反射来创建Java类型的实例有如下两种方式。
    • 1.2.3 访问类的属性

  在本章中,将学习Java中反射的用法,如通过反射查看类的信息,创建实例、调用方法等。通过本章学习,我们能够了解反射的概念和作用,能够在开发中简单应用反射,能够借助API独立解决问题。

1.1 Java反向概述

  反射(Reflection)机制是Java语言特性之一,是Java被视为动态(或准动态)语言的一个关键特性。

1.1.1 什么是反射

  在计算机领域,反射指一种能力,能够自描述和自控制,即在运行状态中,动态获取类信息及动态调用实例方法的能力。

Java反射有以下3个动态特性。

  • 运行时创建实例。
  • 运行期间调用方法。
  • 运行时更改属性。

  Java反射(Reflection)提供了在运行时检查和修改代码的能力,这些能力通常被称为动态特性。以下是Java反射的三个主要动态特性及其示例代码:

  1. 动态地获取类的信息: 使用反射可以动态地获取类的名称、包、修饰符、父类、实现的接口、字段、方法等信息。
    示例代码:
Class<?> clazz = String.class;  
System.out.println("Class Name: " + clazz.getName());  
System.out.println("Package Name: " + clazz.getPackage().getName());  
System.out.println("Modifiers: " + Modifier.toString(clazz.getModifiers()));  
System.out.println("Superclass: " + clazz.getSuperclass().getName());  
for (Class<?> iface : clazz.getInterfaces()) {  
    System.out.println("Interface: " + iface.getName());  
}
  1. 动态地调用对象的方法: 使用反射可以动态地调用对象的方法,即使这些方法在编译时是不可知的。
    示例代码:
try {  
    Class<?> clazz = String.class;  
    Method method = clazz.getMethod("substring", int.class, int.class);  
    String str = "Hello, World!";  
    Object result = method.invoke(str, 0, 5); // 调用 str.substring(0, 5)  
    System.out.println("Substring: " + result); // 输出 "Hello"  
} catch (Exception e) {  
    e.printStackTrace();  
}
  1. 动态地创建和操作对象: 使用反射可以动态地创建类的实例,并设置和获取字段的值。
    示例代码:
try {  
    Class<?> clazz = MyClass.class; // 假设 MyClass 有一个无参构造函数和一个名为 "field" 的字段  
    Object obj = clazz.getDeclaredConstructor().newInstance(); // 创建 MyClass 的实例  
    Field field = clazz.getDeclaredField("field"); // 获取 "field" 字段  
    field.setAccessible(true); // 如果字段是私有的,需要设置为可访问  
    field.set(obj, "New Value"); // 设置字段的值  
    System.out.println(field.get(obj)); // 获取字段的值并输出  
} catch (Exception e) {  
    e.printStackTrace();  
}

注意
  MyClass 是上述示例中的一个假设类,你需要用实际的类名替换它。同时,请注意使用反射时的安全性问题,例如当处理私有字段或方法时。另外,反射操作通常比直接方法调用慢,因此应该避免在性能关键的代码中使用反射

通过Java反射可以实现以下功能。

  • 在运行时探知任意一个实例所属的类。
  • 在运行时构造任意一个类的实例。
  • 在运行时探知任意一个类所真有的方法和属性。
  • 在运行时调用任意一个实例的方法。

通过Java反射,我们可以实现多种功能,包括但不限于:

1.动态加载类

try {  
    // 加载类  
    Class<?> clazz = Class.forName("com.example.MyClass");  
    // 实例化对象(假设有一个无参构造函数)  
    Object obj = clazz.getDeclaredConstructor().newInstance();  
    // ...  
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException e) {  
    e.printStackTrace();  
}

2.动态调用方法

try {  
    Class<?> clazz = String.class;  
    // 获取指定名称和参数类型的方法  
    Method method = clazz.getMethod("charAt", int.class);  
    String str = "Hello";  
    // 调用方法  
    Object result = method.invoke(str, 0);  
    System.out.println("First char: " + result); // 输出 "H"  
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {  
    e.printStackTrace();  
}

3.动态访问和修改字段

try {  
    Class<?> clazz = MyClass.class; // 假设 MyClass 有一个公共字段 "publicField"  
    Object obj = clazz.getDeclaredConstructor().newInstance();  
      
    // 获取字段  
    Field field = clazz.getDeclaredField("publicField");  
      
    // 设置字段值(如果需要访问私有字段,使用 field.setAccessible(true))  
    field.set(obj, "NewValue");  
      
    // 获取字段值  
    Object value = field.get(obj);  
    System.out.println("Field value: " + value); // 输出 "NewValue"  
} catch (NoSuchFieldException | IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException e) {  
    e.printStackTrace();  
}

4.获取类的所有方法、字段、构造函数

Class<?> clazz = MyClass.class;  
  
// 获取所有公共方法  
Method[] methods = clazz.getMethods();  
for (Method method : methods) {  
    System.out.println(method.getName());  
}  
  
// 获取所有字段(包括私有)  
Field[] fields = clazz.getDeclaredFields();  
for (Field field : fields) {  
    System.out.println(field.getName());  
}  
  
// 获取所有构造函数  
Constructor<?>[] constructors = clazz.getDeclaredConstructors();  
for (Constructor<?> constructor : constructors) {  
    System.out.println(constructor.toString());  
}

5.动态修改数组大小(尽管这不是反射的直接用途,但可以通过反射操作Array类来实现)

Object array = Array.newInstance(String.class, 5); // 创建一个大小为5的String数组  
Array.set(array, 0, "First"); // 设置第一个元素为"First"  
  
// 注意:Java数组的大小在创建后是固定的,不能通过反射改变。  
// 但你可以通过创建一个新的数组并复制元素来“模拟”改变大小。  
  
// 假设我们要“扩大”数组到10个元素  
Object newArray = Array.newInstance(String.class, 10);  
System.arraycopy(array, 0, newArray, 0, Array.getLength(array));  
  
// 现在newArray是一个新的大小为10的数组,并且包含了原来数组的所有元素

6.动态代理(虽然不完全是反射的直接应用,但它是基于反射实现的)
使用Proxy类和InvocationHandler接口可以动态地创建代理对象,这些对象在方法调用时可以执行额外的逻辑。

请注意,使用反射时需要谨慎处理异常和安全性问题,并且由于性能原因,应避免在频繁执行的代码路径上使用反射。

1.1.2 Java反射常用的API

使用Java反射技术,常用的类如下。

  • java.lang.Class类:反射的核心类,反射所有的操作都围绕该类来生成的。通过Class类可以获得类的属性、方法等内容信息。
  • java.lang.reflect.Constructor类:表示类的构造方法。
  • java.lang.reflect.Field类:表示类的属性,可以获取和设置中属性的值。
  • java.lang.reflect.Method类:表示类的方法,可以用来获取类中方法的信息或执行方法。

  在Java中,反射是一种强大的工具,允许我们在运行时检查类、接口、字段和方法的信息,并且可以动态地创建和调用对象。以下是一些Java反射常用的API代码示例:
1. 获取Class对象

Class<?> clazz = String.class; // 使用.class语法  
Class<?> clazz2 = Class.forName("java.lang.String"); // 使用Class.forName()方法

2. 获取类的构造函数

try {  
    Constructor<?> constructor = String.class.getConstructor(String.class); // 获取接受String参数的构造函数  
    Object instance = constructor.newInstance("Hello, World!"); // 创建实例  
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {  
    e.printStackTrace();  
}

3. 获取类的字段

try {  
    Field field = String.class.getDeclaredField("value"); // 获取名为value的字段(String的内部字段,可能不可见)  
    field.setAccessible(true); // 如果字段不是public的,需要设置可访问  
  
    // 假设我们有一个String实例  
    String str = "Hello";  
    Object value = field.get(str); // 获取字段的值  
    // 注意:对于String类的value字段,通常不建议直接访问,因为它不是public的且可能在不同版本的Java中有不同的实现  
} catch (NoSuchFieldException | IllegalAccessException e) {  
    e.printStackTrace();  
}

4. 获取类的方法

try {  
    Method method = String.class.getMethod("substring", int.class, int.class); // 获取名为substring且接受两个int参数的方法  
    String str = "Hello, World!";  
    String result = (String) method.invoke(str, 0, 5); // 调用方法并获取结果  
    System.out.println(result); // 输出 "Hello"  
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {  
    e.printStackTrace();  
}

5. 修改类的字段值(对于可修改的字段)

try {  
    Field field = SomeClass.class.getDeclaredField("someField"); // 假设SomeClass有一个名为someField的可访问字段  
    SomeClass instance = new SomeClass();  
    field.set(instance, "newValue"); // 设置字段的值  
} catch (NoSuchFieldException | IllegalAccessException e) {  
    e.printStackTrace();  
}

6. 遍历类的所有公共方法

Method[] methods = String.class.getMethods(); // 获取String类的所有公共方法  
for (Method method : methods) {  
    System.out.println(method.getName()); // 打印方法名  
}

7. 遍历类的所有字段(包括私有字段)

Field[] fields = SomeClass.class.getDeclaredFields(); // 获取SomeClass类的所有字段(包括私有字段)  
for (Field field : fields) {  
    System.out.println(field.getName()); // 打印字段名  
}

8. 判断类是否实现了某个接口

boolean isInterfaceImplemented = SomeClass.class.isInstance(List.class); // 错误的用法,应该使用isAssignableFrom  
boolean isList = List.class.isAssignableFrom(SomeClass.class); // 正确的用法,检查SomeClass是否是List或其子接口的实现类  
System.out.println(isList);

注意
  在使用反射时,请确保了解你正在操作的类、字段和方法的访问权限。对于非public的成员,你可能需要调用setAccessible(true)来绕过Java的访问控制检查,但这可能会破坏封装性并引入安全风险。因此,在实际应用中应谨慎使用。

1.2 反射的应用

在Java程序中使用反射的基本步骤如下。

  • (1)导入java.lang.reflect包中的相关类。
  • (2)获得需要操作的类的Class实例。
  • (3)调用Class实例的方法获取Field、Method等实例。
  • (4)使用反射API操作实例成员。

在Java程序中使用反射的基本步骤通常包括以下几个代码示例所展示的过程:

1.加载类: 使用Class.forName()方法加载类。

try {  
    Class<?> clazz = Class.forName("com.example.MyClass");  
    // ...  
} catch (ClassNotFoundException e) {  
    e.printStackTrace();  
}

2.获取类的构造方法: 通过Class对象的getConstructor()或getDeclaredConstructor()方法获取构造方法。

try {  
    Class<?> clazz = MyClass.class; // 或者从步骤1中获取  
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class); // 假设有一个接受String参数的构造方法  
    // 如果构造方法不是public的,需要设置可访问  
    constructor.setAccessible(true);  
    // ...  
} catch (NoSuchMethodException e) {  
    e.printStackTrace();  
}

3.创建类的实例: 使用获取到的构造方法创建类的实例。

try {  
    Constructor<?> constructor = ... // 从步骤2中获取  
    Object obj = constructor.newInstance("someStringValue"); // 假设构造方法接受一个String参数  
    // ...  
} catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {  
    e.printStackTrace();  
}

4.获取类的字段: 通过Class对象的getField()或getDeclaredField()方法获取字段。

try {  
    Class<?> clazz = MyClass.class;  
    Field field = clazz.getDeclaredField("fieldName"); // 假设有一个名为fieldName的字段  
    // 如果字段不是public的,需要设置可访问  
    field.setAccessible(true);  
    // ...  
} catch (NoSuchFieldException e) {  
    e.printStackTrace();  
}

5.访问和修改字段的值: 使用Field对象的get()和set()方法访问和修改字段的值。

try {  
    Field field = ... // 从步骤4中获取  
    Object obj = ... // 从步骤3中获取的对象实例  
    // 获取字段的值  
    Object value = field.get(obj);  
    // 修改字段的值  
    field.set(obj, "newValue");  
    // ...  
} catch (IllegalAccessException e) {  
    e.printStackTrace();  
}

6.获取类的方法: 通过Class对象的getMethod()或getDeclaredMethod()方法获取方法。

try {  
    Class<?> clazz = MyClass.class;  
    Method method = clazz.getMethod("methodName", int.class); // 假设有一个名为methodName接受int参数的方法  
    // ...  
} catch (NoSuchMethodException e) {  
    e.printStackTrace();  
}

7.调用方法: 使用Method对象的invoke()方法调用方法。

try {  
    Method method = ... // 从步骤6中获取  
    Object obj = ... // 从步骤3中获取的对象实例  
    Object result = method.invoke(obj, 123); // 假设方法接受一个int参数,并返回一个结果  
    // ...  
} catch (IllegalAccessException | InvocationTargetException e) {  
    e.printStackTrace();  
}

这些步骤展示了在Java中使用反射的基本流程。请注意,当处理非公共成员(字段、方法等)时,你可能需要使用setAccessible(true)来允许访问。同时,使用反射时应谨慎处理异常,并确保代码的安全性。

1.2.1 获取类的信息

  一个类或接口被加载后,从系统中都能获得一个代表该类或接口的Class类型的实例,通过该实例就可以访问到Java虚拟机中的这个类或接口。

1.获取Class实例

  Java程序中获得Class实例通常有如下3种方式,可根据实例情况灵活选择。

  • 调用类或接口实例的getClass()方法。
      在Java中,所有的对象都继承自Object类,而Object类定义了一个getClass()方法。因此,任何Java对象的实例都可以调用getClass()方法来获取其对应的Class对象。以下是一个简单的示例代码,展示了如何调用一个类或接口实例的getClass()方法:
public class MyClass {  
    public static void main(String[] args) {  
        // 创建一个MyClass的实例  
        MyClass myInstance = new MyClass();  
  
        // 调用getClass()方法获取Class对象  
        Class<?> clazz = myInstance.getClass();  
  
        // 输出类的名称  
        System.out.println("Class name: " + clazz.getName());  
  
        // 假设我们有一个实现了某个接口的实例  
        MyInterface myInterfaceInstance = new MyClassThatImplementsInterface(); // MyClassThatImplementsInterface需要实现MyInterface  
  
        // 同样可以调用getClass()方法获取Class对象  
        Class<?> interfaceClazz = myInterfaceInstance.getClass();  
  
        // 输出类的名称(注意这里输出的是实现类的名称,而不是接口的名称)  
        System.out.println("Interface instance class name: " + interfaceClazz.getName());  
  
        // 如果你想要获取接口本身的Class对象,你应该这样做:  
        Class<?> interfaceType = MyInterface.class;  
  
        // 输出接口的名称  
        System.out.println("Interface type name: " + interfaceType.getName());  
    }  
}  
  
interface MyInterface {  
    // 接口定义  
}  
  
class MyClassThatImplementsInterface implements MyInterface {  
    // 实现接口的方法  
}

  在上面的代码中,我们首先创建了一个MyClass的实例,并调用了它的getClass()方法来获取其Class对象。然后,我们创建了一个实现了MyInterface接口的MyClassThatImplementsInterface的实例,并同样调用了getClass()方法。但请注意,即使myInterfaceInstance是一个接口类型的变量,getClass()方法返回的仍然是实际对象的类(即MyClassThatImplementsInterface),而不是接口本身的Class对象。
要获取接口本身的Class对象,你应该直接使用接口名加上.class,如MyInterface.class。

  • 调用类或接口的class属性。
      在Java中,类和接口本身都有一个名为class的静态成员,它代表了该类或接口的Class对象。这个class属性是隐式的,并且可以通过类名或接口名加上.class后缀来访问。以下是如何调用类或接口的class属性的示例代码:
public class MyClass {  
    public static void main(String[] args) {  
        // 获取MyClass的Class对象  
        Class<MyClass> myClassClass = MyClass.class;  
  
        // 输出类的名称  
        System.out.println("MyClass Class name: " + myClassClass.getName());  
  
        // 假设我们有一个接口MyInterface  
        interface MyInterface {  
            // 接口定义  
        }  
  
        // 获取MyInterface的Class对象  
        Class<MyInterface> myInterfaceClass = MyInterface.class;  
  
        // 输出接口的名称  
        System.out.println("MyInterface Class name: " + myInterfaceClass.getName());  
    }  
}

  在上面的代码中,我们使用了MyClass.class来访问MyClass的Class对象,并且使用MyInterface.class来访问MyInterface接口的Class对象。这些Class对象代表了对应的类或接口在Java运行时环境中的元数据。

  请注意,虽然myInterfaceClass是一个Class类型的变量,但它实际上包含的是关于MyInterface接口的元数据,而不是关于任何特定实现类的元数据。接口本身没有实例,所以你不能创建一个MyInterface的实例并调用它的getClass()方法(因为接口不能实例化)。你只能使用接口名加上.class来获取接口的Class对象。

  • 使用Class.forName()方法。
      在Java中,Class.forName()方法用于在运行时动态地加载类。该方法接受一个包含完全限定类名的字符串,并返回对应的Class对象。如果找不到指定的类,它会抛出一个ClassNotFoundException。

以下是使用Class.forName()方法的示例代码:

public class ClassForNameExample {  
    public static void main(String[] args) {  
        try {  
            // 加载String类  
            Class<?> stringClass = Class.forName("java.lang.String");  
            System.out.println("Loaded class: " + stringClass.getName());  
  
            // 加载自定义类(需要确保该类在类路径上)  
            Class<?> myClass = Class.forName("com.example.MyClass");  
            System.out.println("Loaded custom class: " + myClass.getName());  
  
            // 尝试加载不存在的类(会抛出ClassNotFoundException)  
            // Class nonExistentClass = Class.forName("com.example.NonExistentClass");  
  
        } catch (ClassNotFoundException e) {  
            e.printStackTrace();  
            System.out.println("Class not found: " + e.getMessage());  
        }  
    }  
}

  在上面的代码中,我们首先使用Class.forName()加载了Java核心库中的String类,并打印出了它的名称。然后,我们尝试加载一个自定义的类com.example.MyClass(注意你需要确保这个类在类路径上)。

  如果你尝试加载一个不存在的类(比如com.example.NonExistentClass),Class.forName()会抛出一个ClassNotFoundException,你需要捕获这个异常并相应地处理它。

  使用Class.forName()的一个常见场景是在JDBC中加载数据库驱动。例如,当你使用MySQL数据库时,你可能需要这样加载驱动:

try {  
    Class.forName("com.mysql.cj.jdbc.Driver");  
    // 接下来,你可以使用DriverManager来建立数据库连接  
} catch (ClassNotFoundException e) {  
    e.printStackTrace();  
    // 处理异常  
}

注意
  从Java 6开始,JDBC 4.0规范自动将驱动类注册到DriverManager中,因此在很多情况下,你不再需要显式地调用Class.forName()来加载JDBC驱动。但是,在一些旧版本的Java或特定的配置中,你可能仍然需要这样做。

2.从Class实例获取信息

  在获得了某个类型对应的Class实例之后,就可以调用Class实例的方法获取类型的信息。Class类提供了大量实例方法来获取对应类型的详细信息。

  获取对应类型的基本信息,相关方法如下:

方法 说明
String getName() 以字符串形式返回该类型的名称
String getSimpleName() 以字符串形式返回该类型的简单(类名)
Package getPackage() 获取该类型所在的包
Class getSuperclass() 获取该类型的超类的Class实例
Class[] getInterfaces() 返回该类型所实例的全部接口的Class实例
int getModifiers() 返回该类型的所有修饰符,由public、protected、private、final、static、abstract等对应的int常量组成,返回的整数应使用Modifier工具类来解码,才可以判断修饰符的构成。
Class[] getDeclaredClasses() 返回该类型中包含的全部内部类的Class实例
Class getDeclaringClass() 返回该类型所在的外部类的Class实例

  为了通过反射获取JavaBean的类型信息,我们可以首先创建两个简单的JavaBean类。然后,我们可以编写一个测试类,该类使用反射来检查这两个JavaBean的Class对象,并获取它们的各种类型信息。

下面是两个简单的JavaBean类的示例:
  Person类

package shili;

import java.io.IOException;
import java.io.Serializable;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:10
 **/
public class Person extends BaseClass implements Serializable {
    private String name;
    static final int AGE = 30;
    protected String address;
    public String message;

    public Person(){

    }

    private Person(String name){
        this.name = name;
    }

    protected Person(String name, String address, String message){
        this.name = name;
        this.address = address;
        this.message = message;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    static final  int getAge(){
        return AGE;
    }

    protected String getAddress(){
        return address;
    }

    private void silentMethod()throws IOException,NullPointerException {
        System.out.println("这是悄悄话");
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

  BaseClass类 (无须定义任何成员)

package shili;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:11
 **/
public class BaseClass {
}

  获取对应类型所包含的构造方法,常用方法如下:

方法 说明
Constructor getConstructor(Class…params) 返回该类型指定参数列表的public构造方法,构造方法的参数列表与params所指定的类型列表所匹配
Constructor[] getConstructors() 返回该类型的所有public构造方法
Constructor getDeclaredConstructor(Class…params) 返回该类型的指定参数列表的构造方法,访问级别不限
Constructor[] getDeclaredConstructors() 返回该类型的所有构造方法,访问级别不限
Constructor getDeclaredConstructor() 获取该类型的无参数构造函数
package shili;

import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:16
 **/
public class GetClassConstrustorsInfo {
    public static void main(String[] args) {
        // 获取Person类声明的所有构造方法
        // 它们是公共、保护、默认(包)访问和私有构造方法
        // 如果此 Class 实例表示一个接口、一个基本类型、一个数组类或 void,则此方法返回一个长度为 0 的数组
        Constructor[] cons = Person.class.getDeclaredConstructors();

        //构造方法的一些信息
        System.out.println("========构造方法展示========");
        for (Constructor con : cons){

            // 该构造方法的访问修饰符
            System.out.print("访问修饰符:");
            int modifier = con.getModifiers();
            //判断该构造方法的访问修饰符
            if((modifier & Modifier.PUBLIC) == Modifier.PUBLIC)
                System.out.println("public");
            else if((modifier & Modifier.PROTECTED) == Modifier.PROTECTED)
                System.out.println("protected");
            else if((modifier & Modifier.PRIVATE) == Modifier.PRIVATE)
                System.out.println("private");
            else
                System.out.println("default(package)");

            // 获取构造方法的参数列表
            Class[] params = con.getParameterTypes();
            if (params.length == 0){
                System.out.println("该构造方法没有参数");
            }else{
                System.out.println("该构造方法的参数列表为:[");
                for (int i = 0; i < params.length; i++){
                    if(i != 0)
                        System.out.print(",");
                    System.out.print(params[i].getName());
                }
                System.out.println("]");
            }
            System.out.println("-----------------------");
        }
    }
}

  获取对应类型所包含的属性,常用方法如下:

方法 说明
Field getField(String name) 返回该类型中指定名称的public属性,name参数用于指定属性名称。例如:clz.getField(“age”);//clz为某Class对象,age为属性名
Field[] getFields 返回该类型中所有public属性
Field getDeclaredField(String name) 返回该类型中指定名称的属性,与属性的访问级别无关
Field[] getDeclaredFields() 返回该类型中的全部属性,与属性的访问级别无关
package shili;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:20
 **/
public class GetClassFieldsInfo {
    public static void main(String[] args) {
        // 获取Person中的所有属性,
        // 包括公共、保护、默认(包)访问和私有属性,但不包括继承的属性,
        // 如果该类或接口不声明任何属性,或者此 Class 实例表示一个基本类型、一个数组或 void,则此方法返回一个长度为 0 的数组。
        Field[] fields = Person.class.getDeclaredFields();

        // 展示属性的一些信息
        System.out.println("===========属性展示==========");
        for (Field field : fields) {
            System.out.println("类型:" + field.getType().getName() + " 属性名:" + field.getName());

            System.out.print("访问修饰符:");
            int modifier = field.getModifiers();
            System.out.println(Modifier.toString(modifier));

            // 判断该属性是否有static修饰符
            if ((modifier & Modifier.STATIC) == Modifier.STATIC)
                System.out.println("这是一个静态属性");
            // 判断该属性是否有final修饰符
            if ((modifier & Modifier.FINAL) == Modifier.FINAL)
                System.out.println("这是一个final属性");

            System.out.println("----------------------------");
        }
    }
}

  访问对应类型所包含的方法,常用方法如下:

方法 说明
Method getMethond(String name,Class…params) 返回该实例中指定的public方法,name参数用于指定方法名称,params参数指定参数列表
Method[] getMethods() 返回该实例中所有public方法
Method getDeclaredMethod(String nae,Class…params) 返回该实例中指定的方法,与方法的访问级别无关
Method[] getDeclaredMethods() 返回该实例中的全部方法,与级别无关
package shili;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:21
 **/
public class GetClassMethodsInfo {
    public static void main(String[] args) {
        // 获取Person中的所有方法,
        // 包括公共、保护、默认(包)访问和私有方法,但不包括继承的方法,
        // 如果该类或接口不声明任何方法,或者此 Class 实例表示一个基本类型、一个数组或 void,则此方法返回一个长度为 0 的数组。
        Method[] methods = Person.class.getDeclaredMethods();

        // 展示方法的一些信息
        System.out.println("===========方法展示==========");
        for (Method method : methods) {
            System.out.println("方法名:" + method.getName());
            System.out.println("返回值类型:" + method.getReturnType().getName());

            // 获取方法的参数列表
            Class[] params = method.getParameterTypes();
            if (params.length == 0) {
                System.out.println("该方法没有参数");
            } else {
                System.out.print("该方法的参数列表为:[");
                for (int i = 0; i < params.length; i++) {
                    if (i != 0)
                        System.out.print(", ");
                    System.out.print(params[i].getName());
                }
                System.out.println("]");
            }

            System.out.print("访问修饰符:");
            int modifier = method.getModifiers();
            // 判断该方法的访问修饰符
            System.out.println(Modifier.toString(modifier));

            // 获取方法所属的类或接口的Class实例
            Class declaringClass = method.getDeclaringClass();
            System.out.println("方法声明在:" + declaringClass.getName() + " 中");

            // 获取方法抛出的异常类型,即throws子句中声明的异常
            Class[] exceptions = method.getExceptionTypes();
            if (exceptions.length > 0) {
                System.out.print("该方法抛出的异常有:[");
                for (int i = 0; i < exceptions.length; i++) {
                    if (i != 0)
                        System.out.print(", ");
                    System.out.print(exceptions[i].getName());
                }
                System.out.println("]");
            }
            System.out.println("----------------------------");
        }
    }
}

1.2.2 通过反射来创建Java类型的实例有如下两种方式。

  通过反射来创建Java类型的实例有如下两种方式。

  • 使用Class实例的newlnstance()方法创建相关类型的实例。
  • 使用Constructor实例创建相关类型的实例。
      使用Class实例的newlnstance()方法创建相关类型的实例,这种方式要求改Class实例相关的类型有无参构造方法,执行newlnstance()方法实际上就是调用无参构造方法来创建实例。
package shili;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:21
 **/
public class AccessPersonConstructors {
    public static void main(String[] args) {
        // 本例演示通过反射操作Person的属性
        try {
            // 通过反射加载一个Person实例
            Class cls = Class.forName("shili.Person");
            Object person = cls.newInstance();

            // 获取name属性
            Field name = cls.getDeclaredField("name");
            // name属性为private,这里已超出其访问范围,不能直接访问
            // 通过setAccessable方法,设定为可以访问
            name.setAccessible(true);
            // 先取值看一下
            System.out.println("赋值前的name:" + name.get(person));
            // 为name属性赋值
            name.set(person, "New Person");
            // 展示一下赋值效果
            System.out.println("赋值后的name:" + name.get(person));
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

每个Constructor实例对应一个构造方法,指定构造方法来创建Java对象的步骤如下:

  • (1)获取与该类型相关的Class实例。
  • (2)调用Class实例的方法获取表示指定构造方法的Constructor实例。
  • (3)调用Constructor实例的newlnstance()方法来创建相关类型的实例。
package shili;

import java.lang.reflect.Field;

/**
 * @Author:T369李喆
 * @Date:2024/6/27 17:34
 **/
public class AccessPersonFields {
    public static void main(String[] args) {
        // 本例演示通过反射操作Person的属性
        try {
            // 通过反射加载一个Person实例
            Class cls = Class.forName("shili.Person");
            Object person = cls.newInstance();

            // 获取name属性
            Field name = cls.getDeclaredField("name");
            // name属性为private,这里已超出其访问范围,不能直接访问
            // 通过setAccessable方法,设定为可以访问
            name.setAccessible(true);
            // 先取值看一下
            System.out.println("赋值前的name:" + name.get(person));
            // 为name属性赋值
            name.set(person, "New Person");
            // 展示一下赋值效果
            System.out.println("赋值后的name:" + name.get(person));
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

注意
  受访问修饰符的限制,使用反射方式问超出访问范围的构造方法、属性、方法时,会引发java.lang.lllegalAccessException异常。若要禁止Java语言访问检查强行访问,需要设置相关实例为可访问,语法如下:
  c3.setAccessible(true); // 通过构造方法 / 属性 / 方法实例调用
当然此种做法会破坏封装,需谨慎使用。

1.2.3 访问类的属性

  使用Field实例可以对属性进行取值或赋值操作,常用方法如下:

方法 说明
xxx getXxx(Object obj) xxx表示8种基本数据类型之一,如int getInt(Object obj).obj 为该属性所在类的实例。假设 instance 表示A类的实例,field表示A类中的属性a,则field.getInt(instance)表示以 int 类型返回instance中属性a的值。若Field实例表示的是一个静态属性,则obj可以设置为null
Object get(Object obj) 以Object类型返回obj中相关属性的值
void setXxx(Object obj, xxx val) 将obj中相关属性的值设置为val。xxx为8种基本数据类型之一
void set(Object obj,Object val) 将obj中相关属性的值设置为val
void setAccessible(boolean flag) 对相关属性设置访问权限。设置为true可以禁止Java语言访问检查
package shili;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author:T369李喆
 * @Date:2024/6/26 19:54
 **/
public class AccessPersonMethods {
    public static void main(String[] args) {
        // 本例演示通过反射操作Person的方法
        try {
            // 通过反射加载Person类
            Class clz = Class.forName("shili.Person");

            // 根据方法名和参数列表获取static final int getAge()方法,没有参数可以不写或用null表示
            Method getAge = clz.getDeclaredMethod("getAge", null);
            // getAge方法为default(package),这里已超出其访问范围,不能直接访问
            // 通过setAccessable方法,设定为可以访问
            getAge.setAccessible(true);
            // 调用getAge方法并传参,没有参数可以不写或用null表示
            // getAge方法为静态方法,调用时可以不指定具体Person实例
            Object returnAge = getAge.invoke(null, null);
            System.out.println("年龄是:" + returnAge);

            Object person = clz.newInstance(); // 创建Person实例

            // 根据方法名和参数列表获取private void silentMethod()方法,没有参数可以不写或用null表示
            Method silentMethod = clz.getDeclaredMethod("silentMethod", null);
            // silentMethod方法为private,这里已超出其访问范围,不能直接访问
            // 通过setAccessable方法,设定为可以访问
            silentMethod.setAccessible(true);
            // 调用silentMethod方法并传参,没有参数可以不写或用null表示
            silentMethod.invoke(person, null);

            // 根据方法名和参数列表获取public void setName(String)方法
            Method setName = clz.getDeclaredMethod("setName", String.class);
            // setName方法为public,这里可以直接访问
            // 调用setName方法并传参
            setName.invoke(person, "New Person");
            // 验证一下结果,调用public String getName()方法得到name的值
            Object returnName = clz.getDeclaredMethod("getName").invoke(person);
            System.out.println("刚才设定的name是:" + returnName);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SecurityException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}


知识扩展
  在java.lang.reflect包下还提供了一个Array类,Array实例可以代表一个数组。程序可以使用Array类来动态地创建数组及操作数组元素等。Array类的部分常用方法

方法 说明
static Object newInstance(Class componentType,int length) 创建元素类型为componentType,长度为length的数组
static Object newInstance(Class componentType,int…dimensions) 创建多维数组,元素类型是componentType,维数是dimensions.length,dimensions中的每一个元素决定每一维的长度
static int getLength(Object arr,int index) 返回数组arr的长度
static xxx getXxx(Object arr,int index) xxx表示8种基本数据类型之一,以基本数据类型返回数组arr中索引为index的元素
static Object get(Object arr,int index) 以Object类型返回数组arr中索引为index的元素
static void setXxx(Object arr,int index,xxx val) 将val赋值给数组arr中索引为index的元素,xxx为8种基本数据类型之一
static void set(Object arr,int index,Object val) 将val赋值给数组arr中索引为index的元素

注意
  使用方式虽然会很大程度上提高代码的灵活性,但是不能滥用反射,因为通过反射创建和访问实例时性能要稍微低一些,且反射可能会破坏封装。实际上,只有当程序需要动态创建类的实例时才会考虑使用反射。
  通常,在通用性较广的框架、基础平台中反射技术会有大量的运用。实现的数据动态处理能力,在很多Java框架中都有更完善、更强大的实现,如将请求数据自动封装到JavaBean、对JavaBean中的属性值进行格式效验、将SQL查询结果封装到JavaBean、将JavaBean属性值赋值给SQL查询条件等,都需要程序能够动态创建和操作Java实例,这就必须使用反射技术。理解Java反射机制,对以后学习框架技术会有很大的帮助。

你可能感兴趣的:(Java开发之框架基础技术,intellij-idea,python,java)