【JavaSE】【反射】

Java反射机制允许在运行时检查和操作类、对象、方法和属性。它用于获取类信息、动态创建对象、调用方法和访问属性。反射提供了动态调用的灵活性,但牺牲了性能和安全性。文章详细介绍了反射的概念、用途、相关类的使用方法,以及反射的优缺点,包括其在类加载和执行阶段的作用。
摘要由CSDN通过智能技术生成

目录

1、概念

2、用途

Java 运行过程

3、反射相关的类 

Class 类(反射机制的起源)

1. 常用获得类相关的方法

2. 获得类中构造器相关的方法

3. 常用获得类中属性相关的方法

4. 获得类中方法相关的方法

4、反射的使用 

1. 定义一个学生类

 2. 获取 Class对象的三种方式

3.  创建对象

4. 反射私有构造器方法

 5. 反射私有属性

 6. 反射私有方法

 5、反射的优缺点

 优点

 缺点

1、概念

Java的反射(reflection)机制是在运行状态中,它允许在运行时检查和操作类、对象、方法和属性等程序结构。通过反射,可以获取程序在运行时的信息,并能够在程序运行时动态地操作对象;

这种动态获取信息以及动态调用对象方法的功能称为java语言的反射(reflection)机制。在框架中才会大量的使用反射;

静态调用 

  • 静态调用指的是在编译时就已经确定了方法的调用目标。在静态调用中,编译器会在编译期间就确定方法的签名和调用目标,并且会进行一些优化操作,例如内联、常量折叠等。
  • 静态调用的优点是速度快、效率高缺点是不够灵活,不能在运行时根据情况动态地选择方法的调用目标。
Animal animal1 = new Dog();
Animal animal2 = new Cat();
animal1.makeSound();
animal2.makeSound();

动态调用

  • 动态调用指的是在运行时才能确定方法的调用目标。在动态调用中,编译器无法确定方法的签名和调用目标,而是需要在运行时根据实际情况进行选择。
  • 动态调用的优点是灵活、适应性强,缺点是速度较慢、效率低。
Animal animal1 = new Dog();
Animal animal2 = new Cat();
//获取类型  获取方法
Method method = animal1.getClass().getMethod("makeSound");
//调用
method.invoke(animal1);
//获取类型  获取方法
method = animal2.getClass().getMethod("makeSound");
//调用
method.invoke(animal2)

2、用途

  1. 获取类的构造函数、方法和属性
  2. 实例化对象
  3. 调用方法
  4. 操作属性
  5. 动态创建类
  6. 动态代理
  7. 调用私有方法和属性
  8. 获取泛型信息

注意:反射通常比直接调用类的方法和属性要慢,并且不够安全,因此应该谨慎使用。

Java 运行过程

java 运行过程分为三个阶段:编译阶段、类加载阶段和执行阶段

1.  编译阶段Java源代码被编译器编译成字节码文件.class文件

  • Java源代码被编译器编译成字节码文件,即后缀为.class的文件
  • 编译器将Java源代码转换为JVM可以识别的字节码指令,同时检查语法和语义错误。

2.  类加载阶段JVM将字节码文件加载到内存中,并将其转换为Java对象

  • 在类加载阶段,JVM将字节码文件加载到内存中,并将其转换为Java对象。JVM会执行以下步骤:
  • 加载:通过类的全限定名找到字节码文件,并将其加载到内存中;
  • 验证:对字节码文件进行验证,确保其符合Java虚拟机规范;
  • 准备:为类的静态变量分配内存,并设置默认值;
  • 解析:将类、接口、字段和方法的符号引用解析为直接引用;
  • 初始化:执行类构造器<clinit>()方法,给类的静态变量赋初值。

3.  执行阶段

  • VM执行字节码文件中的指令,实现Java程序的功能。

Java程序的运行过程可以概括为:编写Java源代码 -> 编译 -> 类加载 -> 执行

 反射主要发挥作用的是在类加载阶段和执行阶段。

  • 类加载阶段,我们可以使用反射API获取类的信息,如类名、方法名、字段名、构造函数等;
  • 在执行阶段,我们可以使用反射API动态地创建对象、调用方法、访问字段等。

3、反射相关的类 

类名

用途

Class

代表类的实体,在运行时的java应用程序中表示类的接口

Field

代表类的成员变量/属性

Method

代表类的方法

Constructor

代表累的构造方法

Java反射机制可以访问类的私有成员(字段、方法、构造函数等),但是需要注意的是,这种访问方式不够安全,因为它可以绕过类的访问控制机制。

