本篇深入分析并理解 Java Class 对象,这对后续深入理解反射技术非常重要。🚀本篇章代码 Demo

# Class 对象深入理解

# RTTI 概念以及 Class 对象作用

  • 我们先来了解一下 RTTI 概念, RTTI 全称是 Run-Time Type Identification 是指运行时类型识别。
  • 对于这个词一直是 C++ 中的概念,至于会在 Java 中出现 RTTI 的说法来源于 《Thinking in Java》 一书中。
  • 其作用就是在运行时识别一个对象的类型和类的信息,这里分为两种:
    • 第一种:传统的 RTTI 它假设我们在编译期已知道了所有类型 (在没有反射机制创建和使用类对象时,一般都是编译期确定其类型的,如 new 对象时该类必须已经定义好)。
    • 第二种:另外一种是反射机制,它允许我们在运行时发现和使用类型信息,在 Java 中用来表示运行时类型信息的对应类就是 Class 类, Class 类也是一个实实在在的类,它存在于 JDK 中的 java.lang 包下。
Class.java | Class部分源码
public final class Class<T> implements java.io.Serializable, GenericDeclaration, Type, AnnotatedElement, TypeDescriptor.OfField<Class<?>>, Constable {
    private static final int ANNOTATION = 0x00002000;
    private static final int ENUM = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;
    private static native void registerNatives();
    static {
        registerNatives();
    }
    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader, Class<?> arrayComponentType) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
        componentType = arrayComponentType;
    }
}
  • Class 类被创建后对象就是 Class 对象,注意: Class 对象表示的是自己手动编写的类的类型信息。
  • 比如创建一个 Student 类那么 JVM 就会创建一个 Student 对应的 Class 类的类对象,该 Class 对象保存了 Student 类相关的类型信息。
  • 实际上在 Java 中每个类都有一个 Class 对象,当我们编写并编译一个新创建的类时就会产生一个对应的 Class 对象并且这个 Class 对象会被保存在同名的 .class 文件里。
  • 编译后的字节码文件保存的就是 Class 对象信息,为什么需要一个这样的 Class 对象呢?
  • 每当我们 new 一个新对象或者引用静态成员变量时, Java 虚拟机也就是 JVM 中的类加载器子系统会将对应的 Class 对象加载到 JVM 中,然后 JVM 会再根据这个类型信息相关的 Class 对象创建我们需要的对象实例或者提供静态变量的引用值。
  • 特别需要注意的是手动编写的每个 class 类无论创建多少个实例对象,在 JVM 中都只有一个 Class 对象,即在内存中每个类有且只有一个相对应的 Class 对象。
    piGnYa6.jpg
  • 我们可以得出以下几点信息:
    • Class 类也是类的一种与 class 关键字是不一样的。
    • 手动编写的类编译后将会产生一个 Class 对象,表示的是创建的类的类型信息,且这个 Class 对象保存在同名的 .class 文件中也就是字节码文件。
    • 通过关键字 class 标识的类,在内存中有且只有一个与之对应的 Class 对象来描述其类型信息,无论创建多少实例对象其内部指向的都是同一个 Class 对象。
    • Class 类只存私有构造函数,因此对应 Class 对象只能由 JVM 创建和加载。
    • Class 类的对象作用是运行时提供获得某个对象的类型信息,这点对于反射技术是很重要的。

# Class 对象的加载和获取方式

# Class 对象的加载

  • Class 对象是由 JVM 来加载的,那么其加载的时机是什么时候呢?实际上所有的类都是在对其第一次使用动态加载到 JVM 的。
  • 当程序创建第一个对类的静态成员引用时就会加载这个被使用的类,实际上加载的就是这个类的字节码文件,注意:使用 new 操作符创建的类的新实例对象也会被当作对类的静态成员的引用 (构造函数也是类的静态方法)。
  • 由此看来 Java 程序在它们开始运行之前并非被完全加载到内存的,其各个部分是按需加载的,所以在使用该类时类加载器首先会检查这个类的 Class 对象是否已经被加载 (类的实例对象创建时依据 Class 对象中类型信息完成的)。
  • 如果还没有加载,默认的类加载器就会先根据类名查找 .class 文件,编译后 Class 对象被保存在同名的 .class 文件中,在这个类的字节码文件被加载时,必须接受相关验证,确保其没有被破坏且不包含不良 Java 代码。
  • 这就是 Java 的安全机制检测,在检测完后完全没有问题才会被动态加载到内存中,此时相当于 Class 对象也就被载入到了内存中,同时也就可以被用来创建这个类的所有实例对象了。

Class 对象被加载的时机问题代码示例如下:

SweetShop.java
package top.rem.rain.demo;
class Candy {
    static {
        System.out.println("Loading Candy");
    }
}
class Gum {
    static {
        System.out.println("Loading Gum");
    }
}
class Cookie {
    static {
        System.out.println("Loading Cookie");
    }
}
/**
 * @Author: LightRain
 * @Description: 类加载时机代码示例
 * @DateTime: 2023-11-12 21:22
 * @Version:1.0
 **/
public class SweetShop {
    public static void print(Object obj) {
        System.out.println(obj);
    }
    public static void main(String[] args) {
        print("inside main");
        new Candy();
        print("After creating Candy");
        try {
            Class.forName(Gum.class.getName());
        } catch (ClassNotFoundException e) {
            print("Couldn't find Gum");
        }
        print("After Class.forName(\"top.rem.rain.demo.Gum\")");
        new Cookie();
        print("After creating Cookie");
    }
    /*
    结果如下:
    inside main
    Loading Candy
    After creating Candy
    Loading Gum
    After Class.forName ("top.rem.rain.demo.Gum")
    Loading Cookie
    After creating Cookie
     */
}
  • 上述代码中 CandyGumCookie 每个类中都存在一个 static 语句,这个语句会在类第一次被加载时执行, static 的作用就是告诉我们该类在什么时候被加载。
  • 从结果来看 new 一个 Candy 对象和 Cookie 构造函数将会被调用,属于静态方法的引用 Candy 类的 Class 对象和 CookieClass 对象肯定会被加载。
  • 有意思的是 Class.forName("top.rem.rain.demo.Gum") 其中 forName() 方法是 Class 类中的一个 static 成员方法,我们可以获取到 Gum 类对应的 Class 对象引用。
  • 从打印结果上来看调用 forName() 方法将会导致 Gum 类被加载,值得注意的是 Gum 类从来没有被加载过。

