Java专题(二)反射

news2024/11/23 3:04:08

反射概述

Java 反射就是在运行的状态下,对于任意一个类,都能知道这个类的任意的属性和方法,并且能调用这些方法或者改变这些类的属性,因此 Java 被称为准动态语言

动态语言和静态语言

上面说了 Java 是准动态的语言,下面我们来了解动态语言和静态语言的区别:

  • 动态语言是一类可以在运行的时候改变其结构的语言:例如新的函数、对象、甚至是代码可以被引进,已有的函数可以被删除或者是其他结构上的变化。通俗点说就是在运行时代码可以根据某些条件来改变自身的结构。
  • 静态语言在运行时结构是不可变的,这些内容在编译的时候就已经确认了,Java 是准动态语言,是有一定的动态性的,我们可以利用反射机制中的特性实现在运行状态是时候对进行一些修改。

Java 反射提供的功能

  1. 运行时判断一个对象所属的类
  2. 运行时构造任意一个类的对象
  3. 运行时判断一个类所具有的成员变量和方法
  4. 运行时获取泛型信息
  5. 运行时任意调用对象的成员变量和方法,不管是共有或是私有的
  6. 运行时处理注解
  7. 生成动态代理

这篇文章主要介绍和理解前六条,关于动态代理放在 Spring 的 AOP 详解去仔细的介绍。

Java 反射介绍

了解了反射的意义和反射提供的功能后,我们来具体的介绍反射:

  • 反射是 Java 可以被视为动态语言的关键,反射机制允许程序在执行的时候借助反射的 API 获取类的内部信息,并且能直接操控对象内部的属性以及方法。
  • 加载完类后,在堆内存的方法区就产生了一个** Class 类型的对象**,这个对象包含了完整的类的结构信息,我们可以通过这个对象看到类的结构,就像一个镜子一样,所以这个机制被称为反射,需要注意的是一个类仅有一个 Class 对象。

Java 反射的优点和缺点

优点:

可以实现动态的创建对象和编译,体现出很大的灵活性

缺点:

对性能有影响。使用反射基本上是一种解释操作,我们可以告诉 JVM 我们希望做什么,并且它满足我们的要求。这类操作总是慢于直接执行的操作

反射的 API

Java 提供了一套反射的 API,允许在运行时检查和操作类、方法、字段等信息,主要涉及到的类包括 Class、Method、Field、Constructor 等。
这里先把常用的 API 罗列下来,方便后续的查阅和使用,这些类和方法后面都有具体的解析
在理解这些类的方法的时候,可以先从这些类的归属看起:
比如字段和方法是依托于对象的,所以我们使用字段或者调用方法的时候就需要传输是通过哪个对象来使用这个字段和调用这个方法,而构造器是属于整个类的,这些就不需要对象的承载。

Class

API作用
Class.forName(String className)通过类的完整路径名获取 Class 对象。
object.getClass()获取对象的 Class 对象
ClassLoader.loadClass(String className)通过类加载器加载类

Method

API作用
Class.getDeclaredMethods()获取所有声明的方法。
Class.getDeclaredMethod(String name, Class<?>… parameterTypes)获取指定声明的方法。
Method.invoke(Object obj, Object… args)调用方法。

Field

API作用
Class.getDeclaredFields()获取所有声明的字段
Class.getDeclaredField(String name)获取指定声明的字段
Field.get(Object obj)获取字段的值
Field.set(Object obj, Object value)设置字段的值

Constructor

API作用
Class.getDeclaredConstructors()获取所有声明的构造方法
Class.getDeclaredConstructor(Class<?>… parameterTypes)获取指定声明的构造方法
Constructor.newInstance(Object… initargs)通过构造方法创建新对象

获取反射对象

获取反射对象的方式

观察上面的 API 我们可以发现,不管是 Method、Field 或者是 Constructor 在获取的时候都需要得到 Class 对象,这个 Class 对象是反射的源头
在 Object,也就是所有类的父类中我们可以很容易的发现一个方法

	public final native Class<?> getClass();