要反射一个私有成员,需要使用以下步骤:

1. 获取 Class 对象

  • 需要使用 Class.forName() 方法或类的 .class 语法来获取 Class 对象。

2. 获取 Field、Method 或 Constructor 对象

  • 使用 Class 类中的 getDeclaredField、getDeclaredMethod、getDeclaredConstructor 等方法来获取私有成员的 Field、Method 或 Constructor 对象。

3. 设置可访问性

  • 由于私有成员默认是不可访问的,因此需要设置它们的可访问性。可以使用 Field、Method 或 Constructor 对象的 setAccessible(true) 方法来设置可访问性。

4. 访问私有成员

  • 一旦设置了可访问性,就可以使用 Field、Method 或 Constructor 对象的 get、set、invoke 等方法来访问私有成员。

Class 类(反射机制的起源

代表类的实体,在运行时的java应用程序中表示类的接口

编译时期生成 .class文件,在类加载阶段,JVM此时就要去解读.class文件 ,被编译后的Java文件. class 也被JVM解析为一个对象,这个对象就是 java.lang.Class ;

这样当程序在运行时,每个java文件就最终变成了Class类对象的一个实例

我们通过Java的反射机制应用到这个实例,就可以去获得甚至去添加改变这个类的属性和动作使得这个类成为一个动态的类 .

1. 常用获得类相关的方法

方法

功能

getClassLoader()

获得类的加载器

getDeclaredClasses()

返回一个数组,数组中包含该类中所有类和接口类的对象(包括私有的)

forName(String className)

根据类名返回类的对象

newInstance()

创建类的实例

getName()

获得类的完整路径名字

2. 获得类中构造器相关的方法

方法

功能

getConstructor(Class...<?> parameterTypes)

获得该类中与参数类型匹配的公有构造方法

getConstructors()

获得该类的所有公有构造方法

getDeclaredConstructor(Class...<?> parameterTypes)

获得该类中与参数类型匹配的构造方法

getDeclaredConstructors()

获得该类所有构造方法

3. 常用获得类中属性相关的方法

方法

功能

getField(String name)

获得某个公有的属性对象

getFields()

获得所有公有的属性对象

getDeclaredField(String name)

获得某个属性对象

getDeclaredFields()

获得所有属性对象

4. 获得类中方法相关的方法

方法

功能

getMethod(String name, Class...<?> parameterTypes)

获得该类某个公有的方法

getMethods()

获得该类所有公有的方法

getDeclaredMethod(String name, Class...<?> parameterTypes)

获得该类某个方法

getDeclaredMethods()

获得该类所有方法

4、反射的使用 

1. 定义一个学生类

package Reflect;

class Student {
    //私有属性name
    private String name = "bit";
    //公有属性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 +
                '}';
    }
}

 2. 获取 Class对象的三种方式

1.  通过 Class.forName("类的全路径") 静态方法 获取;前提知道类的全路径

  • 可能会抛出 ClassNotFoundException 异常

2.  通过 类名.class 获得最安全可靠,程序性能高

  • 说明任何一个类都有一个隐含的静态成员变量 class
  • 仅适合在编译前就已经明确要操作的 Class

3.  getClass 方法

public static void main(String[] args) throws ClassNotFoundException {
    //1、通过 Class.forName("类的全路径") 静态方法获取
    //可能会抛出 ClassNotFoundException 异常
    Class<?> c1 = Class.forName("Reflect.Student");

    //2、通过 类名.class 获得,最安全可靠,程序性能高
    //说明任何一个类都有一个隐含的静态成员变量 class
    Class<?> c2 = Student.class;

    //3、getClass 方法
    Student student = new Student();
    Class<?> c3 = student.getClass();

    //一个 JVM 中只有一个Class 实例
    System.out.println(c1 == c2);       //true
    System.out.println(c1 == c3);       //true
    System.out.println(c2 == c2);       //true
}

Class<?> 表示一个未知的泛型类型 Class,其中的 <?> 是一个通配符,表示这个泛型类型可以接受任何类型参数。

3.  创建对象

  1. 用 Class.forName() 方法获取对象
  2. 调用 Class 类的 newInstance() 或 Constructor 类的 newInstance() 方法 创建对象
  3. 这些方法返回一个 Object 类型的对象因为在编译时无法确定具体的类类型。因此,需要在运行时根据需要将其转换为实际的类型。
