首页 > 编程语言 >java反射机制的作用

java反射机制的作用

时间:2022-10-25 22:57:37浏览次数:52  
标签:反射 java 构造方法 获取 Student 机制 Class

反射(Reflection) 是 Java 程序开发语言的特征之一,它允许运行中的 Java 程序对自身进行检查,或者说“自审”,并能直接操作程序的内部属性和方法。

反射是一项高级开发人员应该掌握的“黑科技”,其实反射并不是 Java 独有的,许多编程语言都提供了反射功能。在面试中面试官也经常对反射问题进行考察,反射是所有注解实现的原理,尤其在框架设计中,有不可替代的作用。关于反射,常见的面试考察点包括:

  • 如何反射获取 Class 对象
  • 如何反射获取类中的所有字段
  • 如何反射获取类中的所有构造方法
  • 如何反射获取类中的所有非构造方法

为什么要有反射

有的同学可能会疑惑,Java 已经有了封装为什么还要有反射呢?反射看起来像是破坏了封装性。甚至让私有变量都可以被外部访问到,使得类变得不那么安全了。我们来看一下 Oracle 官方文档中对反射的描述:
从 Oracle 官方文档中可以看出,反射主要应用在以下几方面:

  • 反射让开发人员可以通过外部类的全路径名创建对象,并使用这些类,实现一些扩展的功能。
  • 反射让开发人员可以枚举出类的全部成员,包括构造函数、属性、方法。以帮助开发者写出正确的代码。
  • 测试时可以利用反射 API 访问类的私有成员,以保证测试代码覆盖率。

也就是说,Oracle 希望开发者将反射作为一个工具,用来帮助程序员实现本不可能实现的功能(perform operations which would otherwise be impossible)。正如《人月神话》一书中所言:软件工程没有银弹。很多程序架构,尤其是三方框架,无法保证自己的封装是完美的。如果没有反射,对于外部类的私有成员,我们将一筹莫展,所以我们有了反射这一后门,为程序设计提供了更大的灵活性。工具本身并没有错,关键在于如何正确地使用。

反射 API

Java 类的成员包括以下三类:属性字段、构造函数、方法。反射的 API 也是与这几个成员相关:

  • Field 类:提供有关类的属性信息,以及对它的动态访问权限。它是一个封装反射类的属性的类。
  • Constructor 类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类。
  • Method 类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的一个类。
  • Class 类:表示正在运行的 Java 应用程序中的类的实例。
  • Object 类:Object 是所有 Java 类的父类。所有对象都默认实现了 Object 类的方法。

接下来,我们通过一个典型的例子来学习反射。先做准备工作,新建 com.test.reflection 包,在此包中新建一个 Student 类:

package com.test.reflection;

public class Student {

    private String studentName;
    public int studentAge;

    public Student() {
    }

    private Student(String studentName) {
        this.studentName = studentName;
    }

    public void setStudentAge(int studentAge) {
        this.studentAge = studentAge;
    }

    private String show(String message) {
        System.out.println("show: " + studentName + "," + studentAge + "," + message);
        return "testReturnValue";
    }
}

可以看到,Student 类中有两个字段、两个构造方法、两个函数,且都是一个私有,一个公有。由此可知,这个测试类基本涵盖了我们平时常用的所有类成员。

3.1 获取 Class 对象的三种方式

获取 Class 对象有三种方式:

// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.通过类的class属性
Class studentClass2 = Student.class;
// 3.通过对象的getClass()函数
Student studentObject = new Student();
Class studentClass3 = studentObject.getClass();
  • 第一种方法是通过类的全路径字符串获取 Class 对象,这也是我们平时最常用的反射获取 Class 对象的方法;
  • 第二种方法有限制条件:需要导入类的包;
  • 第三种方法已经有了 Student 对象,不再需要反射。

通过这三种方式获取到的 Class 对象是同一个,也就是说 Java 运行时,每一个类只会生成一个 Class 对象。

我们将其打印出来测试一下:

System.out.println("class1 = " + studentClass + "\n" +
        "class2 = " + studentClass2 + "\n" +
        "class3 = " + studentClass3 + "\n" +
        "class1 == class2 ? " + (studentClass == studentClass2) + "\n" +
        "class2 == class3 ? " + (studentClass2 == studentClass3));

