当前位置: 首页 > news >正文

Reflection反射——Class类

概述

        在Java中,除了int等基本类型外,Java的其他类型全部都是class(包括interface)。例如:

String、Object、Runnable、Exception……

        Java反射机制是Java语言的一个重要特性。在学习Java反射机制前,需要了解两个概念:编译器和运行期。

        编译期:是指把源码交给编译器编译成计算机可以执行的文件的过程。在Java中也就是把Java代码编译成class文件的过程。编译期只是做了一些翻译功能,并没有把代码放在内存中运行起来,而只是把代码当成了文本进行操作。比如:检查语法错误。

        运行期:是把编译后的文件交给计算机执行,直到程序允许结束。所谓运行期就把磁盘中的代码放到内存中执行起来。

        Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能成为Java语言的反射机制。简单来说,反射机制指的是程序在运行时能过获取自身的信息。在Java中,只要给定类的名字,就可以通过反射机制来获得类的所有信息。

        Java反射机制在服务器程序和中间件程序中得到了广泛运用。在服务器端,往往需要根据客户的请求,动态调用某一个对象的特定方法或者为属性赋值。例如:在主流的ORM中框架的实现中,运用Java反射机制可以读取任意一个JavaBean的所有属性,或者给这些属性赋值。

        Java反射机制主要提供了以下功能,这些功能都位于java.lang.reflect包。

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时判断任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的方法
  • 生成动态代理

Class类

        要想知道一个类的属性和方法,必须要获取到该类的字节码文件对象。获取类的信息时,使用的就是Class类中的方法。所以先要获取到每一个字节码文件(.class)对应的Class类型的对象。

        class(包括interface)的本质是数据类型(Type)。class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。

        注意:这里的Class类型是一个名叫Class的class,定义如下:

// final声明不允许继承
public final class Class{// 私有的构造方法private Class(){}
}

        以String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);

        这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现class类的构造方法private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。

        所以JVM持有的每个Class实例都指向一个数据类型(class或interface):

|---------------------------------|
|          Class Instance         | -------> String
|---------------------------------|
|    name = "java.lang.String"    |
|---------------------------------||---------------------------------|
|          Class Instance         | -------> Random
|---------------------------------|
|    name = "java.util.Random"    |
|---------------------------------||---------------------------------|
|          Class Instance         | -------> Runnable
|---------------------------------|
|   name = "java.lang.Runnable"   |
|---------------------------------|

一个Class实例包含了该class的所有完整信息

|---------------------------------|
|          Class Instance         | -------> String
|---------------------------------|
|   name = "java.lang.String"     |
|---------------------------------|
|      package  = "java.lang"     | 
|---------------------------------|
|    super = "java.lang.Object"   |
|---------------------------------|
|   Interface = CharSequence ...  |
|---------------------------------|
|     field = value[] ,hash,...   |
|---------------------------------|
|      method = indexOf() ...     | 
|---------------------------------|

        由于JVM每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段(成员变量)等,因此,如果获取了某个Class实例,我们就可以通过这个Clas实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法称为反射(Reflection)。

        如何获取一个class的Class实例?由三个方法:

       方法一:直接通过一个class的静态变量class获取:

Class cls = String.class;

        方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

String s = "Hello";
Class cls = s.getClass();

        方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

Class cls = Class.forName("java.lang.String");

        因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例:

Class cls1 = String.classString s = "Hello";
Class cls2 = s.getClass();boolean sameClass = cls1 == cls2; // true

        反射的目的是为了获得某个实例的信息。因此,当我们拿到某个Object实例时,我们可以通过反射获取该Object的class信息:

void printObjectInfo(Object obj) {Class cls = obj.getClass();
}

        要从Class实例获取获取的基本信息,参考下面的代码:

public class Main {public static void main(String[] args) {printClassInfo("".getClass());printClassInfo(Runnable.class);printClassInfo(java.time.Month.class);printClassInfo(String[].class);printClassInfo(int.class);}static void printClassInfo(Class cls) {System.out.println("Class name: " + cls.getName());System.out.println("Simple name: " + cls.getSimpleName());if (cls.getPackage() != null) {System.out.println("Package name: " + cls.getPackage().getName());}System.out.println("is interface: " + cls.isInterface());System.out.println("is enum: " + cls.isEnum());System.out.println("is array: " + cls.isArray());System.out.println("is primitive: " + cls.isPrimitive());}
}

        注意,数组(例如String[])也是一种类,而且不同于String.class,它的类名是java.lang.String。此外,JVM为每一种基本类型如int也创建了Class实例,通过int.class访问。
如果获取到了一个Class实例,我们就可以通过该Class实例来创建对应类型的实例:

// 获取String的Class实例:
Class cls = String.class;// 创建一个String实例:
String s = (String) cls.newInstance();

        上述代码相当于new String()。通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

Class常用方法

包路径

getPackage()

Package 对象

获取该类的存放路径

类名称

getName()

String 对象

获取该类的名称

继承类

getSuperclass()

Class 对象

获取该类继承的类

实现接口

getlnterfaces()

Class 型数组

获取该类实现的所有接口

构造方法

getConstructors()

Constructor 型数组

获取所有权限为 public 的构造方法

getDeclaredContruectors()

Constructor 对象

获取当前对象的所有构造方法

方法

getMethods()

Methods 型数组

获取所有权限为 public 的方法

getDeclaredMethods()

Methods 对象

获取当前对象的所有方法

成员变量

getFields()

Field 型数组

获取所有权限为 public 的成员变量

getDeclareFileds()

Field 对象

获取当前对象的所有成员变量

动态加载机制

        JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。例如


public class Main {static {System.out.println("Main被加载");}public static void main(String[] args) {int rand = new Random().nextInt(10);if (rand > 5) {create(rand);}}static void create(int no) {Person p = new Person(no);}
}class Person{static {System.out.println("Person类被加载");}public Person(int no) {System.out.println("Person类的有参构造方法");}
}

        当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。
动态加载class的特性对于Java程序非常重要。利用JVM动态加载class的特性,我们才能在运行期根据条件加载不同的实现类。

小结

  • JVM为每个加载的class及interface创建了对应的Class实例来保存class及interface的所有信息;
  • 获取一个class对应的Class实例后,就可以获取该class的所有信息;
  • 通过Class实例获取class信息的方法称为反射(Reflection);
  • JVM总是动态加载class,可以在运行期根据条件来控制加载class;

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 大模型训练数据库Common Crawl
  • Python判断两张图片的相似度
  • 汽车免拆诊断案例 | 2013款捷豹XF车偶尔无法起动
  • Jupyter Notebook 修改默认路径
  • 【Linux】:信号的保存和信号处理
  • CCF推荐C类会议和期刊总结:(计算机体系结构/并行与分布计算/存储系统领域)
  • macos 系统文件操作时提示 Read-only file system 解决方法
  • 计算机网络--第六章应用层
  • React实现虚拟列表的优秀库介绍
  • 隐马尔可夫模型(Hidden Markov Model,HMM)—有监督学习方法、概率模型、生成模型
  • 排序方法sort使用方式不同而产生的不同结果,附力扣179思路
  • [001-03-007].第07节:Redis中的事务
  • 【数据结构与算法 | 灵神题单 | 快慢指针(链表)篇】力扣876, 2095, 234
  • CSS之我不会
  • tailscale与zerotier在linux冲突问题解决
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • CentOS6 编译安装 redis-3.2.3
  • download使用浅析
  • Java 实战开发之spring、logback配置及chrome开发神器(六)
  • Javascript基础之Array数组API
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • js面向对象
  • php面试题 汇集2
  • python学习笔记 - ThreadLocal
  • WebSocket使用
  • XForms - 更强大的Form
  • 大快搜索数据爬虫技术实例安装教学篇
  • 订阅Forge Viewer所有的事件
  • 基于Android乐音识别(2)
  • 基于axios的vue插件,让http请求更简单
  • 技术攻略】php设计模式(一):简介及创建型模式
  • 简析gRPC client 连接管理
  • 一些基于React、Vue、Node.js、MongoDB技术栈的实践项目
  • 智能合约Solidity教程-事件和日志(一)
  • postgresql行列转换函数
  • !! 2.对十份论文和报告中的关于OpenCV和Android NDK开发的总结
  • #define
  • #laravel 通过手动安装依赖PHPExcel#
  • #我与Java虚拟机的故事#连载10: 如何在阿里、腾讯、百度、及字节跳动等公司面试中脱颖而出...
  • (10)STL算法之搜索(二) 二分查找
  • (6) 深入探索Python-Pandas库的核心数据结构:DataFrame全面解析
  • (aiohttp-asyncio-FFmpeg-Docker-SRS)实现异步摄像头转码服务器
  • (C)一些题4
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (TipsTricks)用客户端模板精简JavaScript代码
  • (zhuan) 一些RL的文献(及笔记)
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (每日持续更新)jdk api之FileReader基础、应用、实战
  • (七)微服务分布式云架构spring cloud - common-service 项目构建过程
  • (五)MySQL的备份及恢复
  • (转)jQuery 基础
  • (转)从零实现3D图像引擎:(8)参数化直线与3D平面函数库
  • (轉貼) 蒼井そら挑戰筋肉擂台 (Misc)
  • ../depcomp: line 571: exec: g++: not found