public static void main(String[] args) {
    //1.创建对象
    try {
        //获取对象
        Class<?> c1 = Class.forName("Reflect.Student");
        //用Object 接受
        Object objectStudent = c1.newInstance();
        //强制类型转换
        Student student = (Student)objectStudent;
        System.out.println("学生对象:"+student);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

注意:

  1. 如果创建的对象类型与所需的类型不兼容,则会在运行时抛出 ClassCastException 异常。
  2. 因此,应该使用 instanceof 运算符检查对象的类型,以确保类型转换的安全性
//判断
Student student = null;
if (objectStudent instanceof Student) {
    //强制类型转换
    student = (Student) objectStudent;

}
System.out.println("学生对象:" + student);

4. 反射私有构造器方法

Java 9 中引入了新的方法,如 Class.getDeclaredConstructor(),它们允许在创建对象时指定参数类型,从而避免了需要在接收对象时进行类型转换的麻烦。

反射私有构造器方法 ,屏蔽内容为获得公有的构造方法

public static void reflectPrivateConstructor() {
    //2.反射私有的构造方法 屏蔽内容为获得公有的构造方法
    try {
        //获取对象
        Class<?> c1 = Class.forName("Reflect.Student");
       //创建对象时使用了这个构造函数
        Constructor<?> constructor = c1.getDeclaredConstructor(String.class,int.class);
        //修改访问权限
        constructor.setAccessible(true);
        //创建
        Student student = (Student) constructor.newInstance("hang",18);
        System.out.println("学生对象:" + student);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
        throw new RuntimeException(e);
    } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

 5. 反射私有属性

  1. 获取对象
  2. 获取属性 classTest.getDeclaredField("属性名称");
  3. 修改访问权限 .setAccessible(true);
  4. 创建对象
  5. 设置属性值 filed.set(属性,值) —— SET()
public static void reflectPrivateField(){
    //3.反射私有属性
    try {
        //获取对象
        Class<?> classTest = Class.forName("Reflect.Student");
        //获取指定属性
        Field field = classTest.getDeclaredField("name");
        //修改访问权限
        field.setAccessible(true);
        //创建对象
        Student student = (Student) classTest.newInstance();
        //设置 属性
        field.set(student,"hang");
        System.out.println("学生对象:" + student);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    } catch (NoSuchFieldException e) {
        throw new RuntimeException(e);
    } catch (InstantiationException e) {
        throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
        throw new RuntimeException(e);
    }
}

 6. 反射私有方法

  1. 获取对象
  2. 获取方法 classTest.getDeclaredMethod("方法名", 参数类型.class);
  3. 修改访问权限 .setAccessible(true);
  4. 创建对象
  5. 传参数 method.invoke(属性,参数) —— invoke()
public static void reflectPrivateMethod(){
    //4.反射私有方法
    try {
        //获取对象
        Class<?> classTest = Class.forName("Reflect.Student");
        //获取指定方法
        Method method = classTest.getDeclaredMethod("function", String.class);
        //修改访问权限
        method.setAccessible(true);
        //创建对象
        Student student = (Student) classTest.newInstance();
        //设置 属性
        method.invoke(student,"私有方法访问成功");
        System.out.println("学生对象:" + student);
    } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
    } 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);
    }
}

 5、反射的优缺点

 反射是Java语言中一项重要的特性,它提供了一种在运行时动态获取、操作类和对象的能力。反射的优点和缺点如下:

 优点

  1. 动态性:反射允许程序在运行时动态地加载、使用和创建对象,而不需要在编译期间就确定对象的类型和方法。
  2. 适应性:反射可以让程序在运行时根据不同的情况动态地选择、调用不同的方法,适应不同的需求
  3. 灵活性:反射可以让程序以通用的方式处理不同类型的对象,而不需要针对每种类型都编写专门的代码。
  4. 扩展性:反射可以扩展现有代码的功能,例如在不修改原有代码的情况下为一个类增加新的方法,通过动态代理来完成

增加程序的灵活性和扩展性,降低耦合性,提高自适应能力;

缺点

  1. 性能问题射的性能比直接调用方法或创建对象要慢,因为反射涉及到动态查找和解析类信息等操作,而这些操作需要消耗一定的时间和资源。
  2. 安全问题反射可以访问和修改对象的私有成员,这可能导致安全问题。因此,在使用反射时需要小心谨慎,确保程序的安全性。
  3. 可读性问题:使用反射可以使程序变得复杂、难以理解和维护,因为反射操作常常是在运行时动态发生的,而不是在代码中静态地声明。
  4. 代码健壮性问题:由于反射可以访问和修改对象的私有成员,可能会破坏程序的封装性和健壮性,导致程序出现意料之外的错误。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值