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

相关文章
|
5月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
242 32
AQS:Java 中悲观锁的底层实现机制
|
3月前
|
人工智能 缓存 安全
Java中的反射机制:深入探索与应用
Java反射机制是程序运行时动态获取类信息并操作类成员的特性,具备高度灵活性,但也伴随性能与安全风险。本文详解反射的基本用法、高级应用及最佳实践,助你掌握这一强大工具的正确使用方式。
136 0
|
5月前
|
人工智能 Java 关系型数据库
Java——SPI机制详解
SPI(Service Provider Interface)是JDK内置的服务提供发现机制,主要用于框架扩展和组件替换。通过在`META-INF/services/`目录下定义接口实现类文件,Java程序可利用`ServiceLoader`动态加载服务实现。SPI核心思想是解耦,允许不同厂商为同一接口提供多种实现,如`java.sql.Driver`的MySQL与PostgreSQL实现。然而,SPI存在缺陷:需遍历所有实现并实例化,可能造成资源浪费;获取实现类方式不够灵活;多线程使用时存在安全问题。尽管如此,SPI仍是Java生态系统中实现插件化和模块化设计的重要工具。
159 0
|
3月前
|
人工智能 前端开发 安全
Java开发不可不知的秘密:类加载器实现机制
类加载器是Java中负责动态加载类到JVM的组件,理解其工作原理对开发复杂应用至关重要。本文详解类加载过程、双亲委派模型及常见类加载器,并介绍自定义类加载器的实现与应用场景。
185 4
|
3月前
|
人工智能 安全 Java
掌握Java反射:在项目中高效应用反射机制
Java反射是一种强大功能,允许程序在运行时动态获取类信息、创建对象、调用方法和访问字段,提升程序灵活性。它在框架开发、动态代理、注解处理等场景中广泛应用,如Spring和Hibernate。但反射也存在性能开销、安全风险和代码复杂性,应谨慎使用。
|
4月前
|
人工智能 Java
Java中的反射机制:深入探索与应用
本文介绍了Java反射机制的基本概念、用途及其实现方式。反射机制允许程序在运行时动态获取类的属性和方法,并调用它们,适用于处理私有成员或权限受限的情况。文章详细讲解了`Class`类的功能,包括获取类的方法、属性、注解、构造器等信息,以及通过四种方式获取`Class`对象的示例代码。此外,还探讨了类加载器、继承关系判断、动态代理等高级内容,展示了如何在运行时创建接口实例并处理方法调用。文末提供了完整的代码示例以加深理解。
Java中的反射机制:深入探索与应用
|
5月前
|
Java 区块链 网络架构
酷阿鲸森林农场:Java 区块链系统中的 P2P 区块同步与节点自动加入机制
本文介绍了基于 Java 的去中心化区块链电商系统设计与实现,重点探讨了 P2P 网络在酷阿鲸森林农场项目中的应用。通过节点自动发现、区块广播同步及链校验功能,系统实现了无需中心服务器的点对点网络架构。文章详细解析了核心代码逻辑,包括 P2P 服务端监听、客户端广播新区块及节点列表自动获取等环节,并提出了消息签名验证、WebSocket 替代 Socket 等优化方向。该系统不仅适用于农业电商,还可扩展至教育、物流等领域,构建可信数据链条。
|
5月前
|
人工智能 JavaScript Java
Java反射机制及原理
本文介绍了Java反射机制的基本概念、使用方法及其原理。反射在实际项目中比代理更常用,掌握它可以提升编程能力并理解框架设计原理。文章详细讲解了获取Class对象的四种方式:对象.getClass()、类.class、Class.forName()和类加载器.loadClass(),并分析了Class.forName()与ClassLoader的区别。此外,还探讨了通过Class对象进行实例化、获取方法和字段等操作的具体实现。最后从JVM类加载机制角度解析了Class对象的本质及其与类和实例的关系,帮助读者深入理解Java反射的工作原理。
104 0
|
7月前
|
缓存 Dubbo Java
理解的Java中SPI机制
本文深入解析了JDK提供的Java SPI(Service Provider Interface)机制,这是一种基于接口编程、策略模式与配置文件组合实现的动态加载机制,核心在于解耦。文章通过具体示例介绍了SPI的使用方法,包括定义接口、创建配置文件及加载实现类的过程,并分析了其原理与优缺点。SPI适用于框架扩展或替换场景,如JDBC驱动加载、SLF4J日志实现等,但存在加载效率低和线程安全问题。
277 7
理解的Java中SPI机制
|
6月前
|
存储 Java 编译器
Java 中 .length 的使用方法:深入理解 Java 数据结构中的长度获取机制
本文深入解析了 Java 中 `.length` 的使用方法及其在不同数据结构中的应用。对于数组,通过 `.length` 属性获取元素数量;字符串则使用 `.length()` 方法计算字符数;集合类如 `ArrayList` 采用 `.size()` 方法统计元素个数。此外,基本数据类型和包装类不支持长度属性。掌握这些区别,有助于开发者避免常见错误,提升代码质量。
470 1