# Class.forName 方法

  • 上述的案例我们知道了 Class.forName() 方法的调用将会返回一个对应类的 Class 对象,因此如果我们想获取一个类的运行时类型信息并加以使用时,可以调用 Class。forName() 方法来获取 Class 对象的引用。
  • 这样做的好处就是无需通过持有该类的实例对象引用而去获取 Class 对象,下面是第二种获取方式:通过一个实例对象获取一个类的 Class 对象,其中的 getClass() 是从顶级类 Object 继承而来的,它将返回表示该对象的实际类型的 Class 对象引用。
GetClassObject.java
package top.rem.rain.demo;
class Gum2 {
    static {
        System.out.println("Loading Gum2");
    }
}
/**
 * @Author: LightRain
 * @Description: getClassObject: 获取类对象
 * @DateTime: 2023-11-12 21:52
 * @Version:1.0
 **/
public class GetClassObject {
    public static void main(String[] args) {
        try {
            // 通过 Class.forName 获取 Gum2 类的 Class 对象
            Class clazz = Class.forName(Gum2.class.getName());
            System.out.println("forName clazz:" + clazz.getName());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        // 通过实例对象获取 Gum2 的 Class 对象
        Gum2 gum = new Gum2();
        Class cla = gum.getClass();
        System.out.println("new cla:" + cla.getName());
    }
}

注意在调用 forName 方法时需要捕获一个 ClassNotFoundException 的异常,因为 forName 方法在编译器是无法检测到其传递字符串对应的类是否存在的,只能在程序运行时进行检查,如果不存在就会抛出 ClassNotFoundException 异常。

# Class 字面常量

  • Java 中另一种获取 Class 对象的引用那就是 Class 字面常量方式,如下:
Gum.java
// 字面常量的方式获取 Class 对象
Class clazz=Gum.class;
  • 这种方式获取相对前面两种方法更加简单和安全,因为它在编译器就会受到编译器的检查同时由于不需要调用 forName 方法效率也会更高,因为通过字面量的方法获取 Class 对象的引用不会自动初始化该类。
  • 有趣的是字面常量获取的 Class 对象引用方式不仅可以应用于普通的类还可以应用于接口、数组、基本数据类型、这点在反射技术应用传递参数时就会很有帮助。
  • 由于基本数据类型还有对应的基本包装类型,其包装类型有一个标准的 TYPE 字段而这个 TYPE 就是一个引用,指向基本数据类型的 Class 对象,其等价转换如下。
  • 一般情况下更倾向使用 .class 的形式,这样可以保持与普通类的形式统一。
boolean.class =Boolean.TYPE;
        char.class =Character.TYPE;
        byte.class =Byte.TYPE;
        short.class =Short.TYPE;
        int.class =Integer.TYPE;
        long.class =Long.TYPE;
        float.class =Float.TYPE;
        double.class =Double.TYPE;
        void.class =Void.TYPE;

# Class 字面常量类加载分析

