20 - Java 反射

类加载

Java类加载机制是指JVM把类的字节码文件加载到内存,并进行链接和初始化的过程。这个过程主要分为加载、验证、准备、解析和初始化五个阶段。
Java 类加载机制是 Java 虚拟机(JVM)的重要组成部分,负责在运行时动态加载和链接类。这一机制使得 Java 程序可以灵活地在各种环境中运行,并且支持诸如热替换(HotSwap)和反射(Reflection)等高级特性。

1. 类加载器(ClassLoader)

类加载器是实际执行类加载任务的实体。JVM 提供了三种主要的类加载器:
启动类加载器(Bootstrap ClassLoader)‌
  • 负责加载 Java 核心类库(如 rt.jar)。
  • 不是由 Java 代码实现,而是由底层 C/C++ 代码实现。
  • 是所有其他类加载器的父加载器。
扩展类加载器(Extension ClassLoader)‌
  • 负责加载扩展类库(如 lib/ext 目录中的 JAR 包)。
  • 父加载器是启动类加载器。
应用程序类加载器(Application ClassLoader)‌
  • 负责加载应用程序类路径(classpath)上的类。
  • 父加载器是扩展类加载器。
  • 也称为系统类加载器。

2. 双亲委派模型(Parent Delegation Model)

类加载器之间通过双亲委派模型进行协作。具体流程如下:
  1. 当一个类加载器接收到类加载请求时,它首先将这个请求委派给它的父加载器。
  2. 父加载器尝试加载这个类,如果找不到,再交给子加载器去加载。
  3. 最终,如果所有的父加载器都无法加载这个类,最初的类加载器才会尝试自己加载。
这种模型保证了 Java 核心库的类型安全,防止了用户自定义的类替代核心类库中的类。

3. 类加载过程

类加载分为以下几个阶段:
1. 加载(Loading)‌

  • 通过类名获取类的二进制字节流。
  • 将字节流转化为方法区中的运行时数据结构。
  • 在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口。
2. 链接(Linking)‌

 验证(Verification)‌

        目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

        包括:文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证。

        可以考虑使用-Xverify:none 参数来关闭大部分的类验证措施,缩短虛拟机类加载的时间。

‌准备(Preparation)

        为静态变量分配内存并设为默认初始值(如 0null),JVM 会在该阶段对静态变量,分配内存井初始化(对应数据类型的默认初始值如0、OL、null false 等),这些变量所使用的内存都将在方法区中进行分配。

  • ‌解析(Resolution)‌
    •         虛拟机将常量池内的符号引用替换为直接引用的过程。
3.初始化(Initialization)‌

        执行类的初始化代码,包括静态变量的赋值和静态代码块的执行。

        初始化顺序按照代码中出现的顺序进行。

4. 自定义类加载器

除了 JVM 提供的类加载器,用户还可以自定义类加载器,以满足特定需求(如从网络或数据库中加载类)。自定义类加载器需要继承  java.lang.ClassLoader 类,并重写  findClass 方法。
以下是一个简单的Java类加载器示例代码:
public class MyClassLoader extends ClassLoader {
    private String classPath;
 
    public MyClassLoader(String classPath) {
        this.classPath = classPath;
    }
 
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] classData = loadClassData(name);
            return defineClass(name, classData, 0, classData.length);
        } catch (IOException e) {
            throw new ClassNotFoundException("Class file not found: " + name, e);
        }
    }
 
    private byte[] loadClassData(String className) throws IOException {
        // 这里简化处理,实际应读取磁盘上的.class文件
        String path = classPath + "/" + className.replace('.', '/') + ".class";
        try (InputStream is = new FileInputStream(path)) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            int bufferSize = 4096;
            byte[] buffer = new byte[bufferSize];
            int length;
            while ((length = is.read(buffer)) != -1) {
                baos.write(buffer, 0, length);
            }
            return baos.toByteArray();
        }
    }
 
    public static void main(String[] args) throws Exception {
        MyClassLoader classLoader = new MyClassLoader("path/to/classes");
        Class<?> clazz = classLoader.loadClass("com.example.MyClass");
        Object instance = clazz.newInstance();
        System.out.println(instance);
    }
}
这个示例中,MyClassLoader 继承了 ClassLoader 类,并覆盖了 findClass 方法来加载类。 loadClassData 方法用于从磁盘读取类文件的字节码。 main 方法中展示了如何使用自定义类加载器加载类并创建实例。这个例子旨在展示如何扩展类加载器,并在实际应用中根据需要加载类。

