Java反射机制(1)

简介: Java反射机制

反射

反射允许对成员变量,成员方法和构造方法的信息进行编程访问

获取class对象

反射是通过class字节码来获取哪些信息的

类加载各阶段完成的功能

加载阶段:将类的 class 文件读入内存,并为之创建一个 java.lang.Class 对象,此过程由类加载器完成。

连接阶段:又分为验证、准备、解析三个小阶段,此阶段会将类的二进制数据合并到 JRE 中。

初始化阶段:JVM 负责对类的静态成员进行初始化。

如下图所示:

加载阶段

JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、jar 包、甚至网络文件)转换为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。

5 连接阶段——验证
5.3 连接阶段——准备

JVM 会在该阶段对静态变量分配内存并进行默认初始化(不同数据类型会有其默认初始值,如:int ---- 0,boolean ---- false 等)。这些变量的内存空间会在方法区中分配。

举例如下:

public class ClassLoad {
    public static void main(String[] args) {
        // 属性=成员变量=字段
        // 类加载的连接阶段-准备,属性是如何加载的
        public int n1 = 10;
        public static  int n2 = 20;
        public static final  int n3 = 30;
    }
}

代码说明:

n1 是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存

n2 是静态变量,在该阶段 JVM 会为其分配内存,n2 默认初始化的值为 0 ,而不是 20

n3 被 static final 修饰,是常量, 它和静态变量不一样, 其一旦赋值后值就不变,因此其默认初始化 n3 = 30

连接阶段——解析

JVM 将常量池内的符号引用替换为直接引用的过程。

初始化阶段

在初始化阶段,JVM 才会真正执行类中定义的 Java程序代码,此阶段是执行() 方法的过程。

() 方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并进行合并的过程。

JVM 会保证一个类的 () 方法 在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 () 方法,其他线程都要阻塞等待,直到活动线程执行 () 方法完毕。

举例如下:

public class ClassLoad {
    public static void main(String[] args) throws ClassNotFoundException {
        System.out.println(B.num);// 直接使用类的静态属性,也会导致类的加载
    }
}
class B {
    static { // 静态代码块
        System.out.println("B 静态代码块被执行");
        num = 300;
    }
static int num = 100;// 静态变量
public B() {// 构造器
    System.out.println("B() 构造器被执行");
}

输出如下:

B 静态代码块被执行
100

注意没有使用到类,类不会被加载的

代码说明:

  1. 加载阶段:加载 B类,并生成 B的 class对象
  2. 连接阶段:进行默认初始化 num = 0
  3. 初始化阶段:执行 () 方法,该方法会依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并合并。如下:
clinit() {
  System.out.println("B 静态代码块被执行");
    num = 300;
    num = 100;
}
  • 合并后: num = 100

注意:加载类的时候,具有同步机制控制。如下:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
  //正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象
    synchronized (getClassLoadingLock(name)) {
      //....
    }
}

访问字段

对任意的一个Object实例,只要我们获取了它对应的Class类对象,就可以获取它的一切信息。

我们先看看如何通过Class类对象获取其对应的类定义的字段信息。Class类提供了以下几个方法来获取字段:

Field getField(name):根据字段名获取某个 public 的 field(包括父类)

Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类)

Field[] getFields():获取所有 public 的 field(包括父类)

Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)

示例:

public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}
class Student extends Person {
    public int score;
    private int grade;
}
class Person {
    public String name;
}
  • 上述代码首先获取StudentClass实例,然后,分别获取public字段、继承的public字段以及private字段,打印出的Field类似下面:
public int Student.score
public java.lang.String Person.name
private int Student.grade

一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,“name”;
  • getType():返回字段类型,也是一个Class类对象,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的 bit 表示不同的含义。

String类的value字段为例,它的定义是:

public final class String {
    private final byte[] value;
}

我们用反射获取该字段的信息,代码如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false
1. 获取字段值

利用反射拿到字段的一个Field类对象只是第一步,我们还可以拿到一个实例对象对应的该字段的值。