  • 使用字面常量的方式获取 Class 对象的引用不会触发类的初始化,在这里我们可以简单了解一下字面常量类加载的过程,看下图:
    piGHWl9.jpg
  • Loading(加载) 类加载过程的一个阶段是通过一个类的完全限定名查找此类字节码文件,并利用字节码文件创建一个 Class 对象。
  • Linking(链接) 验证字节码的安全性和完整性是有必要的,准备阶段正式为静态区域分配储存空间,注意此时只是分配静态成员变量的储存空间,不包含实例成员变量。
  • Initialization(初始化) 类加载的最后阶段若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量。

可见我们在获取字面常量的 Class 引用时触发的一个是加载阶段,因此在这个阶段 Class 对象已被创建完成,获取其引用并不困难,从而无需触发类的最后阶段初始化。

ClassInitialization.java
import java.util.Random;
class Inevitable {
    /**
     * 编译期静态常量
     */
    static final int STATIC_FINAL = 47;
    /**
     * 非编期静态常量
     */
    static final int STATIC_FINAL2 = ClassInitialization.rand.nextInt(1000);
    static {
        System.out.println("Initializing Inevitable");
    }
}
class Inevitable2 {
    /**
     * 静态成员变量
     */
    static int staticNonFinal = 147;
    static {
        System.out.println("Initializing Inevitable2");
    }
}
class Inevitable3 {
    /**
     * 静态成员变量
     */
    static int staticNonFinal = 74;
    static {
        System.out.println("Initializing Inevitable3");
    }
}
/**
 * @Author: LightRain
 * @Description: 验证字面常量类加载执行顺序
 * @DateTime: 2023-11-13 00:19
 * @Version:1.0
 **/
public class ClassInitialization {
    public static Random rand = new Random(47);
    public static void main(String[] args) throws Exception {
        // 字面常量获取方式获取 Class 对象
        Class<Inevitable> initable = Inevitable.class;
        System.out.println("After creating Inevitable ref");
        // 不触发类初始化
        System.out.println(Inevitable.STATIC_FINAL);
        System.out.println("--------------------------------");
        // 会触发类初始化
        System.out.println(Inevitable.STATIC_FINAL2);
        System.out.println("--------------------------------");
        // 会触发类初始化
        System.out.println(Inevitable2.staticNonFinal);
        System.out.println("--------------------------------");
        //forName 方法获取 Class 对象
        Class<?> initable3 = Class.forName(Inevitable3.class.getName());
        System.out.println("After creating Inevitable3 ref");
        System.out.println(Inevitable3.staticNonFinal);
        /*
        执行结果:
        After creating Inevitable ref
        47
        --------------------------------
        Initializing Inevitable
        258
        --------------------------------
        Initializing Inevitable2
        147
        --------------------------------
        Initializing Inevitable3
        After creating Inevitable3 ref
        74
         */
    }
}
  • 从打印结果来看可以发现通过字面常量获取方式来获取 Inevitable 类的 Class 对象并没有触发 Inevitable 类的初始化,这一点也就验证了前面的分析,同时调用 Inevitable.STATIC_FINAL 变量时也没有触发初始化,这是因为 static final 属于编译期静态常量。
  • 在编译阶段通过常量传播的方式将 Inevitable 类的常量 static final 储存到一个被称为 NotInitialization 类的常量池中,在之后对 Inevitable 类常量 static final 的引用都转化为对 NotInitialization 类对自身常量池的引用了。
  • 所以在编译期后,对编译期常量的引用都将在 NotInitialization 类的常量池中获取,这也就是引用编译期静态常量不会触发 Inevitable 类初始化的主要原因。
  • 在之后调用的 Inevitable.STATIC_FINAL2 常量后就会触发了 Inevitable 类的初始化,注意: STATIC_FINAL2 虽然被 static final 修饰但其值在编译期间并不能被确定,因此 STATIC_FINAL2 不是编译期常量,要想使用该常量必须先进行类初始化。
  • Inevitable2Inevitable3 类中都是静态成员变量并非编译期常量,引用都会触发类初始化操作,至于 forName 方法获取的 Class 对象肯定会触发类初始化操作,这点已在前面分析点击跳转
  • 总结:
    • 获取 Class 对象引用的三种方式
      • 第一种:通过继承自 Object 类的 getClass 方法获取。
      • 第二种:通过 Class 类的静态方法 forName 来获取对象引用。
      • 第三中:通过 .class 字面常量方式来获取对象引用。
    • 其中实例类的 getClassClass 类的 forName 静态方法都将会触发类的初始化操作,而字面常量获取 Class 对象的方式则不会触发类初始化操作。
    • 类初始化是类加载的最后一个阶段,准确来说就是在这个阶段后类就会被加载到内存中 ( Class 对象在加载阶段已被创建),此时可以对类进行各种操作了如: new 对象、调用静态成员变量等,注意:在这个阶段才真正开始执行类中定义的 Java 程序代码或字节码。

# 泛化的 Class 对象引用分析

Class 的引用总数指向某个类的 Class 对象,利用 Class 对象可以创建实例类,也就足以说明 Class 对象的引用指向的对象确切的类型,在 JavaSE5 中加入泛型后,可以使我们更方便的用泛型来表示 Class 对象更具体的类型,即使在运行期间会被擦除,但编译期可以确保我们使用正确的对象类型。

ClassDemo
/**
 * @Author: LightRain
 * @Description: 理解 Class 泛型在编译期的用途
 * @DateTime: 2023-11-13 01:16
 * @Version:1.0
 **/
public class ClassDemo {
    public static void main(String[] args) {
        // 没有使用泛型
        Class strClass = String.class;
        // 使用泛型的 Class 对象
        Class<String> stringClass = String.class;
        // 没有使用泛型的约束,可以随意赋值
        strClass = double.class;
        // 使用泛型后在编译期将会发生错误,无法通过编译
//        stringClass = double.class
    }
}
  • 从上面代码可以看出声明普通的 Class 对象,在编译期间并不会检查 Class 对象的确切类型是否符合所需要求,只要在运行时才会被发现。
  • 但通过泛型声明并指明类型的 Class 对象,编译器会在编译期对带有泛型的类进行额外的类型检查,确保在编译期保证类型的正确性,从而提高编码效率和降低错误的发生。
  • 上述代码中 String.class 就是一个 Class<String> 类的对象,但下面的代码可能会令人疑惑。
// 无法通过编译
Class<Number> numberClass=Integer.class;

Integer 难道不是 Number 的子类吗?但事实并没有这么简单,毕竟 Integer.class 对象并非是 NumberClass 对象的子类,我们可以使用通配符 ? 来解决此问题,前面提到过,所有的 Class 对象都只来源于 Class 类。

// 可以通过编译
Class<?> generalClass=Integer.class;
        generalClass=String.class;
        generalClass=Long.class;

这样写的代码并没有问题,毕竟通配符指明的是所有类型都适用,那么为什么不直接使用 Class 还要用 Class<?> 呢?这样做是告诉编译器,我们是采用任意类型的泛型而并非忘记使用泛型约束,因此 Class<?> 是优于直接使用 Class 的,接下来我们来看使用 extends 关键字来告诉编译器接收某个类型的子类,来解决前面的 NumberInteger 问题。

// 可以通过编译
Class<?extends Number> clazz=Integer.class;
// 赋予其它类型也是可以的,毕竟都是符合属于 Number 的子类
clazz=double.class;
clazz=Number.class;
  • 上述代码因为使用了 extends 关键字来告诉编译器,只要是 Number 的子类都可以进行赋值操作。
  • 这一点与前面直接使用 Class<Number> 的性质是不一样的。
  • 应该时刻记住向 Class 引用添加泛型约束是为了提供编译期类型检查,从而避免将错误延续到运行期间的一种手段。

# 类型转换问题

许多情况中我们需要强制使用类型转换,来将当前的类型转换到另一种类型,更多情况我们会使用直接强制类型转换。

Animal.java
/**
 * 自定义动物接口
 * @author LightRain
 */
public interface Animal {
    //...
}
Cat.java
import top.rem.rain.demo.impl.Animal;
/**
 * @Author: LightRain
 * @Description: 动物 - 猫实现动物接口方法
 * @DateTime: 2023-11-13 14:16
 * @Version:1.0
 **/
public class Cat implements Animal {
    //...
}
Dog.java
/**
 * @Author: LightRain
 * @Description: 动物 - 狗并未实现动物接口方法
 * @DateTime: 2023-11-13 14:17
 * @Version:1.0
 **/
public class Dog {
    //...
}
ClassDemo2.java
import top.rem.rain.demo.impl.Animal;
/**
 * @Author: LightRain
 * @Description: 强制类型转换示例
 * @DateTime: 2023-11-13 13:39
 * @Version:1.0
 **/
public class ClassDemo2 {
    public static void main(String[] args) {
        Animal animal = new Cat();
        // 此处 (Cat) 就是属于直接使用强制类型转换
        Cat cat = (Cat) animal;
    }
}

想知道为什么可以强制转换,这全归功于 RTTI 要知道在 Java 中,所有类型转换都是在运行时进行正确性检查的,利用 RTTI 进行判断类型是否正确,从而保证了强制类型转换的完成,如果:类型转换失败将会抛出 ClassCastException 类型转换异常,除了强制转换外在 JavaSE5 中新增了一种使用 Class 对象进行类型转的方式,代码如下:

ClassDemo2.java
import top.rem.rain.demo.impl.Animal;
/**
 * @Author: LightRain
 * @Description: 强制类型转换示例
 * @DateTime: 2023-11-13 13:39
 * @Version:1.0
 **/
public class ClassDemo2 {
    public static void main(String[] args) {
        Animal animal = new Cat();
        // 此处 (Cat) 就是属于直接使用强制类型转换
//    Cat cat = (Cat) animal;
        // 这两句等同于 Cat cat = (Cat) animal;
        Class<Cat> catClass = Cat.class;
        Cat Cat = catClass.cast(animal);
    }
}

利用 Class 对象的 cast 方法,其参数就是接收一个参数对象并将其转换为 Class 引用的类型,这种方式比之前的强制转换稍微麻烦一点,而且当类型不能正确转换时,仍然会抛出 ClassCastException 异常,我们来看下源码。

Class.java
@IntrinsicCandidate
public T cast(Object obj){
        if(obj!=null&&!isInstance(obj))
        throw new ClassCastException(cannotCastMsg(obj));
        return(T)obj;
        }

# 关键字 instanceof & isInstance 方法