5. 类的热替换与热部署

类加载机制还支持类的热替换(HotSwap),即在不重启 JVM 的情况下替换类的实现。这一特性在开发和调试过程中非常有用。对于某些应用,热部署(Hot Deployment)也是基于类加载机制实现的,使得应用可以在运行时更新。

总结

Java 类加载机制通过类加载器和双亲委派模型保证了类的唯一性和安全性,通过加载、链接和初始化三个阶段完成类的加载过程。理解这一机制对于深入掌握 Java 语言的动态特性以及进行高级开发、调试和优化具有重要意义。

类加载机制的具体示例

将通过一个简单的 Java 程序和自定义类加载器的例子来展示 Java 类加载机制的具体应用。
示例 1: 默认的类加载机制
首先,我们来看一个基本的 Java 程序,它演示了 JVM 如何使用默认的类加载机制加载类。
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}
当你运行这个程序时,JVM 会按照以下步骤加载  HelloWorld 类:
  1. 启动类加载器‌尝试加载 HelloWorld 类(实际上,它会尝试加载 java.lang.* 等核心类库)。
  2. 因为 HelloWorld 不是核心类库的一部分,所以启动类加载器将加载请求委派给扩展类加载器‌。
  3. 扩展类加载器同样无法找到 HelloWorld 类,因此它将加载请求委派给应用程序类加载器‌。
  4. 应用程序类加载器搜索类路径(classpath),找到并加载 HelloWorld.class 文件。
  5. JVM 执行 HelloWorld 类的 main 方法。

示例 2: 自定义类加载器

现在,来看一个自定义类加载器的例子。这个类加载器将从文件系统中加载一个类。
首先,创建一个简单的类  MyClass,并将其编译成字节码文件  MyClass.class
// MyClass.java
public class MyClass {
    public void sayHello() {
        System.out.println("Hello from MyClass!");
    }
}

然后,编写一个自定义类加载器 FileClassLoader 来加载 MyClass。

import java.io.*;

public class FileClassLoader extends ClassLoader {
    private String classPath;

