从初学者到专家:Java反射的完整指南

一.反射的概念及定义

Java 的反射( reflection )机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性,既然能拿到那么,我们就可以修改部分类型信息;这种动态获取信 息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。
反射的用途
  1. 在日常的第三方应用开发过程中,经常会遇到某个类的某个成员变量、方法或是属性是私有的或是只对系统 应用开放,这时候就可以利用Java的反射机制通过反射来获取所需的私有成员或是方法 。
  2. 反射最重要的用途就是开发各种通用框架,比如在spring中,我们将所有的类Bean交给spring容器管理,无 论是XML配置Bean还是注解配置,当我们从容器中获取Bean来依赖注入时,容器会读取配置,而配置中给的就是类的信息,spring根据这些信息,需要创建那些Beanspring就动态的创建这些类。

反射不同时期的类型

Java程序中许多对象在运行时会出现两种类型:运行时类型(RTTI)和编译时类型。

例如:Person p = new Student();

  • 编译时类型是在编译时期确定的,它是变量声明时所使用的类型。在上面的示例代码中,变量p的编译时类型是Person,因为它是通过Person p的声明来定义的。
  • 运行时类型是在程序运行时确定的,它是实际分配给对象的类型。在上面的示例代码中,使用new Student()创建了一个Student对象,并将其赋值给变量p,因此在运行时,p的类型是Student

通过反射,可以获取对象的运行时类型。例如,可以使用p.getClass()方法获取p对象的实际类型(运行时类型),并进行相应的操作。

除了获取对象的运行时类型,反射还可以获取类的详细信息,包括类的名称、父类、接口、构造函数、字段和方法等。通过获取类的信息,可以做一些动态的操作,如动态创建对象、调用方法、访问字段等。

注意:Java文件被编译后,生成了.class文件,JVM此时就要去解读.class文件 ,被编译后的Java文件.class也被JVM解析为一个对象,这个对象就是 java.lang.Class .这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例。我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作,使得这个类成为一个动态的类 .

二.反射相关的示例

在Java中,反射通过java.lang.reflect包中的类和接口来实现。

反射的基本信息:

  1. ClassClass是反射的核心类之一,它表示一个类或接口的运行时对象。通过Class类可以获取和操作类的信息,如类的名称、父类、接口、构造函数、字段和方法等。

  2. ConstructorConstructor类表示类的构造函数。通过Constructor类可以创建类的实例,调用构造函数并实例化对象。

  3. Field类:Field类表示类的字段(成员变量)。通过Field类可以获取和修改字段的值,以及访问字段的属性信息。

  4. Method类:Method类表示类的方法。通过Method类可以调用类的方法,传递参数并获取返回值。

  5. 获取Class对象:可以使用多种方式获取Class对象,如使用类名调用Class.forName()方法、通过类的实例调用getClass()方法、或者直接通过类字面常量使用SomeClass.class

  6. 实例化对象:通过Class对象和Constructor类可以实例化类的对象。可以使用newInstance()方法创建无参构造函数的实例,或者使用Constructor类的newInstance()方法传递参数创建有参构造函数的实例。

  7. 调用方法:通过Class对象和Method类可以调用类的方法。可以使用invoke()方法传递对象和参数来调用方法,并获取返回值。

  8. 访问字段:通过Class对象和Field类可以访问类的字段。可以使用get()set()方法获取和修改字段的值,以及使用getField()getDeclaredField()方法获取字段对象。


 

 2.1获取Class对象的三种方式

在使用反射时,我们需要先获取要反射的类的  Class 对象,因为  Class 对象提供了许多有用的方法和操作,用于在运行时检查和操作类的结构、属性和方法。
你可以这样理解:当我们使用反射时,需要先创建类的  Class 对象,这个对象相当于是类的身份证。通过这个身份证,我们可以了解这个类的各种信息。

通过 Class 对象,我们可以做以下事情:

  • 创建对象实例:通过 Class 对象的 newInstance() 方法可以动态地创建类的对象实例。

  • 获取类的信息:通过 Class 对象可以获取类的名称、修饰符、包名、父类、接口、字段和方法等信息。

  • 获取和设置字段值:通过 Class 对象和字段名称,可以获取和设置类的字段的值。

  • 调用方法:通过 Class 对象和方法名称,可以调用类的方法。

  • 动态加载类:通过 Class.forName() 方法可以在运行时动态加载类。

  • 进行注解处理:通过 Class 对象可以获取类上的注解信息,并进行相应的处理。