这个方法将会被**所有的子类继承,**通过这些子类的对象我们可以调用这个方法去获取 Class 对象。

public class getClass {
    public static void main(String[] args) {
        Person person = new Person();
        // 调用实例的 getClass 方法获取这个类的 Class 对象
        Class<? extends Person> aClass = person.getClass();
    }
}
class Person{}

除了这种方法我们还可以通过类的全路径来获取到类的 Class 对象

public class getClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        Class<? extends Person> aClass = person.getClass();
        // 这个实例类是我在根路径上写的,这就已经是全路径了
        Class<?> person1 = Class.forName("Person");
        System.out.println(person1.hashCode());
    }
}
class Person{}

如果已经知道想要获取的类是哪一个,可以直接通过 类名.class 来获取对应的 Class 对象,这种方式最为安全可靠,程序性能也是最高的

public class getClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Class<Person> personClass = Person.class;
    }
}
class Person{}

还能获取到父类的 Class 对象

// 通过 Class 实例获取父类的 Class 实例
Class<?> superclass = personClass1.getSuperclass();

除了这些方法还能通过类加载器来获取类的 Class 对象,这个放在后面叙述,通过这些方法获取到 Class 类的时候,我们就可以在运行时对其进行操作了。

Class 类

通过这个类能得到的信息就是我们上面反射常用类中提到的属性,有类的属性、方法和构造器,某个类实现了哪些接口,对于每个类而言,都保留了一个不变的 Class 类型的对象,该对象包含了特定的某个结构的有关信息,比如 class、interface、enum、annotation、primitive、type / void、[] 等。

Class 详解

  1. Class 本身也是一个类,这个类只能由系统来进行实例化,内部没有提供给外部使用的构造器。
  2. 一个加载的类在 JVM 中只会有一个 Class 实例
public class getClass {
    public static void main(String[] args) throws ClassNotFoundException {
        Person person = new Person();
        Class<? extends Person> personClass1 = person.getClass();
        Class<? extends Person> personClass2 = person.getClass();
        Class<? extends Person> personClass3 = person.getClass();
        Class<? extends Person> personClass4 = person.getClass();
        Class<? extends Person> personClass5 = person.getClass();
        Class<? extends Person> personClass6 = person.getClass();
    	// 输出这些类的 hashcode
        System.out.println(personClass1.hashCode());
        System.out.println(personClass2.hashCode());
        System.out.println(personClass3.hashCode());
        System.out.println(personClass4.hashCode());
        System.out.println(personClass5.hashCode());
        System.out.println(personClass6.hashCode());
    }
}
class Person{}
# 相同的哈希值,是同一个对象
460141958
460141958
460141958
460141958
460141958
460141958
  1. 一个 Class 对象对应的是一个加载到 JVM 中的一个 .class 文件
  2. 每个类的实例都会记得自己是哪个 Class 实例所生成的
  3. Class 类是反射的根源,任何像动态加载、运行的类,只能先获取响应的 Class 对象

Method 类

通过 Method 调用共有方法

我们可以通过 Class 类中的 getDeclaredMethod 来获取类中的所有方法,也可以通过 getMethod 来获取全部的方法,也可以通过 getMethods来获取所有方法的数组。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class getClass {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Person person = new Person();
        Class<Person> personClass = Person.class;
        person.setName("张三");
        Method getName = personClass.getDeclaredMethod("getName");
        System.out.println(getName.invoke(person));
    }
}
class Person{
    public String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

通过 Method 调用私有方法

前面我们说过利用反射机制是可以调用类中的所有方法的,不论是共有还是私有,但如果直接通过上面的方法调用是会抛出:java.lang.IllegalAccessException异常,我们需要手动开启访问权限才能调用私有方法。

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class getClass {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Person person = new Person();
        Class<Person> personClass = Person.class;
        person.setName("张三");
        Method getName = personClass.getDeclaredMethod("getName");
        // 开放访问权限
        getName.setAccessible(true);
        System.out.println(getName.invoke(person));
    }
}
class Person{
    public String name;
    private String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Field 类

通过 Field 获取和修改对象的属性

getField 和 getDeclaredField 是用于获取类的字段(Field)的两个不同方法,它们之间有一些区别,这和上面 Method 是相同的,Method 同样也有这两种方法。
可见性:

  • getField 用于获取类中的公有字段(public fields)。
  • getDeclaredField 用于获取类中声明的所有字段,不论其可见性(包括公有、受保护、默认、私有等)。

继承关系:

  • getField 会返回指定名称的公有字段,包括从父类继承而来的公有字段。
  • getDeclaredField 只会返回当前类中声明的字段,不包括继承的字段。
import java.lang.reflect.Field;

public class getClass {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Person person = new Person();
        Class<Person> personClass = Person.class;
        person.setName("张三");
        // 获取字段
        Field name = personClass.getDeclaredField("name");
        // 调用私有字段需要设置可见性
        name.setAccessible(true);
        Object o = name.get(person);
        System.out.println(o);
    }
}

class Person{
    private String name;

    private String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Constructor 类

Constructor 相信大家都不陌生,就是我们经常使用的构造器,通过 Class 获取到的构造器同样可以构造对象,这个类可以帮助我们更方便的选择需要的构造器。
这是获取构造器的方法,我们可以通过指定**参数类型(Class)**的方式来指定构造器。

    @CallerSensitive
    public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        checkMemberAccess(Member.DECLARED, Reflection.getCallerClass(), true);
        return getConstructor0(parameterTypes, Member.DECLARED);
    }
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class getClass {
    public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        Class<Person> personClass = Person.class;
        Constructor<Person> constructor = personClass.getDeclaredConstructor(String.class);
        Person person = constructor.newInstance("张三");
        System.out.println(person.getName());
    }
}
class Person{
    private String name;

    public String getName() {
        return name;
    }

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

深入理解反射机制

为了更好的理解反射的机制,我们需要了解一些底层的知识,比如 Java 内存或者类的加载等等。

Java 内存

Java 的内存主要分为三块:堆、栈和方法区

image.png

  • 存放通过 new 创建的对象和数组,所有的对象都在堆中分配内存。
  • 可以被所有线程共享,不会存放别的对象引用
  • 存放基本的变量类型,会存储这个类型的具体数值
  • 存放引用对象的变量,存放这个引用在堆里面的具体地址
  • 栈是线程私有的,每个线程都有自己的栈
方法区
  • 方法区也是线程共享的,用于存储类的信息、静态变量、常量池、方法代码等。
  • 在方法区中保存了类的结构信息,包括类的字段、方法、接口等。
  • 常量池存储了编译期生成的常量,如字符串常量、基本数据类型的常量等。

类加载与 ClassLoader 的理解

加载:指的是将一个 class 字节码文件内容加载到内存中,并且将这些静态数据转换成方法区的运行时的数据结构,然后生成一个代表这个类的 java.lang.Class 对象,这个对象是存储在方法区的
链接:将 Java 类的二进制代码合并到 JVM 运行状态之中的过程