  • 关于 instanceof 关键字它返回的是一个 boolean 类型的值,来告诉我们对象是不是某个特定的类型实例。
  • 在强制转换前利用 instanceof 来对类型检测是否可以转换是很有必要的。
  • 如下:使用 instanceof 检测 obj 是不是 Animal 类型的实例对象,如果返回 true 再进行类型转换,这样就避免了发生 ClassCastException 类型转换的异常了。
InstanceofDemo.java
import top.rem.rain.demo.impl.Animal;
/**
 * @Author: LightRain
 * @Description: instanceof 示例
 * @DateTime: 2023-11-13 14:19
 * @Version:1.0
 **/
public class InstanceofDemo {
    public static void main(String[] args) {
        // 此处来演示 instanceof
        Dog dog = new Dog();
        Cat cat = new Cat();
        //Dog 没有实现 Animal 接口所以不属于 Animal 类型此处:false
        if (dog instanceof Animal) {
            Animal animal = (Animal) dog;
        }
        //Cat 实现了 Animal 接口所以属于 Animal 类型此处:true
        if (cat instanceof Animal) {
            Animal animal = (Animal) cat;
        }
    }
}

isInstance 方法则是 Class 类中的一个 Native 方法,也是用于来判断对象类型的。

InstanceofDemo.java
import top.rem.rain.demo.impl.Animal;
/**
 * @Author: LightRain
 * @Description: instanceof 示例
 * @DateTime: 2023-11-13 14:19
 * @Version:1.0
 **/
public class InstanceofDemo {
    public static void main(String[] args) {
        // 此处来演示 instanceof
        Dog dog = new Dog();
        Cat cat = new Cat();
        //Dog 没有实现 Animal 接口所以不属于 Animal 类型此处:false
        if (dog instanceof Animal) {
            Animal animal = (Animal) dog;
        }
        //Cat 实现了 Animal 接口所以属于 Animal 类型此处:true
        // 此处使用的是 isInstance 方法来判断
        if (Animal.class.isInstance(cat)) {
            Animal animal = (Animal) cat;
        }
    }
}

instanceofisInstance 方法产生的结果是相同的,对于 instanceof 关键字只是被用于对象引用变量,检查左边对象是不是右边类或接口的实例化,如果被检测对象是 null 则结果总是 false
isInstance 方法则是 Class 类的 Native 方法,其中 obj 是被测试的对象或变量,如果 obj 是调用这个方法的 class 或接口的实例,则返回 true ,如果被检测的对象是 null 或者基本类型则返回值是 false

// 判断这个对象是不是这种类型
obj.instanceof(class);
// 判断这个对象能不能被转化为这个类
class.inInstance(obj);

# 反射技术深入理解

  • 反射机制是在运行状态下可以获取任意一个类的所有属性和方法,能够调用任意方法和属性。
  • 这种动态获取的信息及动态调用对象的方法功能称为 Java 语言中的反射机制。
  • 反射技术可以说是 Java 中比较核心的机制,目前大部分框架如: SpringSpringBootMybatisHibernate 等得以实现的支柱。
  • JavaClass 类与 java.lang.reflect 类库一起对反射技术进行了全力支持。
  • 在反射包中我们常用的类主要有:
    • Constructor :表示 Class 对象的构造方法,它可以在运行时动态创建对象。
    • Field :表示 Class 对象的成员变量,通过它可以在运行时动态修改成员变量的属性值,其中包含 private 修饰的变量。
    • Method :表示 Class 对象的成员方法,通过它可以动态调用对象的方法,其中包含 private 修饰的方法。

# Constructor 类的使用

Constructor 类存在于反射包 java.lang.reflect 包中,反映的是 Class 对象所表示的类的构造方法,获取 Constructor 对象是通过 Class 类中的方法获取的, Class 类与 Constructor 相关的主要方法如下。

返回值方法名称说明
static Class<?>forName(String className)返回与带有给定字符串名的类或接口相关联的 Class 对象
Constructor<T>getConstructor(Class<?>... parameterTypes)返回指定参数类型、具有 public 访问权限的构造函数对象
Constructor<?>[]getConstructors()返回所有具有 public 访问权限的构造函数的 Constructor 对象数组
Constructor<T>getDeclaredConstructor(Class<?>... parameterTypes)返回指定参数类型、所有声明的(包括 private )构造函数对象
Constructor<?>[]getDeclaredConstructor()返回所有声明的(包括 private )构造函数对象
TnewInstance()创建此 Class 对象所表示的类的一个新实例
ConstructorDemo .java
package top.rem.rain.demo;
import java.io.Serializable;
import java.lang.reflect.Constructor;
class User {
    private int age;
    private String name;
    public User() {
        super();
    }
    public User(String name) {
        super();
        this.name = name;
    }
    /**
     * 私有构造
     *
     * @param age  年龄
     * @param name 名称
     */
    private User(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
/**
 * @Author: LightRain
 * @Description: Constructor 对象的使用
 * @DateTime: 2023-11-13 20:08
 * @Version:1.0
 **/
public class ConstructorDemo implements Serializable {
    public static void main(String[] args) throws Exception {
        Class<?> clazz = null;
        // 获取 Class 对象的引用
        clazz = Class.forName(User.class.getName());
        // 第一种方法,实例化默认构造方法,User 必须无参构造函数,否则将抛出异常
        User user = (User) clazz.getDeclaredConstructor().newInstance();
        user.setAge(18);
        user.setName("小红");
        System.out.println(user);
        System.out.println("--------------------------------------------");
        // 获取带 String 参数的 public 构造函数
        Constructor<?> constructor = clazz.getConstructor(String.class);
        // 创建 User
        User lightRainUser = (User) constructor.newInstance("LightRain");
        lightRainUser.setAge(19);
        System.out.println("lightRainUser:" + lightRainUser);
        System.out.println("--------------------------------------------");
        // 取得指定带 int 和 String 参数构造函数,该方法是私有构造 private
        Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, String.class);
        // 由于是 private 必须设置可访问
        constructor2.setAccessible(true);
        // 创建 user 对象
        User tomcat = (User) constructor2.newInstance(20, "tomcat");
        System.out.println("tomcat:" + tomcat.toString());
        System.out.println("--------------------------------------------");
        // 获取所有构造包含 private
        Constructor<?>[] cons = clazz.getDeclaredConstructors();
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            // 获取构造函数参数类型
            Class<?>[] clazzs = cons[i].getParameterTypes();
            System.out.println("构造函数[" + i + "]:" + cons[i].toString());
            System.out.print("参数类型[" + i + "]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1) {
                    System.out.print(clazzs[j].getName());
                } else {
                    System.out.print(clazzs[j].getName() + ",");
                }
            }
            System.out.println(")");
        }
        /*  运行结果:
            User {age=18, name=' 小红 '}
            --------------------------------------------
            lightRainUser:User {age=19, name='LightRain'}
            --------------------------------------------
            tomcat:User {age=20, name='tomcat'}
            --------------------------------------------
            构造函数 [0]:private top.rem.rain.demo.User (int,java.lang.String)
            参数类型 [0]:(int,java.lang.String)
            构造函数 [1]:public top.rem.rain.demo.User (java.lang.String)
            参数类型 [1]:(java.lang.String)
            构造函数 [2]:public top.rem.rain.demo.User ()
            参数类型 [2]:()
         */
    }
}