    public FileClassLoader(String classPath) {
        this.classPath = classPath;
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] classData = loadClassData(name);
        if (classData == null) {
            throw new ClassNotFoundException();
        } else {
            return defineClass(name, classData, 0, classData.length);
        }
    }

    private byte[] loadClassData(String name) {
        try {
            String fileName = classPath + name.replace('.', '/') + ".class";
            InputStream inputStream = new FileInputStream(fileName);
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            int nextValue = 0;
            while ((nextValue = inputStream.read()) != -1) {
                byteArrayOutputStream.write(nextValue);
            }
            return byteArrayOutputStream.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
}

最后,编写一个测试程序来使用自定义类加载器加载并运行 MyClass。

public class ClassLoaderTest {
    public static void main(String[] args) {
        FileClassLoader fileClassLoader = new FileClassLoader("path/to/classes/");
        try {
            Class<?> clazz = fileClassLoader.loadClass("MyClass");
            Object instance = clazz.newInstance();
            clazz.getMethod("sayHello").invoke(instance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
在这个例子中, FileClassLoader 类重写了  findClass 方法,该方法从指定的文件路径中读取类的字节码数据,并使用  defineClass 方法将其转换为  Class 对象。
测试程序  ClassLoaderTest 创建了一个  FileClassLoader 实例,并使用它加载  MyClass 类。然后,它使用反射机制创建  MyClass 的实例并调用其  sayHello 方法。
请注意,你需要将  MyClass.class 文件放在与  FileClassLoader 中指定的  classPath 相对应的文件路径下,以便自定义类加载器能够找到并加载它。

反射机制

基本介绍

反射机制:就是把Java类中的各个成分映射成一个个的Java对象;即在运行状态中,对于任意一个类,都能够知道这个类所有的属性和方法,对于任意一个对象,都能调用它的任意一个方法和属性,这种动态获取信息及动态调用对象方法的功能叫Java的反射机制。
反射来源:加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以,形象的称之为:反射。

反射相关类

  • java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象。
  • java.lang.reflect.Method: 代表该类的方法。
  • java.lang.reflect.Field:代表类的成员变量。
  • java.lang.reflect.Constructor:代表类的构造方法。

Class 类

基本介绍

  • Class也是类,因此也继承Object类。
  • Class类对象不是new出来的,而是系统创建的。
  • 类的Class类对象,在内存中只有一份,且只加载一次。
  • 每个类的实例都会记录它属于哪个Class实例生成。
  • 通过Class可以完整地得到一个类的完整结构,通过一系列API。
  • Class对象是存放在堆的。
  • 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码,变量名,方法名,访问权限等等)。

Class类对象获取

1.前提:已知一个类的全类名,且该类在类路径下,可通过 Class 类的静态方法 forName() 获取。

        应用场景:多用于配置文件,读取类全路径,加载类 。
Class cls1 = Class.forName("java.lang.Cat” )

2.前提:若已知具体的类,通过类的 Class 获取,该方式最为安全可靠,程序性能最高。

        应用场景:多用于参数传递,比如通过反射得到对应构造器对象 。
Class cls2 = Cat.class

3.前提:已知某个类的实例,调用该实例的 getClass() 方法获取 Class 对象。

        应用场景:通过创建好的对象,获取 Class 对象 。
Class class = 对象.getClass();

4.其他方式(类加载器) 。

ClassLoader cl = 对象.getClass().getClassLoader();
Class class4 = cl.loadClass(“类的全类名");

5.基本数据(int, char boolean,float,double.byte.long,short)

Class cls = 基本数据类型.class;

6.基本数据类型对应的包装类,可以通过.type 得到Class类对象。

Class cls = 包装类.TYPE;
public class GetClass_ {
    public static void main(String[] args) throws ClassNotFoundException {

        //1. Class.forName
        String classAllPath = "com.ben.Car"; //通过读取配置文件获取
        Class<?> cls1 = Class.forName(classAllPath);
        System.out.println(cls1);

        //2. 类名.class , 应用场景: 用于参数传递
        Class cls2 = Car.class;
        System.out.println(cls2);

        //3. 对象.getClass(), 应用场景,有对象实例
        Car car = new Car();
        Class cls3 = car.getClass();
        System.out.println(cls3);

        //4. 通过类加载器【4种】来获取到类的Class对象
        //(1)先得到类加载器 car
        ClassLoader classLoader = car.getClass().getClassLoader();
        //(2)通过类加载器得到Class对象
        Class cls4 = classLoader.loadClass(classAllPath);
        System.out.println(cls4);

        //cls1 , cls2 , cls3 , cls4 其实是同一个对象
        System.out.println(cls1.hashCode());
        System.out.println(cls2.hashCode());
        System.out.println(cls3.hashCode());
        System.out.println(cls4.hashCode());

        //5. 基本数据(int, char,boolean,float,double,byte,long,short) 按如下方式得到Class类对象
        Class<Integer> integerClass = int.class;
        Class<Character> characterClass = char.class;
        Class<Boolean> booleanClass = boolean.class;
        System.out.println(integerClass);//int

        //6. 基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
        Class<Integer> type1 = Integer.TYPE;
        Class<Character> type2 = Character.TYPE; //其它包装类BOOLEAN, DOUBLE, LONG,BYTE等待
        System.out.println(type1);

        System.out.println(integerClass.hashCode());//?
        System.out.println(type1.hashCode());//?
    }
}

有Class对象的类型

  • 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  • interface :接口
  • 数组
  • enum:枚举
  • annotation :注解
  • 基本数据类型
  • void

反射获取类的结构信息

第一组 Class 类方法

在Java反射中, Class 类是一个非常重要的类,它提供了关于类和接口的信息,并且包含了用于获取类的构造器、方法、字段等成员的方法。
以下是 Class类中与反射相关的一些重要方法:
获取构造器
  • getConstructor(Class... parameterTypes):返回此 Class 对象所表示的类的公共构造器,该构造器具有与指定参数类型相匹配的形参。
  • getDeclaredConstructor(Class... parameterTypes):返回此 Class 对象表示的类声明的构造器,该构造器具有与指定参数类型相匹配的形参。
  • getConstructors():返回一个包含此 Class 对象所表示的类的所有公共构造器的数组。
  • getDeclaredConstructors():返回反映此 Class 对象表示的类声明的所有构造器的 Constructor 对象的数组。
获取方法
  • getMethod(String name, Class... parameterTypes):返回此 Class 对象所表示的类或接口的特定公共成员方法,该方法由名称和参数类型确定。
  • getDeclaredMethod(String name, Class... parameterTypes):返回此 Class 对象表示的类或接口声明的特定方法,该方法由名称和参数类型确定。
  • getMethods():返回一个包含此 Class 对象所表示的类的所有公共成员方法的数组,包括其继承的公共方法。
  • getDeclaredMethods():返回此 Class 对象表示的类或接口声明的所有方法的数组,包括私有、保护、默认(包)访问和公共方法,但不包括继承的方法。
获取字段
  • getField(String name):返回此 Class 对象所表示的类或接口的指定的公共成员字段。
  • getDeclaredField(String name):返回此 Class 对象表示的类或接口声明的指定字段。
  • getFields():返回一个包含此 Class 对象所表示的类的所有公共成员字段的数组。
  • getDeclaredFields():返回此 Class 对象表示的类或接口声明的所有字段的数组。
其他重要方法
  • getName():以 String 的形式返回此 Class 对象所表示的实体(类、接口、数组类、基本类型或 void)的名称。
  • getSuperclass():返回表示此 Class 所表示的实体的超类的 Class。如果此 Class 表示 Object 类、一个接口、一个基本类型或 void,则返回 null
  • getInterfaces():确定此对象所表示的类或接口实现的接口。
  • isArray():判定此 Class 对象是否表示一个数组类。
  • isPrimitive():判定指定的 Class 对象是否表示一个基本类型。
  • isAnnotation():如果此 Class 对象表示一个注解类型则返回 true
  • newInstance():创建此 Class 对象所表示的类的一个新实例。这个方法已经被弃用,因为它在类型检查方面存在潜在的安全问题。应该使用 Constructor.newInstance() 方法替代。
使用示例
以下是一个简单的示例,展示了如何使用 Class类的一些方法来获取类的信息:
import java.lang.reflect.Method;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;

public class ReflectionExample {
    private int number;
    private String text;

    public ReflectionExample() {}

    public ReflectionExample(int number, String text) {
        this.number = number;
        this.text = text;
    }

    public void printInfo() {
        System.out.println("Number: " + number + ", Text: " + text);
    }

    public static void main(String[] args) {
        try {
            // 获取ReflectionExample类的Class对象
            Class<?> clazz = ReflectionExample.class;

            // 获取类的名称
            System.out.println("Class Name: " + clazz.getName());

            // 获取所有公共方法
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                System.out.println("Method: " + method.getName());
            }

            // 获取所有字段
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                System.out.println("Field: " + field.getName());
            }

            // 获取所有构造器
            Constructor<?>[] constructors = clazz.getDeclaredConstructors();
            for (Constructor<?> constructor : constructors) {
                System.out.println("Constructor: " + constructor);
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,使用了 Class 类的多个方法来获取 ReflectionExample 类的信息,包括类的名称、所有公共方法、所有字段和所有构造器。这些信息在运行时是非常有用的,特别是当你需要动态地处理类时。

第二组 Field 类方法

Java中, Field类是反射API的一部分,它提供了关于类或接口的字段(也称为成员变量)的信息以及动态访问这些字段的能力。 Field类位于 java.lang.reflect包中。通过 Field类,你可以在运行时获取字段的类型、名称、修饰符等元数据,并且可以读取或设置字段的值。
以下是一些 Field类中的常用方法:
  • getName()‌:返回字段的名称,例如"age"或"name"。
  • getType()‌:返回一个Class对象,该对象表示字段的类型,例如int.class、String.class等。
  • getModifiers()‌:以 int 形式返回修饰符 public 是 1,private 是 2 ,protected 是 4,static 是8,final 是 16 。你可以使用Modifier类来解析这些修饰符。
  • isAccessible()‌:检查字段是否可以被当前线程访问。对于私有字段,这个方法通常会返回false,除非你之前调用了setAccessible(true)方法。
  • setAccessible(boolean flag)‌:设置字段的访问权限。如果flag为true,则允许反射代码访问私有和受保护的字段。这通常用于绕过Java的访问控制检查。
  • get(Object obj)‌:从指定对象中获取字段的值。obj参数应该是包含该字段的实例(对于静态字段,可以是null)。
  • set(Object obj, Object value)‌:将字段的值设置为指定值。obj参数应该是包含该字段的实例(对于静态字段,可以是null),value参数是要设置的新值。字段的类型必须与value参数的类型兼容。
  • getDeclaredField(String name)‌:这不是Field类的方法,而是Class类的方法。它返回类中声明的指定名称的字段(不考虑修饰符),如果没有找到该字段,则抛出NoSuchFieldException。
下面是一个使用 Field类的简单示例:
import java.lang.reflect.Field;

public class FieldExample {
    private String name = "John Doe";
    public int age = 30;

    public static void main(String[] args) {
        try {
            // 获取FieldExample类的Class对象
            Class<?> clazz = FieldExample.class;

            // 获取name字段的Field对象
            Field nameField = clazz.getDeclaredField("name");

            // 设置name字段为可访问
            nameField.setAccessible(true);

            // 创建FieldExample类的实例
            FieldExample example = new FieldExample();

            // 获取name字段的值
            String nameValue = (String) nameField.get(example);
            System.out.println("Name: " + nameValue);

            // 修改name字段的值
            nameField.set(example, "Jane Doe");
            System.out.println("Updated Name: " + example.name);

            // 获取age字段的Field对象并打印其值
            Field ageField = clazz.getField("age");
            int ageValue = (int) ageField.get(example);
            System.out.println("Age: " + ageValue);

        } catch (NoSuchFieldException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们使用了反射来获取和修改 FieldExample 类的字段。注意,对于私有字段 name ,我们调用了 setAccessible(true) 方法来绕过访问控制。对于公共字段 age ,我们直接使用了 getField 方法来获取其 Field 对象。

第三组 Method类 方法

在Java中, Method类是反射API的一个重要组成部分,它代表类中的一个方法。通过 Method对象,你可以在运行时获取关于方法的信息(如方法名、返回类型、参数类型等),并且可以动态地调用该方法。
以下是 Method类中的一些常用方法:
  • getName()‌:返回方法的名称,例如"myMethod"。
  • getReturnType()‌:返回一个Class对象,该对象表示方法的返回类型。
  • getParameterTypes()‌:返回一个Class数组,表示方法的参数类型。
  • getParameterCount()‌:返回方法的参数个数。
  • getModifiers()‌:以 int 形式返回修饰符 public 是 1,private 是 2 ,protected 是 4,static 是8,final 是 16。你可以使用Modifier类来解析这些修饰符。
  • isAccessible()‌:检查方法是否可以被当前线程访问。对于私有方法,这个方法通常会返回false,除非你之前调用了setAccessible(true)方法。
  • ‌setAccessible(boolean flag)‌:设置方法的访问权限。如果flag为true,则允许反射代码访问私有和受保护的方法。这通常用于绕过Java的访问控制检查。
  • ‌invoke(Object obj, Object... args)‌:在指定对象上调用该方法。obj参数应该是包含该方法的实例(对于静态方法,可以是null),args参数是传递给方法的参数值。方法的返回类型将被自动包装在Object中返回。
  • ‌getDeclaredMethod(String name, Class... parameterTypes)‌:这不是Method类的方法,而是Class类的方法。它返回类中声明的指定名称和参数类型的方法(不考虑修饰符),如果没有找到该方法,则抛出NoSuchMethodException。
  • ‌getMethod(String name, Class... parameterTypes)‌:这也是Class类的方法。它返回类中声明的可被当前线程访问的指定名称和参数类型的公共方法。如果没有找到该方法,或者方法不是公共的,则抛出NoSuchMethodException。
下面是一个使用Method类的简单示例:
import java.lang.reflect.Method;

public class MethodExample {
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }

    private int add(int a, int b) {
        return a + b;
    }

    public static void main(String[] args) {
        try {
            // 获取MethodExample类的Class对象
            Class<?> clazz = MethodExample.class;

            // 获取sayHello方法的Method对象
            Method sayHelloMethod = clazz.getMethod("sayHello", String.class);

            // 创建MethodExample类的实例
            MethodExample example = new MethodExample();

            // 调用sayHello方法
            sayHelloMethod.invoke(example, "World");

            // 获取add方法的Method对象(私有方法)
            Method addMethod = clazz.getDeclaredMethod("add", int.class, int.class);

            // 设置add方法为可访问
            addMethod.setAccessible(true);

            // 调用add方法并获取返回值
            int result = (int) addMethod.invoke(example, 5, 3);
            System.out.println("5 + 3 = " + result);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,我们使用了反射来获取和调用 MethodExample 类的方法。注意,对于私有方法 add ,我们调用了 setAccessible(true) 方法来绕过访问控制。对于公共方法 sayHello ,我们直接使用了getMethod 方法来获取其 Method 对象,并通过 invoke 方法调用了它。

第四组 Constructor 类方法

在Java反射中, Constructor 类代表类的构造器,它允许你在运行时动态地创建类的实例。 Constructor 类提供了多种方法来获取构造器的信息以及创建类的实例。
以下是一些 Constructor 类中常用的方法:
获取构造器信息
  • getName():返回构造器的名称。由于构造器的名称总是与类名相同,这个方法通常不是特别有用。
  • getParameterTypes():返回一个 Class 对象数组,表示构造器的参数类型。
  • getParameterCount():返回构造器接受的参数个数。
  • getModifiers():返回构造器的修饰符,可以使用 Modifier 类来解析这些修饰符。
  • isAccessible():检查构造器是否可以被当前线程访问。对于私有构造器,这个方法通常会返回 false,除非你之前调用了setAccessible(true)方法。
创建实例
  • newInstance(Object... initargs):使用此 Constructor 对象表示的构造器,使用指定的初始化参数来创建和初始化新实例。这个方法在Java 9及以后的版本中被标记为过时(deprecated),因为它可能会抛出各种未检查的异常。建议使用 newInstanceWithArgs 方法或其他更安全的替代方法。
  • newInstanceWithArgs(Object... args):这是Java 9引入的一个更安全的方法来创建实例,它会将任何由构造器抛出的异常封装在InvocationTargetException中。
设置访问权限
  • setAccessible(boolean flag):设置构造器的访问权限。如果 flag true,则允许反射代码访问私有和受保护的构造器。这通常用于绕过 Java 的访问控制检查。
示例代码
以下是一个使用 Constructor 类的示例代码,展示了如何获取构造器信息以及如何使用反射来创建类的实例:
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ReflectionConstructorExample {
    private int number;
    private String text;

    public ReflectionConstructorExample() {}

    public ReflectionConstructorExample(int number, String text) {
        this.number = number;
        this.text = text;
    }

    @Override
    public String toString() {
        return "ReflectionConstructorExample{number=" + number + ", text='" + text + "'}";
    }

    public static void main(String[] args) {
        try {
            // 获取ReflectionConstructorExample类的Class对象
            Class<?> clazz = ReflectionConstructorExample.class;

            // 获取无参构造器
            Constructor<?> noArgsConstructor = clazz.getConstructor();
            // 使用无参构造器创建实例
            ReflectionConstructorExample instance1 = (ReflectionConstructorExample) noArgsConstructor.newInstance();
            System.out.println("Instance created with no-args constructor: " + instance1);

            // 获取带参构造器
            Constructor<?> argsConstructor = clazz.getConstructor(int.class, String.class);
            // 使用带参构造器创建实例
            ReflectionConstructorExample instance2 = (ReflectionConstructorExample) argsConstructor.newInstance(42, "Hello, World!");
            System.out.println("Instance created with args constructor: " + instance2);

            // 如果需要访问私有构造器,可以使用setAccessible(true)
            // ...

        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

在这个示例中,首先获取了 ReflectionConstructorExample 类的 Class 对象,然后分别获取了无参构造器和带参构造器的 Constructor 对象。接着,我们使用 newInstance方法创建了类的实例,并打印了它们的信息。注意,在实际应用中,应该更倾向于使用 newInstanceWithArgs 方法来创建实例,因为它更安全。
另外,如果需要访问私有构造器,可以使用 getDeclaredConstructor 方法来获取构造器的 Constructor对象,然后调用 setAccessible(true) 方法来设置访问权限。不过,在大多数情况下,直接访问私有构造器并不是一个好的实践,因为它违反了封装原则。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿垂爱挖掘

谢谢大家的鼓励!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值