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

相关文章
|
14天前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
55 0
|
12天前
|
Java 程序员
深入理解Java异常处理机制
【9月更文挑战第20天】在Java编程世界中,异常处理是一项基础而重要的技能。本文将通过通俗易懂的语言和生动的比喻,带你走进Java异常的世界,了解它们的本质、分类以及如何优雅地处理这些不请自来的特殊“客人”。从简单的try-catch语句到复杂的异常链追踪,我们将一步步揭开异常处理的面纱,让你在遇到问题时不再手足无措。
41 21
|
2天前
|
Java 程序员 开发者
深入理解Java中的异常处理机制
【9月更文挑战第31天】在Java编程中,异常处理是维护程序健壮性的关键。本文将通过浅显易懂的语言和生动的例子,带你了解Java异常处理的基本概念、分类以及如何优雅地处理它们。从初学者到资深开发者,每个人都能从中获得新的洞见和技巧,让你的代码更加健壮和易于维护。
10 4
|
5天前
|
Java 数据库连接
深入理解Java异常处理机制
【9月更文挑战第28天】在Java编程中,异常处理是确保程序健壮性的关键。本文通过浅显易懂的语言和生动的例子,带你一步步了解Java的异常处理机制。从try-catch-finally的基本用法,到自定义异常类,再到异常处理的最佳实践,我们将一起探索如何在代码中优雅地处理那些不期而遇的小插曲。
13 4
|
7天前
|
Java 程序员 数据库连接
Java中的异常处理机制:理解与实践
本文将深入探讨Java语言中异常处理的核心概念、重要性以及应用方法。通过详细解析Java异常体系结构,结合具体代码示例,本文旨在帮助读者更好地理解如何有效利用异常处理机制来提升程序的健壮性和可维护性。
|
7天前
|
Java 开发者 UED
深入理解Java中的异常处理机制
本文旨在通过通俗易懂的语言,详细解析Java异常处理的核心概念及应用。从异常的基本分类到具体处理方法,再到最佳实践和常见误区,一步步引领读者深入理解这一关键技术,提升编程质量和效率。
15 2
|
7天前
|
Java 程序员 数据库连接
深入理解Java中的异常处理机制
【9月更文挑战第25天】在Java的海洋中航行,不可避免地会遇到异常的风暴。本文将作为你的航海图,指引你穿越异常处理的迷雾,让你学会如何使用try-catch语句、finally块以及throw和throws关键字来驾驭这些风暴。我们将一起探索自定义异常的岛屿,并了解如何创建和使用它们。准备好了吗?让我们启航,确保你的代码不仅能够抵御异常的狂澜,还能优雅地处理它们。
|
7天前
|
Java 开发者
Java中的异常处理机制深度解析
在Java编程中,异常处理是保证程序稳定性和健壮性的重要手段。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获与处理、自定义异常以及一些最佳实践。通过详细讲解和代码示例,帮助读者更好地理解和应用这一机制,提升代码质量。
12 1
|
13天前
|
Java 程序员 数据库连接
深入理解Java中的异常处理机制
【9月更文挑战第20天】在Java编程的世界中,异常处理是一块不可忽视的拼图。本文将带你深入探讨Java的异常处理机制,从异常的基础概念到高级应用,通过实际代码示例,揭示如何优雅地管理程序中的错误和异常情况。我们将一起学习如何使用try-catch语句捕获异常,了解finally块的重要性,以及何时使用throws关键字。此外,我们还会探索自定义异常类的创建和利用,以及最佳实践来优化你的异常处理策略。无论你是Java新手还是有经验的开发者,这篇文章都将为你提供有价值的见解和技巧,帮助你编写更加健壮和易于维护的代码。
23 7
|
8天前
|
Java 数据库连接 开发者
深入理解Java中的异常处理机制
本文旨在全面解析Java的异常处理机制,从基础概念到高级应用,逐步揭示其在软件开发中的重要性。通过实例分析,帮助读者更好地理解和运用异常处理,提升代码的健壮性和可维护性。
下一篇
无影云桌面