Constructor 类本身的一些部分常用方法如下:

返回值方法名称说明
Class<T>getDeclaringClass()返回 Class 对象,该对象表示声明由此 Constructor 对象表示的构造方法的类,其实就是返回真实类型(不包含参数)
Type[]getGenericParameterTypes()按照声明顺序返回一组 Type 对象,返回的就是 Constructor 对象构造函数的形参类型
StringgetName()以字符串形式返回此构造方法的名称
Class<?>[]getParameterTypes()按照声明顺序返回一组 Class 对象,即返回 Constructor 对象所表示构造方法的形参类型
TnewInstance(Object... initargs)使用此 Constructor 对象表示的构造函数来创建新实例
StringtoGenericString()返回描述此 Constructor 的字符串,其中包括类型参数
ConstructorDemo.java
package top.rem.rain.demo;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
class User {
    private int age;
    private String name;
    public User() {
        super();
    }
    public User(String name) {
        super();
        this.name = name;
    }
    /**
     * 私有构造
     *
     * @param age  年龄
     * @param name 名称
     */
    private User(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
/**
 * @Author: LightRain
 * @Description: Constructor 对象的使用和 Constructor 类本身一些常用方法
 * @DateTime: 2023-11-13 20:08
 * @Version:1.0
 **/
public class ConstructorDemo implements Serializable {
    public static void main(String[] args) throws Exception {
        //Class 类与 Constructor 相关的主要方法如下
        Class<?> clazz = null;
        // 获取 Class 对象的引用
        clazz = Class.forName(User.class.getName());
        // 第一种方法,实例化默认构造方法,User 必须无参构造函数,否则将抛出异常
        User user = (User) clazz.getDeclaredConstructor().newInstance();
        user.setAge(18);
        user.setName("小红");
        System.out.println(user);
        System.out.println("--------------------------------------------");
        // 获取带 String 参数的 public 构造函数
        Constructor<?> constructor = clazz.getConstructor(String.class);
        // 创建 User
        User lightRainUser = (User) constructor.newInstance("LightRain");
        lightRainUser.setAge(19);
        System.out.println("lightRainUser:" + lightRainUser);
        System.out.println("--------------------------------------------");
        // 取得指定带 int 和 String 参数构造函数,该方法是私有构造 private
        Constructor<?> constructor2 = clazz.getDeclaredConstructor(int.class, String.class);
        // 由于是 private 必须设置可访问
        constructor2.setAccessible(true);
        // 创建 user 对象
        User tomcat = (User) constructor2.newInstance(20, "tomcat");
        System.out.println("tomcat:" + tomcat.toString());
        System.out.println("--------------------------------------------");
        // 获取所有构造包含 private
        Constructor<?>[] cons = clazz.getDeclaredConstructors();
        // 查看每个构造方法需要的参数
        for (int i = 0; i < cons.length; i++) {
            // 获取构造函数参数类型
            Class<?>[] clazzs = cons[i].getParameterTypes();
            System.out.println("构造函数[" + i + "]:" + cons[i].toString());
            System.out.print("参数类型[" + i + "]:(");
            for (int j = 0; j < clazzs.length; j++) {
                if (j == clazzs.length - 1) {
                    System.out.print(clazzs[j].getName());
                } else {
                    System.out.print(clazzs[j].getName() + ",");
                }
            }
            System.out.println(")");
        }
        /*  执行结果:
            User {age=18, name=' 小红 '}
            --------------------------------------------
            lightRainUser:User {age=19, name='LightRain'}
            --------------------------------------------
            tomcat:User {age=20, name='tomcat'}
            --------------------------------------------
            构造函数 [0]:private top.rem.rain.demo.User (int,java.lang.String)
            参数类型 [0]:(int,java.lang.String)
            构造函数 [1]:public top.rem.rain.demo.User (java.lang.String)
            参数类型 [1]:(java.lang.String)
            构造函数 [2]:public top.rem.rain.demo.User ()
            参数类型 [2]:()
         */
        //Constructor 类本身的一些常用方法如下
        Constructor<?> cs3 = clazz.getDeclaredConstructor(int.class, String.class);
        System.out.println("-----getDeclaringClass-----");
        Class<?> uclazz = cs3.getDeclaringClass();
        //Constructor 对象表示的构造方法的类
        System.out.println("构造方法的类:" + uclazz.getName());
        System.out.println("-----getGenericParameterTypes-----");
        // 对象表示此 Constructor 对象所表示的方法的形参类型
        Type[] tps = cs3.getGenericParameterTypes();
        for (Type tp : tps) {
            System.out.println("参数名称tp:" + tp);
        }
        System.out.println("-----getParameterTypes-----");
        // 获取构造函数参数类型
        Class<?>[] clazzs = cs3.getParameterTypes();
        for (Class<?> claz : clazzs) {
            System.out.println("参数名称:" + claz.getName());
        }
        System.out.println("-----getName-----");
        // 以字符串形式返回此构造方法的名称
        System.out.println("getName:" + cs3.getName());
        System.out.println("-----getGenericString-----");
        // 返回描述此 Constructor 的字符串,其中包括类型参数。
        System.out.println("getGenericString():" + cs3.toGenericString());
        /*  执行结果:
            -----getDeclaringClass-----
            构造方法的类:top.rem.rain.demo.User
            -----getGenericParameterTypes-----
            参数名称 tp:int
            参数名称 tp:class java.lang.String
            -----getParameterTypes-----
            参数名称:int
            参数名称:java.lang.String
            -----getName-----
            getName:top.rem.rain.demo.User
            -----getGenericString-----
            getGenericString ():private top.rem.rain.demo.User (int,java.lang.String)
         */
    }
}

# Field 类的使用

Field 类提供有关类或接口的单个字段信息,以及它的动态访问权限,反射的可能是一个类、字段、实例,同样的道理我们可以通过 Class 类提供的方法来获取字段信息的 Field 对象, Class 类与 Field 对象相关方法如下:

返回值方法名称说明
FieldgetDeclaredField(String name)获取指定 name 名称的 (包含 private 修饰的) 字段,不包括继承的字段
Field[]getDeclaredField()获取 Class 对象所表示的类或接口的所有 (包含 private 修饰的) 字段,不包括继承的字段
FieldgetField(String name)获取指定 name 名称、具有 public 修饰的字段,包含继承字段
Field[]getField()获取修饰符为 public 的字段,包含继承字段
FieldDemo.java
package top.rem.rain.demo;
import java.lang.reflect.Field;
class Person {
    public int age;
    public String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Student{" +
                "desc='" + desc + '\'' +
                ", score=" + score +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Student extends Person {
    public String desc;
    private int score;
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "Student{" +
                "desc='" + desc + '\'' +
                ", score=" + score +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
/**
 * @Author: LightRain
 * @Description: Field 与 Class 相关方法使用
 * @DateTime: 2023-11-13 22:47
 * @Version:1.0
 **/
public class FieldDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        //Field 与 Class 相关方法使用
        Class<?> clazz = Class.forName(Student.class.getName());
        // 获取指定字段名称的 Field 类,注意字段修饰符必须为 public 而且存在该字段,否则抛 NoSuchFieldException
        Field field = clazz.getField("age");
        System.out.println("field:" + field);
        // 获取所有修饰符为 public 的字段,包含父类字段,注意修饰符为 public 才会获取
        Field[] fields = clazz.getFields();
        for (Field f : fields) {
            System.out.println("f:" + f.getDeclaringClass());
        }
        System.out.println("================getDeclaredFields====================");
        // 获取当前类所字段 (包含 private 字段), 注意不包含父类的字段
        Field[] fields2 = clazz.getDeclaredFields();
        for (Field f : fields2) {
            System.out.println("f2:" + f.getDeclaringClass());
        }
        // 获取指定字段名称的 Field 类,可以是任意修饰符的自动,注意不包含父类的字段
        Field field2 = clazz.getDeclaredField("desc");
        System.out.println("field2:" + field2);
        /*
        执行结果:
        field:public int top.rem.rain.demo.Person.age
        f:class top.rem.rain.demo.Student
        f:class top.rem.rain.demo.Person
        f:class top.rem.rain.demo.Person
        ================getDeclaredFields====================
        f2:class top.rem.rain.demo.Student
        f2:class top.rem.rain.demo.Student
        field2:public java.lang.String top.rem.rain.demo.Student.desc
         */
    }
}

需要注意的是上面的代码如果我们不期望获取其父类的字段,则需要使用 Class 类的 getDeclaredFieldgetDeclaredFields 方法来获取字段即可,倘若需要获取到父类字段,那么就需要使用 Class 类的 getFieldgetFields 方法,但是也只能获取到 public 修饰的字段,无法获取到父类的 private 修饰的字段,下面将通过 Field 类本身方法对指定类属性赋值的代码。

package top.rem.rain.demo;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
class Person {
    public int age;
    public String name;
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
class Student extends Person {
    public String desc;
    private int score;
    public String getDesc() {
        return desc;
    }
    public void setDesc(String desc) {
        this.desc = desc;
    }
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }
    @Override
    public String toString() {
        return "Student{" +
                "desc='" + desc + '\'' +
                ", score=" + score +
                ", age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
}
/**
 * @Author: LightRain
 * @Description: Field 类本身的方法使用
 * @DateTime: 2023-11-13 22:47
 * @Version:1.0
 **/
public class FieldDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        //Field 类本身的方法使用
        // 获取 Class 对象引用
        Class<?> clazz = Class.forName(Student.class.getName());
        Student st = (Student) clazz.getDeclaredConstructor().newInstance();
        // 获取父类 public 字段并赋值
        Field ageField = clazz.getField("age");
        ageField.set(st, 18);
        Field nameField = clazz.getField("name");
        nameField.set(st, "Lily");
        // 只获取当前类的字段,不获取父类的字段
        Field descField = clazz.getDeclaredField("desc");
        descField.set(st, "I am student");
        Field scoreField = clazz.getDeclaredField("score");
        // 设置可访问,score 是 private 的
        scoreField.setAccessible(true);
        scoreField.set(st, 88);
        System.out.println(st.toString());
        // 获取字段值
        System.out.println(scoreField.get(st));
        /*
        执行结果:
        Student {desc='I am student', score=88, age=18, name='Lily'}
        88
         */
    }
}

其中 set(Object obj,Object value) 方法是 Field 本身的方法,用于设置字段的值,而 get(Object obj) 则是获取字段的值,关于 Field 类还有其它常用方法如下:

返回值方法名称说明
voidset(Object obj,Object value)将指定对象变量上此 Field 对象表示的字段设置为指定的新值
Objectget(Object obj)返回指定对象上此 Field 表示的字段的值
Class<?>getType()返回一个 Class 对象,它标识了此 Field 对象所表示字段的声明类型
booleanisEnumConstant()如果此字段表示枚举类型的元素则返回 true 否则返回 false
StringtoGenericString()返回一个描述此 Field (包括其一般类型) 的字符串
StringgetName()返回此 Field 对象表示的字段的名称
Class<?>getDeclaringClass()返回表示类或接口的 Class 对象,该类或接口声明由此 Field 对象表示的字段
voidsetAccessible(boolean flag)将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性

这是一些较为常用的方法,在设置值的方法上 Field 类还提供了专门针对基本数据类型的方法,如 setInt() getInt() setBoolean() getBoolean() setChar() getChar() 等方法,需要时查 API 文档即可,需要特别注意的是被 final 修饰的关键字的 Field 字段是安全的,在运行时可以接收任何修改,但最终其值不会发生任何改变。

# Method 类的使用

Method 提供了类或接口上单独某个方法,以及如何方法该方法的信息,所反映的方法可能是类方法或实例方法 (包括抽象方法),下面是 Class 类获取 Method 对象相关的方法:

返回值方法名称说明
MethodgetDeclaredMethod(String name,Class<?>... parameterTypes)返回一个指定参数的 Method 对象,该对象反映此 Class 对象所表示的类或接口的指定已声明方法
Method[]getDeclaredMethod()返回 Method 对象的一个数组,这些对象反映此 Class 对象表示的类或接口声明的所有方法,包括公共、保护、默认 (包) 访问的私有方法,但不包含继承的方法
MethodgetMethod(String name,Class<?>... parameterTypes)返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法
Method[]getMethods()返回一个包含某些 Method 对象的数组,这些对象反映此 Class 对象所表示的类或接口 (包括那些由该类或声明的以及从超类和超接口继承的那些类或接口) 的公共 member 方法
package top.rem.rain.demo;
import java.lang.reflect.Method;
class Shape {
    public void draw() {
        System.out.println("draw");
    }
    public void draw(int count, String name) {
        System.out.println("draw " + name + ",count=" + count);
    }
}
class Circle extends Shape {
    private void drawCircle() {
        System.out.println("drawCircle");
    }
    public int getAllCount() {
        return 100;
    }
}
/**
 * @Author: LightRain
 * @Description: Method 与 Class 相关方法使用
 * @DateTime: 2023-11-13 23:49
 * @Version:1.0
 **/
public class MethodDemo {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
        Class<?> clazz = Class.forName(Circle.class.getName());
        // 根据参数获取 public 的 Method, 包含继承自父类的方法
        Method method = clazz.getMethod("draw", int.class, String.class);
        System.out.println("method:" + method);
        // 获取所有 public 的方法:
        Method[] methods = clazz.getMethods();
        for (Method m : methods) {
            System.out.println("m::" + m);
        }
        System.out.println("=========================================");
        // 获取当前类的方法包含 private, 该方法无法获取继承自父类的 method
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        System.out.println("method1::" + method1);
        // 获取当前类的所有方法包含 private, 该方法无法获取继承自父类的 method
        Method[] methods1 = clazz.getDeclaredMethods();
        for (Method m : methods1) {
            System.out.println("m1::" + m);
        }
        /*
            执行结果:
            method:public void top.rem.rain.demo.Shape.draw (int,java.lang.String)
            m::public int top.rem.rain.demo.Circle.getAllCount ()
            m::public void top.rem.rain.demo.Shape.draw ()
            m::public void top.rem.rain.demo.Shape.draw (int,java.lang.String)
            m::public final void java.lang.Object.wait (long,int) throws java.lang.InterruptedException
            m::public final void java.lang.Object.wait () throws java.lang.InterruptedException
            m::public final native void java.lang.Object.wait (long) throws java.lang.InterruptedException
            m::public boolean java.lang.Object.equals (java.lang.Object)
            m::public java.lang.String java.lang.Object.toString ()
            m::public native int java.lang.Object.hashCode ()
            m::public final native java.lang.Class java.lang.Object.getClass ()
            m::public final native void java.lang.Object.notify ()
            m::public final native void java.lang.Object.notifyAll ()
            =========================================
            method1::private void top.rem.rain.demo.Circle.drawCircle ()
            m1::private void top.rem.rain.demo.Circle.drawCircle ()
            m1::public int top.rem.rain.demo.Circle.getAllCount ()
         */
    }
}

通过 getMethod 方法获取 Method 对象时,也会获取到其父类中的方法,上面的执行结果中把 Object 类的方法都打印了出来,而 getDeclaredMethod getDeclaredMethods 方法都只能获取当前类的方法。 下面是通过 Method 对象调用指定类的方法。

MethodDome2.java
package top.rem.rain.demo;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
class Shape2 {
    public void draw() {
        System.out.println("draw");
    }
    public void draw(int count, String name) {
        System.out.println("draw " + name + ",count=" + count);
    }
}
class Circle2 extends Shape2 {
    private void drawCircle() {
        System.out.println("drawCircle");
    }
    public int getAllCount() {
        return 100;
    }
}
/**
 * @Author: LightRain
 * @Description: 通过 Method 对象调用指定类的方法
 * @DateTime: 2023-11-14 00:02
 * @Version:1.0
 **/
public class MethodDome2 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class<?> clazz = Class.forName(Circle2.class.getName());
        // 创建对象
        Circle2 circle = (Circle2) clazz.getDeclaredConstructor().newInstance();
        // 获取指定参数的方法对象 Method
        Method method = clazz.getMethod("draw", int.class, String.class);
        // 通过 Method 对象的 invoke (Object obj,Object... args) 方法调用
        method.invoke(circle, 18, "小雨");
        // 对私有无参方法的操作
        Method method1 = clazz.getDeclaredMethod("drawCircle");
        // 修改私有方法的访问标识
        method1.setAccessible(true);
        method1.invoke(circle);
        // 对有返回值得方法操作
        Method method2 = clazz.getDeclaredMethod("getAllCount");
        Integer count = (Integer) method2.invoke(circle);
        System.out.println("count:" + count);
        /*
            执行结果:
            draw 小雨,count=18
            drawCircle
            count:100
         */
    }
}

