Java中的反射机制(Reflect)

本文深入探讨Java中Class类的应用,包括获取类实例、基本数据类型、动态加载类、获取方法及成员变量信息等方面,帮助读者全面掌握Class类的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、Class类的使用

1、在面向对象的世界里,万事万物皆对象。

不过在java语言中,有两类事物不是面向对象的。它们分别是 java 中的基本数据类型(例如:int a = 5)和 java 中的静态成员(包括静态成员变量、静态成员方法)。其中,虽然普通数据类型不是对象,但是与之对应的包装类却是面向对象的,例如:int - Integer,boolean - Boolean 等。

2、类是谁的对象呢?

类是对象,类是 java.lang.Class 类的实例对象。(There is a class named Class)

假如有一个类 Person,那么可以用 new Person()来表示 Person 类的一个实例对象。
其实在 Java 中,Person 类本身也是一个类的实例对象,这个类就是 Class (java.lang.Class)。
也就是说,在 Java 中任何一个类都是 Class 的实例对象,只不过这些实例对象只能由 JVM 创建,可以理解为就是编译之后产生的一个个 .class 文件。

java.lang.Class的部分源码如下:

public final
    class Class<T> implements java.io.Serializable,
                              java.lang.reflect.GenericDeclaration,
                              java.lang.reflect.Type,
                              java.lang.reflect.AnnotatedElement {
    ···

    /*
     * Constructor. Only the Java Virtual Machine creates Class
     * 私有的构造函数,只有Java虚拟机可以创建Class
     * objects.
     */
    private Class() {}

    ···
}

Person 的实例对象可以用 new Person( )来表示,那么 Class 的实例对象应该如何表示呢?
以 Person 类为例,表示 Class 的实例对象一般有以下三种方式:

a、类名.class
Class c1 = Person.class;

实际在告诉我们任何一个类都有一个隐含的静态成员变量 class

b、该类的已知对象.getClass()
Person p = new Person();
Class c2 = p.getClass();

小结:
  这里 p 代表 Person 类的一个实例对象, c1 、c2 则分别代表 Class 类的实例对象。
  官网描述为:c1、c2 表示了 Person 类的类类型(class type)。
  
  万事万物皆对象,类也是对象,是Class类的实例对象,这个对象我们称为该类的类类型。
  
  c1、c2都代表了 Person 类的类类型,c1 == c2 为true说明一个类只可能是 Class 类的一个实例对象