通过先创建类的  Class 对象,我们可以在运行时对类进行检查和操作,而无需提前知道类的具体信息。这使得代码更加灵活,可以在运行时根据需要动态地处理和操作类。
举例子解释:
1.先创建出Student类:
package demo1;

/**
 * @Author 12629
 * @Description:
 */
class Student {
    //私有属性name
    private String name = "Classmates";
    //公有属性age
    public int age = 18;
    //不带参数的构造方法
    public Student(){
        System.out.println("Student()");
    }

    private Student(String name,int age) {
        this.name = name;
        this.age = age;
        System.out.println("Student(String,name)");
    }

    private void eat(){
        System.out.println("i am eat");
    }

    public void sleep(){
        System.out.println("i am pig");
    }

    private void function(String str) {
        System.out.println(str);
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

第一种,使用 Class.forName("类的全路径名"); 静态方法。(前提:已明确类的全路径名。)

public class Test {

    public static void main(String[] args) {
        //获取Class对象有三种方式之一
        //第一种
        Class<Student> c1 = null;
        try {
            c1 = Class.forName("demo1.Student");
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        
    }
}

这段代码的作用是将字符串 "demo1.Student" 作为参数传递给 Class.forName() 方法,并将返回的 Class 对象赋值给变量 c1

为什么有调异常呢?

这是因为 Class.forName() 方法尝试根据提供的类名加载对应的类,但如果在类路径中找不到该类,就会抛出 ClassNotFoundException 异常。

注意:使用 Class.forName() 方法时,需要提供类的全限定名,即包括包名和类名。


 

第二种:使用 .class 方法。(仅适合在编译前就已经明确要操作的 Class)

public class Test {

    public static void main(String[] args) {
        //第二种
        Class<Student> c2 = Student.class;

    }
}

段代码的作用是通过类字面常量 Student.class 来获取 Student 类的 Class 对象,并将其赋值给变量 c2

使用类字面常量的方式非常简单,只需要在类名后面加上 .class 就可以直接访问该类的 Class 对象。


第三种 ,使用类对象的 getClass() 方法

public class Test {

        //第三种
        Student student = new Student();
Class<? extends Student> c3 = student.getClass();

    }

我们创建了一个 Student 类的对象 student,然后通过 student.getClass() 方法获取该对象的运行时类的 Class 对象,并将其赋值给类型为 Class<? extends Student> 的变量 c3

使用对象的 getClass() 方法可以在运行时获取对象所属类的 Class 对象。这种方式适用于当我们有一个对象实例,想要获取其对应的类信息时。

 


注意:一个类在 JVM 中只会有一个 Class 实例

public class Test {

    /*
    Class对象 只有一个
     */
    public static void main(String[] args) {
        //获取Class对象有三种方式
        //生成的对象只有一个
        //第一种
        Class<Student> c1 = null;
        try {
            c1 = Class.forName("demo1.Student");
        }catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        //第二种
        Class<Student> c2 = Student.class;

        //第三种
        Student student = new Student();
Class<? extends Student> c3 = student.getClass();

        System.out.println(c1 == c2);
        System.out.println(c1 == c3);
    }
}

运行例图如下: 


2.2 Class对象的使用 

上一步我们已经创建好 Class 对象了,可以使用它来进行各种操作。

注意:所有和反射相关的包都在 import java.lang.reflect 包下面。

 

使用前我们先了解Class类中的相关的方法有哪些
1.常用获得类相关的方法:

 2.常用获得类中属性相关的方法(以下方法返回值为Field相关):

3. 获得类中构造器相关的方法(以下方法返回值为Constructor相关

4.

4.获得类中方法相关的方法(以下方法返回值为Method相关 

5.获得类中注解相关的方法 (了解即可)

代码案例如下:

package demo1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectClassDemo {

/*reflectNewInstance() 方法使用反射创建一个类的实例,然后输出该实例。

它通过 Class.forName() 方法获取类的 Class 对象,然后使用 newInstance() 方法创建实例。
*/

    public static void reflectNewInstance() {
        Class<?> classStudent = null;
        try {
            // 获取类的Class对象
            classStudent = Class.forName("demo1.Student");
            // 使用newInstance()方法创建类的实例
            Student student = (Student) classStudent.newInstance();
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
/*
reflectPrivateConstructor() 方法演示了如何使用反射调用私有的构造方法。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredConstructor() 方法获取私有构造方法,并通过 setAccessible(true) 设置访问权限。
最后,使用 newInstance() 方法创建实例并输出。
*/

    public static void reflectPrivateConstructor() {
        Class<?> classStudent = null;
        try {
            classStudent = Class.forName("demo1.Student");
            // 获取私有构造方法
            Constructor<?> constructor = classStudent.getDeclaredConstructor(String.class, int.class);
            constructor.setAccessible(true); // 设置私有构造方法可访问
            // 调用私有构造方法创建实例
            Student student = (Student) constructor.newInstance("xiaoming", 15);
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
/*
reflectPrivateField() 方法展示了如何使用反射操作私有字段。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredField() 方法获取私有字段,并通过 setAccessible(true) 设置访问权限。
接下来,使用 set() 方法给字段设置新的值,并输出结果。
*/
    public static void reflectPrivateField() {
        Class<?> classStudent = null;
        try {
            classStudent = Class.forName("demo1.Student");
            // 获取私有字段
            Field field = classStudent.getDeclaredField("name");
            field.setAccessible(true); // 设置私有字段可访问(这个特别要注意,不然会报异常)
            // 创建类的实例
            Student student = (Student) classStudent.newInstance();
            // 设置私有字段的值
            field.set(student, "caocao");
            System.out.println(student);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchFieldException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }
/*
reflectPrivateMethod() 方法演示了如何使用反射调用私有方法。
它通过 Class.forName() 方法获取类的 Class 对象,然后使用 getDeclaredMethod() 方法获取私有方法,并通过 setAccessible(true) 设置访问权限。
最后,使用 invoke() 方法调用方法并输出结果。
*/
    public static void reflectPrivateMethod() {
        Class<?> classStudent = null;
        try {
            classStudent = Class.forName("demo1.Student");
            // 获取私有方法
            Method method = classStudent.getDeclaredMethod("function", String.class);
            method.setAccessible(true); // 设置私有方法可访问
            // 创建类的实例
            Student student = (Student) classStudent.newInstance();
            // 调用私有方法
            method.invoke(student, "我是一个反射的参数!");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            throw new RuntimeException(e);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
    // 反射实例化类
     reflectNewInstance();

    // 反射调用私有构造方法
     reflectPrivateConstructor();

    // 反射操作私有字段
     reflectPrivateField();

    //反射调用私有方法
    reflectPrivateMethod();

   }
}

 运行例图如下:

三.总结

反射的优缺点如下:

优点:

  1. 动态性和灵活性:反射允许在运行时动态地获取和操作类的信息,使程序能够根据需要适应不同的情况和需求。它提供了灵活的实例化、字段访问和方法调用,以及动态代理和处理注解等功能,增强了程序的灵活性和可扩展性。

  2. 泛型操作:反射使得可以在运行时获取泛型类型的信息,并进行相应的操作。这对于编写通用代码和框架非常有用,可以在不知道具体类型的情况下进行更多的操作和处理。

  3. 框架和库的开发:反射广泛应用于框架和库的开发中。通过反射,可以在运行时动态地加载和使用类,根据配置文件或用户输入进行相应的操作,使框架和库具有更强的扩展性和适应性。

缺点:

  1. 性能影响:反射的操作通常比直接调用方法或访问字段的性能要低。使用反射会引入额外的开销,包括方法调用和类型检查等。因此,频繁使用反射可能导致程序的性能下降。

  2. 安全性问题:反射可以绕过访问权限的限制,可以访问和修改私有成员,并执行敏感操作。这可能导致安全性问题,特别是在处理不受信任的代码或用户输入时需要格外小心。

  3. 编码复杂性和可读性降低:反射的使用可能会增加代码的复杂性和可读性降低。由于反射是在运行时动态进行的,因此一些问题只能在运行时才能被发现,而不是在编译时。这可能导致调试和维护过程中的困难。

  4. 局限性:反射有一些局限性,例如无法操作编译时不存在的类、字段或方法;无法操作原始类型的字段等。此外,由于反射是基于运行时信息的,因此在某些情况下可能无法获得期望的结果。

反射是Java语言中的一项强大特性,它允许程序在运行时动态地获取、操作和修改类、对象、字段和方法的信息。通过反射,我们可以实现灵活的类实例化、字段访问和方法调用,以及处理注解和实现动态代理等功能。然而,反射的使用应谨慎,需要平衡灵活性、性能和安全性,并注意其局限性和注意事项。总而言之,反射为Java开发者提供了强大的工具,使得程序可以在运行时动态地适应不同的需求和场景。

  • 49
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 62
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 62
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值