上述代码中调用的方法使用了 Method 类的 invoke(Object obj,Object... args) 第一个参数代表调用的对象,第二个参数传递调用方法的参数,这样就完成了类方法的动态调用了。

返回值方法名称说明
Objectinvoke(Object obj, Object... args)对带有指定参数的指定对象调用由此 Method 对象表示的底层方法
Class<?>getReturnType()返回一个 Class 对象,该对象描述了此 Method 对象所表示的方法的正式返回类型,即方法返回类型
TypegetGenericReturnType()返回表示由此 Method 对象所表示方法的正式返回类型的 Type 对象,也是方法的返回类型
Class<?>[]getParameterTypes()按照声明顺序返回 Class 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型,即返回方法的参数类型组成的数组
Typep[]getGenericParameterTypes()按照声明顺序返回 Type 对象的数组,这些对象描述了此 Method 对象所表示的方法的形参类型,也是返回方法的参数类型
StringgetName()String 形式返回此 Method 对象表示的方法名称,即返回方法的名称
booleanisVarArgs()判断方法是否带有可变参数,如果将此方法声明为带有可变数量的参数,则返回 true 否则返回 false
StringtoGenericString()返回描述此 Method 的字符串,包括类型参数

getReturnTypegetGenericReturnType 方法都是获取 Method 对象表示的方法的返回类型,只不过前者返回的 Class 类型,后者返回的是 TypeType 只是一个接口而已,在 Java8 中新增了一个默认的方法实现,返回的就是参数类型信息。