例如,对于一个Person类对象,我们可以先拿到其name字段对应的Field,再获取这个Person类对象的name字段的 值:

import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");// 获取 private String name;
        Object value = f.get(p);
        System.out.println(value); // "Xiao Ming"
    }
}
class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }
}

上述代码先获取Person类对应的Class类对象,再通过该Class类对象获取Field类对象,然后,用Field.get(Object)获取指定Person类对象的指定字段的值。

运行代码,如果不出意外,会得到一个IllegalAccessException异常,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get§;前,先写一句:

f.setAccessible(true);

调用Field.setAccessible(true)的意思是,别管这个字段是不是public,一律允许访问。

可以试着加上上述语句,再运行代码,就可以打印出private字段的值。

有童鞋会问:如果使用反射可以获取private字段的值,那么类的封装还有什么意义?

  • 答案是一般情况下,我们总是通过p.name来访问Person的name字段,编译器会根据public、protected和private这些访问权限修饰符决定是否允许访问字段,这样就达到了数据封装的目的。
  • 而反射是一种非常规的用法,使用反射,首先代码非常繁琐;其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标对象任何信息的情况下,获取特定字段的值。

此外,setAccessible(true)可能会失败。 如果 JVM 运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对java和javax开头的package的类调用setAccessible(true),这样可以保证 JVM 核心库的安全。

2.设置字段值

通过 Field 类对象既然可以获取到指定对象的字段值,自然也可以设置字段的值。

设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的对象,第二个Object参数是待修改的值。示例代码如下:

import java.lang.reflect.Field;
public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("people");
        f.setAccessible(true);// 获取 private String name;
//        Object value = f.get(p);
        f.set(p,new People());
        System.out.println(f.get(p)); // "Xiao Ming"
    }
}
class Person {
    protected String name;
    private    People people;
    public Person(String name) {
        this.name = name;
    }
}
class  People{
    private String name;
    public People(){ this.name="qq";}
    @Override
    public String toString() {
        return name;
    }
}

可以通过反射给Person的字段赋值

但如果赋值的类型不是Person字段的类型的呢

这里我给People people 赋值的是字符串而不是People类型

public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("people");
        f.setAccessible(true);// 获取 private String name;
//        Object value = f.get(p);
        f.set(p,"new People()");
        System.out.println(f.get(p)); // "Xiao Ming"
    }
3.小结

Java 的反射 API 提供的Field类封装了对应的类定义的全部字段的所有信息:

通过Class类对象的方法可以获取Field类对象:getField(),getFields(),getDeclaredField(),getDeclaredFields();

通过Field类对象可以获取类定义字段信息:getName(),getType(),getModifiers();

通过Field类对象可以读取或设置某个对象的字段的值,如果存在访问限制,则需要调用setAccessible(true)来访问非public字段。

通过反射读写字段是一种非常规的方法,它会破坏对象的封装。

调用方法

我们已经能通过Class类的Field类对象获取其对应的类class中定义的所有字段信息,同样的,可以通过Class类获取所有Method信息。Class类提供了以下几个方法来获取类class中定义的Method:

Method getMethod(name, Class…):获取某个public的Method(包括父类)

Method getDeclaredMethod(name, Class…):获取当前类的某个Method(不包括父类)

Method[] getMethods():获取所有public的Method(包括父类)

Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