运行程序,输出如下:

class1 = class com.test.reflection.Student
class2 = class com.test.reflection.Student
class3 = class com.test.reflection.Student
class1 == class2 ? true
class2 == class3 ? true

OK,拿到 Class 对象之后,我们就可以为所欲为啦!

3.2 获取成员变量

获取字段有两个 API:getDeclaredFieldsgetFields。他们的区别是:getDeclaredFields用于获取所有声明的字段,包括公有字段和私有字段,getFields仅用来获取公有字段:

// 1.获取所有声明的字段
Field[] declaredFieldList = studentClass.getDeclaredFields();
for (Field declaredField : declaredFieldList) {
    System.out.println("declared Field: " + declaredField);
}
// 2.获取所有公有的字段
Field[] fieldList = studentClass.getFields();
for (Field field : fieldList) {
    System.out.println("field: " + field);
}

运行程序,输出如下:

declared Field: private java.lang.String com.test.reflection.Student.studentName
declared Field: public int com.test.reflection.Student.studentAge
field: public int com.test.reflection.Student.studentAge

3.3 获取构造方法

获取构造方法同样包含了两个 API:用于获取所有构造方法的 getDeclaredConstructors和用于获取公有构造方法的getConstructors:

// 1.获取所有声明的构造方法
Constructor[] declaredConstructorList = studentClass.getDeclaredConstructors();
for (Constructor declaredConstructor : declaredConstructorList) {
    System.out.println("declared Constructor: " + declaredConstructor);
}
// 2.获取所有公有的构造方法
Constructor[] constructorList = studentClass.getConstructors();
for (Constructor constructor : constructorList) {
    System.out.println("constructor: " + constructor);
}

运行程序,输出如下:

declared Constructor: public com.test.reflection.Student()
declared Constructor: private com.test.reflection.Student(java.lang.String)
constructor: public com.test.reflection.Student()

3.4.获取非构造方法

同样地,获取非构造方法的两个 API 是:获取所有声明的非构造函数的 getDeclaredMethods 和仅获取公有非构造函数的 getMethods

// 1.获取所有声明的函数
Method[] declaredMethodList = studentClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethodList) {
    System.out.println("declared Method: " + declaredMethod);
}
// 2.获取所有公有的函数
Method[] methodList = studentClass.getMethods();
for (Method method : methodList) {
    System.out.println("method: " + method);
}

运行程序,输出如下:

declared Method: public void com.test.reflection.Student.setStudentAge(int)
declared Method: private java.lang.String com.test.reflection.Student.show(java.lang.String)
method: public void com.test.reflection.Student.setStudentAge(int)
method: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
method: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
method: public final void java.lang.Object.wait() throws java.lang.InterruptedException
method: public boolean java.lang.Object.equals(java.lang.Object)
method: public java.lang.String java.lang.Object.toString()
method: public native int java.lang.Object.hashCode()
method: public final native java.lang.Class java.lang.Object.getClass()
method: public final native void java.lang.Object.notify()
method: public final native void java.lang.Object.notifyAll()

从输出中我们看到,getMethods 方法不仅获取到了我们声明的公有方法setStudentAge,还获取到了很多 Object 类中的公有方法。这是因为我们前文已说到:Object 是所有 Java 类的父类。所有对象都默认实现了 Object 类的方法。 而getDeclaredMethods是无法获取到父类中的方法的。

实践

学以致用,让我们来一个实际的应用感受一下。还是以 Student 类为例,如果此类在其他的包中,并且我们的需求是要在程序中通过反射获取他的构造方法,构造出 Student 对象,并且通过反射访问他的私有字段和私有方法。那么我们可以这样做:

// 1.通过字符串获取Class对象,这个字符串必须带上完整路径名
Class studentClass = Class.forName("com.test.reflection.Student");
// 2.获取声明的构造方法,传入所需参数的类名,如果有多个参数,用','连接即可
Constructor studentConstructor = studentClass.getDeclaredConstructor(String.class);
// 如果是私有的构造方法,需要调用下面这一行代码使其可使用,公有的构造方法则不需要下面这一行代码
studentConstructor.setAccessible(true);
// 使用构造方法的newInstance方法创建对象,传入构造方法所需参数,如果有多个参数,用','连接即可
Object student = studentConstructor.newInstance("NameA");
// 3.获取声明的字段,传入字段名
Field studentAgeField = studentClass.getDeclaredField("studentAge");
// 如果是私有的字段,需要调用下面这一行代码使其可使用,公有的字段则不需要下面这一行代码
// studentAgeField.setAccessible(true);
// 使用字段的set方法设置字段值,传入此对象以及参数值
studentAgeField.set(student,10);
// 4.获取声明的函数,传入所需参数的类名,如果有多个参数,用','连接即可
Method studentShowMethod = studentClass.getDeclaredMethod("show",String.class);
// 如果是私有的函数,需要调用下面这一行代码使其可使用,公有的函数则不需要下面这一行代码
studentShowMethod.setAccessible(true);
// 使用函数的invoke方法调用此函数,传入此对象以及函数所需参数,如果有多个参数,用','连接即可。函数会返回一个Object对象,使用强制类型转换转成实际类型即可
Object result = studentShowMethod.invoke(student,"message");
System.out.println("result: " + result);

程序的逻辑注释已经写得很清晰了,我们再梳理一下:

  1. 先用第一种全路径获取 Class 的方法获取到了 Student 的 Class 对象
  2. 然后反射调用它的私有构造方法 private Student(String studentName),构建出 newInstance
  3. 再将其公有字段 studentAge 设置为 10
  4. 最后反射调用其私有方法 show,传入参数 “message”,并打印出这个方法的返回值。

其中,setAccessible 函数用于动态获取访问权限,Constructor、Field、Method 都提供了此方法,让我们得以访问类中的私有成员。

运行程序,输出如下:

show: NameA,10,message
result: testReturnValue

标签:反射,java,构造方法,获取,Student,机制,Class
From: https://www.cnblogs.com/breeze-jeek/p/16826686.html

相关文章

  • Java的碎碎念(2)
    p21:1.2软件开发介绍常用的DOS命令(windows+Rcmd)>dir:列出当前目录下的文件以及文件夹>md:创建目录>rd:删除目录>cd:进入指定目录>cd.:退回到上一级目录>cdl:退回到根目录......
  • Java碎碎念(3)
    P24:1.5 Java语言的环境搭建什么是JDK,JREJDK(JavaDevelopmentKitJava开发工具包)JDK是提供给Java开发人员使用的,其中包含了java的开发工具,也包括了JRE。所以安装了J......
  • Java碎碎念(三,hello world + 注释)
    windows不区分大小写且区别中英文java区分大小写 第一个代码文件:classHelloChina{         publicstaticvoidmain(String[]args){   ......
  • Java的碎碎念(1)
    P9:比特(bit)和字节(byte)●一个0或者一个1存储为一个比特(bt),是计算机中最小的存储单位●计算机中是最基本的存储单元是字节(byte)每个字节由8个比特构成(即8个二进制单位)。......
  • 全球名校AI课程库(23)| Harvard哈佛 · 基于Python/JavaScript的Web编程课程『Web Progr
    ......
  • JAVA---HashMap的四种遍历方式,未加泛型
    publicclassHashMapDemo{publicstaticvoidmain(String[]args){HashMaphashMap=newHashMap();hashMap.put("小吉祥草王","纳西妲");......
  • 服务端声网获取Token(Java)
    声网社区已经提供了实例代码:https://github.com/AgoraIO/Tools/tree/master/DynamicKey/AgoraDynamicKey/java稍微要注意一下的:声网提供的生成API接口,有通过uid(int)和acc......
  • java中的垃圾回收算法与垃圾回收器
    常用的垃圾回收算法标记-清除标记清除算法是一种非移动式的回收算法,分为标记清除2个阶段,简而言之就是先标记出需要回收的对象,标记完成后再回收掉所有标记的内存对象,如......
  • java: 无效的目标发行版: 11
    一、修改项目结构二、修改java编译器三、修改maven运行jre大功告成!......
  • JavaWeb的简单介绍
    1:基本概念web开发:网页的意思,比如百度www.baidu.com静态web:html+css,提供给所有人看不会改变的数据动态web:提供给不同人看,但是看的是不同的数据在不同的时间......