Type.java
package java.lang.reflect;
public interface Type {
    // 自 1.8 新增
    default String getTypeName() {
        return toString();
    }
}

getParameterTypesgetGenericParameterTypes 方法也是同样的道理,都是获取 Method 对象所表示的方法的参数类型,其它方法与前面的 FieldConstructor 是类似的。

# 反射包中的 Array 类

Java 中的 java.lang.reflect 包中存在着一个可以动态操作数组的类: Array 类它提供了动态创建和访问 Java 数组的方法, Array 允许在执行 getset 时进行取值和赋值操作, Class 类中与数组关联的方法如下:

返回值方法名称说明
Class<?>getComponentType()返回表示数组元素类型的 Class 即数组的类型
booleanisArray()判定此 Class 对象是否表示一个数组类

java.lang.reflect.Array 中的常用静态方法如下:

返回值方法名称说明
static Objectset(Object array, int index)返回指定数组对象中索引组件的值
static intgetLength(Object array)int 形式返回指定数组对象的长度
static objectnewInstance(Class<?> componentType, int... dimensions)创建一个具有指定类型和维度的新数组
static ObjectnewInstance(Class<?> componentType, int length)创建一个具有指定的组件类型和长度的新数组
static voidset(Object array, int index, Object value)将指定数组对象中索引组件的值设置为指定的新值
ArrayDemo.java
package top.rem.rain.demo;
import java.lang.reflect.Array;
/**
 * @Author: LightRain
 * @Description: Array 常用方法使用
 * @DateTime: 2023-11-14 15:50
 * @Version:1.0
 **/