public class Main {
    public static void main(String[] args) throws Exception {
        Class stdClass = Student.class;
        // 获取 public方法 getScore,形参类型为 String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 获取继承的 public方法 getName,无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取 private方法 getGrade,形参类型为 int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}
class Student extends Person {
    public int getScore(String type) {
        return 99;
    }
    private int getGrade(int year) {
        return 1;
    }
}
class Person {
    public String getName() {
        return "Person";
    }
}

一个Method类对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:“getScore”;
  • getReturnType():返回方法的返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
  • getModifiers():返回方法的修饰符,它是一个int,不同的 bit 表示不同的含义。

Java反射机制(2)https://developer.aliyun.com/article/1530900

相关文章
|
4天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
8天前
|
Java 编译器
探索Java中的异常处理机制
【10月更文挑战第35天】在Java的世界中,异常是程序运行过程中不可避免的一部分。本文将通过通俗易懂的语言和生动的比喻,带你了解Java中的异常处理机制,包括异常的类型、如何捕获和处理异常,以及如何在代码中有效地利用异常处理来提升程序的健壮性。让我们一起走进Java的异常世界,学习如何优雅地面对和解决问题吧!
|
29天前
|
存储 算法 Java
Java HashSet:底层工作原理与实现机制
本文介绍了Java中HashSet的工作原理,包括其基于HashMap实现的底层机制。通过示例代码展示了HashSet如何添加元素,并解析了add方法的具体过程,包括计算hash值、处理碰撞及扩容机制。
|
19天前
|
XML 安全 Java
Java反射机制:解锁代码的无限可能
Java 反射(Reflection)是Java 的特征之一,它允许程序在运行时动态地访问和操作类的信息,包括类的属性、方法和构造函数。 反射机制能够使程序具备更大的灵活性和扩展性
33 5
Java反射机制:解锁代码的无限可能
|
7天前
|
Java 数据库连接 开发者
Java中的异常处理机制及其最佳实践####
在本文中,我们将探讨Java编程语言中的异常处理机制。通过深入分析try-catch语句、throws关键字以及自定义异常的创建与使用,我们旨在揭示如何有效地管理和响应程序运行中的错误和异常情况。此外,本文还将讨论一些最佳实践,以帮助开发者编写更加健壮和易于维护的代码。 ####
|
13天前
|
安全 IDE Java
Java反射Reflect机制详解
Java反射(Reflection)机制是Java语言的重要特性之一,允许程序在运行时动态地获取类的信息,并对类进行操作,如创建实例、调用方法、访问字段等。反射机制极大地提高了Java程序的灵活性和动态性,但也带来了性能和安全方面的挑战。本文将详细介绍Java反射机制的基本概念、常用操作、应用场景以及其优缺点。 ## 基本概念 ### 什么是反射 反射是一种在程序运行时动态获取类的信息,并对类进行操作的机制。通过反射,程序可以在运行时获得类的字段、方法、构造函数等信息,并可以动态调用方法、创建实例和访问字段。 ### 反射的核心类 Java反射机制主要由以下几个类和接口组成,这些类
32 2
|
18天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
22 3
|
18天前
|
安全 Java UED
深入理解Java中的异常处理机制
【10月更文挑战第25天】在编程世界中,错误和意外是不可避免的。Java作为一种广泛使用的编程语言,其异常处理机制是确保程序健壮性和可靠性的关键。本文通过浅显易懂的语言和实际示例,引导读者了解Java异常处理的基本概念、分类以及如何有效地使用try-catch-finally语句来处理异常情况。我们将从一个简单的例子开始,逐步深入到异常处理的最佳实践,旨在帮助初学者和有经验的开发者更好地掌握这一重要技能。
19 2
|
20天前
|
Java 数据库连接 开发者
Java中的异常处理机制####
本文深入探讨了Java语言中异常处理的核心概念,通过实例解析了try-catch语句的工作原理,并讨论了finally块和throws关键字的使用场景。我们将了解如何在Java程序中有效地管理错误,提高代码的健壮性和可维护性。 ####
|
21天前
|
Java
Java中的反射机制与应用实例
【10月更文挑战第22天】Java作为一门面向对象的编程语言,提供了丰富的特性来支持对象的创建、操作和交互。其中,反射机制是Java的一项核心特性,它允许程序在运行时动态地获取类的信息、创建对象、调用方法、访问属性等。本文将从三个部分探讨Java中的反射机制及其应用实例:一是反射机制的基本概念和原理;二是反射机制在Java中的应用场景;三是通过实例深入理解反射机制的使用方法和技巧。
17 4