  1. 验证:确保加载的类信息符合 JVM 的规范且没有安全方面的问题
  2. 准备:正式为类变量(static)分配内存并设置
  3. 解析:虚拟机常量池内符号引用替换为直接引用的过程

**初始化:**执行初始化类构造器 () 方法的过程。类构造器() 方法是由编译器自动收集类中的所有变量的赋值动作和静态代码块中的语句合并而成的。(类构造器构造类的信息,不是构造类的对象

  1. 当初始化一个类的时候,如果发现父类还没有被初始化,则先调用父类的初始化。
  2. 虚拟机 会保证一个类的 () 方法在多线程环境中被正确的加锁和同步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1249705.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面向对象三大特性,类与接口,java重写与重载,对象相等的判断, hashCode 与 equals

文章目录 2.1 面向对象三大特性2.1.1 封装 继承 多态2.1.2 其中Java 面向对象编程三大特性&#xff1a;封装 继承 多态2.1.3 关于继承如下 3 点请记住&#xff1a;2.1.4 什么是多态机制&#xff1f;Java语言是如何实现多态的&#xff1f;2.1.5 Java实现多态有三个必要条件&…

电脑中提示关于ntdll.dll错误怎么办,解决出现ntdll.dll错误的办法

ntdll.dll是Windows操作系统的一个关键系统文件&#xff0c;它包含了许多核心函数和系统调用&#xff0c;对于系统的稳定运行至关重要。然而&#xff0c;有时我们可能会遇到ntdll.dll报错的问题&#xff0c;导致程序无法正常运行。那么今天就和大家谈谈电脑中提示关于ntdll.dll…

企业计算机服务器中了360勒索病毒怎么办,360勒索病毒解密文件恢复

计算机技术的不断发展&#xff0c;为企业的生产运营提供了极大便利&#xff0c;不仅提升了办公效率&#xff0c;还促进了企业的发展。企业计算机在日常工作中一定加以防护&#xff0c;减少网络威胁事件的产生&#xff0c;确保企业的生产生产运营。最近&#xff0c;网络上的360后…

两巨头Facebook 和 GitHub 联手推出 Atom-IDE

9月13日&#xff0c;GitHub 宣布与 Facebook 合作推出了 Atom-IDE —— 它包括一系列将类 IDE 功能带到 Atom 的可选工具包。初次发布的版本包括更智能、感知上下文的自动完成&#xff1b;导航功能&#xff0c;如大纲视图和定义跳转(outline view and goto-definition)&#xf…

爬虫项目实战:利用基于selenium框架的爬虫模板爬取豆瓣电影Top250

&#x1f44b; Hi, I’m 货又星&#x1f440; I’m interested in …&#x1f331; I’m currently learning …&#x1f49e; I’m looking to collaborate on …&#x1f4eb; How to reach me … README 目录&#xff08;持续更新中&#xff09; 各种错误处理、爬虫实战及模…

【JAVA】SpringBoot + mongodb 分页、排序、动态多条件查询及事务处理

【JAVA】SpringBoot mongodb 分页、排序、动态多条件查询及事务处理 1.引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- mongodb ↓ -->&…

2023年亚太杯数学建模A题解题思路(*基于OpenCV的复杂背景下苹果目标的识别定位方法研究)

摘要 由于要求较高的时效性和劳力投入&#xff0c;果实采摘环节成为苹果生产作业中十分重要的一部分。而对于自然环境下生长的苹果&#xff0c;光照影响、枝叶遮挡和果实重叠等情况普遍存在&#xff0c;这严重影响了果实的准确识别以及采摘点的精确定位。针对在复杂背景下苹果的…

android 保活的一种有效的方法

android 保活的一种有效的方法 为什么要保活 说起程序的保活,其实很多人都觉得,要在手机上进行保活,确实是想做一些小动作,其实有些正常的场景也是需要我们进行保活的,这样可以增强我们的用户体验。保活就是使得程序常驻内存,这种程序不容易被杀,或者在被杀以后还能完…

kubernetes使用nfs创建pvc部署mysql stateful的方法

kubernetes创建的pod默认都是无状态的&#xff0c;换句话说删除以后不会保留任何数据。 所以对于mysql这种有状态的应用&#xff0c;必须使用持久化存储作为支撑&#xff0c;才能部署成有状态的stateful. 最简单的方法就是使用nfs作为网络存储&#xff0c;因为nfs存储很容易被…

css Vue尺子样式

原生css生成尺子样式 <template><div class"page"><div class"Light"></div><div class"rile"><ul id"list"><!--尺子需要几个单位就加几个--><li></li><li></li&…

css三角,鼠标样式,溢出文字

目录 css三角 鼠标样式 例子&#xff1a;页码模块 溢出文字表示方式 margin负值运用 css三角强化 css三角 css三角中&#xff1a;line-height&#xff1a;0和font-size&#xff1a;0是防止兼容性的问题 jd {position: relative;width: 120px;height: 249px;background-…

数据库基础知识小结

数据库基础知识小结 什么是数据库&#xff0c;数据库管理员&#xff0c;数据库管理员&#xff0c;数据库系统&#xff1f; 数据库: 数据库(DataBase 简称 DB)就是信息的集合或者说数据库是由数据库管理系统管理的数据的集合。 数据库管理系统: 数据库管理系统(DataBase Mana…

Oracle SQL 注入上的 Django GIS 函数和聚合漏洞 (CVE-2020-9402)

漏洞描述 Django 于2020年3 月4日发布了一个安全更新&#xff0c;修复了 GIS 函数和聚合中的 SQL 注入漏洞。 参考链接&#xff1a; Django security releases issued: 3.0.4, 2.2.11, and 1.11.29 | Weblog | Django 该漏洞要求开发者使用 JSONField/HStoreField;此外&…

【matlab程序】图像最大化填充画布

【matlab程序】图像最大化填充画布 不做任何修饰&#xff1a; 修饰&#xff1a; 图片 往期推荐 图片 【python海洋专题一】查看数据nc文件的属性并输出属性到txt文件 【python海洋专题二】读取水深nc文件并水深地形图 【python海洋专题三】图像修饰之画布和坐标轴 【Pytho…

flutter之graphic图表自定义tooltip

renderer graphic中tooltip的TooltipGuide类提供了renderer方法&#xff0c;接收三个参数Size类型&#xff0c;Offset类型&#xff0c;Map<int, Tuple>类型。可查到的文档是真的少&#xff0c;所以只能在源码中扒拉例子&#xff0c;做符合需求的修改。 官方github示例 …

在 Python 的 requests 二进制数据的传输方式发生了变化

在Python编程中&#xff0c;requests库是一个非常有用的工具&#xff0c;用于发送HTTP请求。由于其简单易用的API和广泛的兼容性&#xff0c;requests库已经成为Python开发者中最常用的网络请求库之一。 然而&#xff0c;最近在requests 0.10.1版本中&#xff0c;POST二进制数据…

力扣学习笔记——239. 滑动窗口最大值

力扣学习笔记——239. 滑动窗口最大值 题目描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回 滑动窗口中的最大值 。 示例 1&#xff1a; 输…

php高级工程师范文模板

以下简历内容以php高级工程师招聘需求为背景&#xff0c;我们制作了1份全面、专业且具有参考价值的简历案例&#xff0c;大家可以灵活借鉴&#xff0c;希望能帮助大家在众多候选人中脱颖而出。 php高级工程师简历在线制作下载&#xff1a;百度幻主简历 求职意向 求职类型&…

带submodule的git仓库自动化一键git push、git pull脚本

前言 很久没写博客了&#xff0c;今天难得闲下来写一次。 不知道大家在使用git的时候有没有遇到过这样的问题&#xff1a;发现git submodule特别好用&#xff0c;适合用于满足同时开发和部署的需求&#xff0c;并且结构清晰&#xff0c;方便我们对整个代码层次有一个大概的了…

[Docker]十.Docker Swarm讲解

一.Dokcer Swarm集群介绍 1.Dokcer Swarm 简介 Docker Swarm 是 Docker 公司推出的用来管理 docker 集群的工具&#xff0c; 使用 Docker Swarm 可以快速方便的实现 高可用集群 ,Docker Compose 只能编排单节点上的容器, Docker Swarm 可以让我们在单一主机上操作来完成对 整…