public class ArrayDemo {
    public static void main(String[] args) throws ClassNotFoundException {
        int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
        // 获取数组类型的 Class 即 int.class
        Class<?> clazz = array.getClass().getComponentType();
        // 创建一个具有指定的组件类型和长度的新数组。
        // 第一个参数:数组的类型,第二个参数:数组的长度
        Object newArr = Array.newInstance(clazz, 15);
        // 获取原数组的长度
        int co = Array.getLength(array);
        // 赋值原数组到新数组
        System.arraycopy(array, 0, newArr, 0, co);
        for (int i:(int[]) newArr) {
            System.out.print(i+",");
        }
        // 创建了一个长度为 10 的字符串数组,
        // 接着把索引位置为 6 的元素设为 "hello world!",然后再读取索引位置为 6 的元素的值
        Class<?> clazz2 = Class.forName("java.lang.String");
        // 创建一个长度为 10 的字符串数组,在 Java 中数组也可以作为 Object 对象
        Object array2 = Array.newInstance(clazz2, 10);
        // 把字符串数组对象的索引位置为 6 的元素设置为 "hello"
        Array.set(array2, 6, "hello world!");
        // 获得字符串数组对象的索引位置为 5 的元素的值
        String str = (String) Array.get(array2, 6);
        System.out.println();
        System.out.println(str);
        /*
        执行结果:
        1,2,3,4,5,6,7,8,9,0,0,0,0,0,0,
        hello world!
         */
    }
}

上述代码演示确实可以利用 Array 类和反射结合动态创建数组,也可以在运行时动态获取和设置数组中元素的值,其实除了以上的 set getArray 还专门为八种基本类型提供了特有的方法,如: setIntgetIntsetBooleangetBoolean 等,需要时查看 API 文档即可,除了上述动态修改数组长度或者动态创建数组和动态获取值和设置值外,可以利用泛型动态创建泛型数组代码如下:

/**
 * @Author: LightRain
 * @Description: Array 常用方法使用
 * @DateTime: 2023-11-14 15:50
 * @Version:1.0
 **/
public class ArrayDemo {
  /**
   * 接收一个泛型数组,创建一个长度与接收的数组长度一样的泛型数组,并把接收的数组的元素复制到新创建的数组中,最后找出新数组中的最小元素,并打印出来
   * @param a 泛型数组
   * @param <T> 泛型
   */
  public static <T extends Comparable<T>> void min(T[] a) {
    // 通过反射创建相同类型的数组
    T[] b = (T[]) Array.newInstance(a.getClass().getComponentType(), a.length);
    System.arraycopy(a, 0, b, 0, a.length);
    T min = null;
    boolean flag = true;
    for (T t : b) {
      if (flag) {
        min = t;
        flag = false;
      }
      if (t.compareTo(min) < 0) {
        min = t;
      }
    }
    System.out.println(min);
  }
}

毕竟我们无法直接创建泛型数组,有了 Array 的动态创建数组的方法这个文件也就迎刃而解了。

// 无法通过编译
T[] a = new T[];

到这里反射的几个重要常用的就基本介绍完了,但更重要的是我们应该认识到反射机制并没有什么神奇之处,当通过反射与一个未知类型的对象打交道时, JVM 只会简单的检查这个对象,判断该对象属于哪种类型,同时也应该知道,在使用反射机制创建对象前,必须确保已经加载了这个类的 Class 对象,当然这点完全不必由我们来操作,毕竟只能由 JVM 来加载,但必须确保该类的 .class 文件已存在并且 JVM 能够找到。
关于 Class 类的方法在前面我们只分析了主要的一些方法,下面仅列出前面没有介绍过又可能用到的 API 如下:

/** 
  *    修饰符、父类、实现的接口、注解相关 
  */
// 获取修饰符,返回值可通过 Modifier 类进行解读
public native int getModifiers();
// 获取父类,如果为 Object,父类为 null
public native Class<? super T> getSuperclass();
// 对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类间接继承来的
public native Class<?>[] getInterfaces();
// 自己声明的注解
public Annotation[] getDeclaredAnnotations();
// 所有的注解,包括继承得到的
public Annotation[] getAnnotations();
// 获取或检查指定类型的注解,包括继承得到的
public <A extends Annotation> A getAnnotation(Class<A> annotationClass);
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass);
/** 
  *   内部类相关
  */
// 获取所有的 public 的内部类和接口,包括从父类继承得到的
public Class<?>[] getClasses();
// 获取自己声明的所有的内部类和接口
public Class<?>[] getDeclaredClasses();
// 如果当前 Class 为内部类,获取声明该类的最外部的 Class 对象
public Class<?> getDeclaringClass();
// 如果当前 Class 为内部类,获取直接包含该类的类
public Class<?> getEnclosingClass();
// 如果当前 Class 为本地类或匿名内部类,返回包含它的方法
public Method getEnclosingMethod();
/** 
  *    Class 对象类型判断相关
  */
// 是否是数组
public native boolean isArray();  
// 是否是基本类型
public native boolean isPrimitive();
// 是否是接口
public native boolean isInterface();
// 是否是枚举
public boolean isEnum();
// 是否是注解
public boolean isAnnotation();
// 是否是匿名内部类
public boolean isAnonymousClass();
// 是否是成员类
public boolean isMemberClass();
// 是否是本地类
public boolean isLocalClass();