c、Class.forName(“类的完整包名路径”)
try {
    Class<?> c3 = Class.forName("com.example.Person");
    System.out.println(c3);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

我们完全可以通过类的类类型来创建该类的对象实例。

try {
    Person person = c1.newInstance(); // 【注意】需要有无参数的构造方法
    person.print();
} catch (Exception e) {
    e.printStackTrace();
}

完整的代码:

public class MainClass {
    public static void main(String[] args) {
        // Person是一个类,它的实例对象如何表示?
        Person p = new Person();    // p 就代表了Person类的一个实例对象

        // 其实,Person类也是一个类的实例对象,这个类就是 Class (java.lang.Class)
        // 在Java中,任何一个类都是Class的实例对象,只不过这些实例对象只能由JVM创建,也就是编译后产生的一个个 .class 文件
        // 那么Person作为Class的一个实例对象,应该如何来表示呢?

        // 第一种方式
        Class<? extends Person> c1 = p.getClass();
        System.out.println(c1);
        System.out.println(c1.getSuperclass()); // 这里印证了Class的父类其实还是Object

        // 第二种方式
        Class<? extends Person> c2 = Person.class; // 任何一个类都有一个隐含的静态成员变量class
        System.out.println(c2);
        System.out.println(c1 == c2);

        // 第三种方式
        try {
            Class<?> c3 = Class.forName("com.example.Person");
            System.out.println(c3);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // 通过类的类类型来创建该类的对象实例
        try {
            Person person = c1.newInstance(); // 【注意】需要有无参数的构造方法
            person.print();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

class Person {
    void print() {
        System.out.print("person ----------------");
    }
}

二、基本数据类型

java中的基本数据类型(boolean、byte、char、short、int、long、float、double)以及void关键字等都存在类类型。

public class TypeClass {
    public static void main(String[] args) {
        Class c1 = int.class;
        Class c2 = Integer.class;
        Class c3 = double.class;
        Class c4 = Double.class;
        Class c5 = void.class;
        Class c6 = Void.class;

        System.out.println("int.class     = " + c1.getName());
        System.out.println("Integer.class = " + c2.getName());
        System.out.println("double.class  = " + c3.getName());
        System.out.println("Double.class  = " + c4.getName());
        System.out.println("void.class    = " + c5.getName());
        System.out.println("Void.class    = " + c6.getName());


    }
}

执行后的打印日志:

int.class     = int
Integer.class = java.lang.Integer
double.class  = double
Double.class  = java.lang.Double
void.class    = void
Void.class    = java.lang.Void
getName( )

获取类类型的名称

getSimpleName( )

获取不带包名的类类型名称

Class c = Integer.class;
System.out.println("c.getName() = " + c.getName()); // c.getName() = java.lang.Integer
System.out.println("c.getSimpleName() = " + c.getSimpleName()); // c.getSimpleName() = Integer

三、动态加载类

Class.forName(“类的完整包名路径”)

不仅表示了类的类类型,还代表了动态加载类。
编译时刻加载类是静态加载类,运行时刻加载类是动态加载类。

静态加载示例:
public class Office {
    public static void main(String[] args) {
        start("Word");
    }

    /**
     * new 创建对象是静态加载类,在编译时刻就需要加载所有可能使用到的类。
     */
    private static void start(String arg) {

       if ("Word".equals(arg)) {
            Word w = new Word();
            w.start();
        }

        if ("Excel".equals(arg)) {
            Excel e = new Excel();
            e.start();
        }
    }
}

上边示例代码其实只使用到了 Word 类,但是因为是静态加载,在编译时期就需要加载所有可能使用到的类。所以,当只提供了 Word 类的情况下,因为没有 Excel 类,在编译时期就会检测到 Excel 类不存在的异常,导致程序其实根本用不到 Excel 类的情况下,也会因为编译时期报错而导致异常。

动态加载示例:
public class OfficeBetter {
    public static void main(String[] args) {
        run("Word");
        run("Excel");
    }

    /**
     * 根据文件类型动态加载对应的类,并执行其中的 start() 方法
     * @param name 代表文件的类型
     */
    private static void run(String name) {
        try {
            Class<?> c1 = Class.forName("com.example.office." + name);
            // 这里因为动态加载,无法确定加载的类是Word还是Excel,所以抽出一个接口类 OfficeAble
            // 让 Word 和 Excel 都实现 OfficeAble 并实现 start() 方法即可
            OfficeAble o = (OfficeAble) c1.newInstance();
            o.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里即使我们只使用Word类,也不会出现静态加载所产生的问题。同时如果后期需要添加新的功能,比如加入一个 PPT 的类并要执行一定的逻辑,这时不需要改动 OfficeAble 中的代码逻辑,只需要创建出 PPT 类并实现 OfficeAble 接口,将相应的逻辑功能在 start( )方法中实现即可。这就是动态加载相对的一些优势。

四、获取方法信息

方法也是对象,是 java.lang.reflect.Method 的实例对象, Method 类封装了关于类方法的操作。

先通过上面三种获取类类型的任意一种方式获取到类类型,例如:

String s = "abc";
Class c = s.getClass();
Class.getMethods()
Method[] methods = c.getMethods();  // 【注意】获取的是所有public的方法,包括从父类继承而来的
Class.getDeclaredMethods()
Method[] methods = c.getDeclaredMethods(); // 【注意】获取的是所有该类自己声明的方法,不问访问权限(不包含父类继承来的方法)
Method常用方法
method.getReturnType().getSimpleName(); // getReturnType() 得到方法返回值类型的类类型
method.getName();   // getName() 得到方法的名称
Class<?>[] paramTypes = method.getParameterTypes(); // getParameterTypes() 得到方法参数列表类型的类类型

五、获取成员变量信息

成员变量也是对象,是 java.lang.reflect.Field 的实例对象, Field 类封装了关于成员变量的操作。

先通过上面三种获取类类型的任意一种方式获取到类类型,例如:

String s = "abc";
Class c = s.getClass();
Class.getFields()
Field[] fields = c.getFields();  // 【注意】获取的是所有public的成员变量信息,包括从父类继承而来的
Class.getDeclaredFields()
Field[] fields = c.getDeclaredFields(); // 【注意】获取的是所有自己声明的成员变量信息
Field常用方法
Field[] fields = c.getFields();
for (int i = 0; i < fields.length; i++) {
    // 可以直接打印成员变量的信息
    // System.out.println(fields[i].toString());

    Class<?> type = fields[i].getType(); // getType() 获得成员变量类型的类类型
    System.out.println(type.getSimpleName() + "  " + fields[i].getName());
}

六、获取构造函数信息

构造函数也是对象,是 java.lang.reflect.Constructor 的实例对象, Constructor 类封装了关于构造函数的操作。

先通过上面三种获取类类型的任意一种方式获取到类类型,例如:

String s = "abc";
Class c = s.getClass();
Class.getConstructors()
Constructor[] constructors = c.getConstructors();  // 【注意】获取的是所有public的构造函数
Class.getDeclaredConstructors()
Constructor[] declaredConstructors = c.getDeclaredConstructors(); // 【注意】获取所有的构造函数(不问访问权限,构造函数都是自己声明的)
Constructor常用方法
Constructor[] declaredConstructors = c.getDeclaredConstructors();
for (int i = 0; i < constructors.length; i++) {
    Constructor constructor = constructors[i];

    String name = constructor.getName(); // getName() 获取构造函数名称
    Class[] paramTypes = constructor.getParameterTypes(); // getParameterTypes() 获取构造函数参数列表类型的类类型
}

七、方法反射的基本操作

八、通过反射了解泛型

本人能力有限,如果此博文中有哪里讲得让人难以理解,欢迎留言交流,若有讲解错的地方欢迎指出,大家互相学期,共同进步!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值