Java反射(Reflection)总结

news2024/11/26 16:58:54

目录

Java反射概述

Class类

        Class对象获取的三种方式:

        Class类的常用方法

                 动态加载机制:

通过反射调用构造方法

        Constructor类

 通过反射获取继承关系

通过反射访问字段(成员变量)

                获取字段值:

设置字段值 

通过反射调用方法 

动态代理

        代理模式:

        静态代理 

         JDK动态代理


Java反射概述

        Java反射就是在程序运行状态下,对于任意一个类,都能获取到类的所有信息,例如:属性,方法。对于任意一个对象,都能调用它的任意方法和属性。简单来说,反射就是在程序运行期间能够获取自身的信息。

        要了解反射,就必须要了解Class类。

Class类

        Class对象中包含了类的所有信息。要想获取类的信息,就必须先获取Class对象。

        Class对象获取的三种方式:

                Class类中得到构造方法是私有的,所有所有的Class对象都指向了某一个数据类型。

                方式一:通过一个类的静态变量class获取        

                Class cls = String.class;

                 方式二:通过某个类的实例对象的getClass()方法获取

                String s = "lmy";

                Class cls = s.getClass();

                方式三:通过Class类的静态方法Class.forName()获取--(需要知道类的完整类名)

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

        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类提供的newInstance()方法:

                String s = "lmy";

                Class cls = s.getClass();

                String s1 = cls.newInstance();  

         newInstance()只能调用该类的public无参构造。

        Constructor类

                为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。

                 getConstructor(Class...):获取某个public的Constructor

Constructor cons = Integer.class.getConstructor(int.class);

Integer n1 = (Integer)cons.newInstance(123);

             getDeclaredConstructor(Class...):获取某个定义的Constructor

                 

Constructor cons = Integer.class.getDeclaredConstructor(int.class);

Integer n1 = (Integer)cons.newInstance(123);

                 getConstructors():获取所有public的Constructor

                getDeclaredConstructors():获取所有定义的Constructor

        Constructor类总结:

                Constructor对象封装了构造方法的所有信息;

                通过Class实例的方法可以获取Constructor实例:getConstructor(),getConstructors(),getDeclaredConstructor(),getDeclaredConstructors();

                通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。

 通过反射获取继承关系

        可以通过Class实例的getSuperClass()方法来获取父类信息

                例如:        

                        

        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n);
        
        Class o = n.getSuperclass();
        System.out.println(o);
        System.out.println(o.getSuperclass());
        /*
            运行上述代码,可以看到,Integer的父类类型是Number,
        Number的父类是Object,Object的父类是null。
    除Object外,其他任何非interface的Class都必定存在一个父类类型。

        */

               通过getInterfaces()可以获取到实现的接口类型

                

        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }

                如果两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()

        

// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer

// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number

// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object

// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer

通过反射访问字段(成员变量)

        Class类提供了以下几个方法来获取字段:

                Field getField(name):根据字段名获取当前类中某个public的field(包括父类)
                Field getDeclaredField(name):根据字段名获取当前类中定义的某个field(不包括父类)
                Field[] getFields():获取所有public的field(包括父类)
                Field[] getDeclaredFields():获取当前类中定义的所有field(不包括父类)

         一个Field对象包含了一个字段的所有信息:
                getName():返回字段名称,例如,"name";
                getType():返回字段类型,也是一个Class实例,例如,String.class;
                getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

                获取字段值:

                        

public class Main {

    public static void main(String[] args) throws Exception {
        Object p = new Person("贝吉塔");
        
        Class c = p.getClass();
        
        Field f = c.getDeclaredField("name");
        //设置访问权限。(可以访问private 修饰的字段值)
        f.setAccessible(true);
        
        Object value = f.get(p);
        
        System.out.println(value); // "贝吉塔"
    }
}

class Person {
    private String name;

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

设置字段值 

public class Main {

    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        System.out.println(p.getName()); // "Xiao Ming"
        
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        f.setAccessible(true);
        f.set(p, "Xiao Hong");
        
        System.out.println(p.getName()); // "Xiao Hong"
    }
}

class Person {
    private String name;

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

    public String getName() {
        return this.name;
    }
}

 访问字段小结

        Java的反射API提供的Field类封装了字段的所有信息:
        通过Class实例的方法可以获取Field实例:getField(),getFields(),getDeclaredField(),getDeclaredFields();
        通过Field实例可以获取字段信息:getName(),getType(),getModifiers();
        通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
        通过反射读写字段是一种非常规方法,它会破坏对象的封装。

通过反射调用方法 

        Class实例可以获取所有方法(Method类型的对象)。

        Method getMethod(name, Class...):获取某个public的Method(包括父类)
        Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
        Method[] getMethods():获取所有public的Method(包括父类)
        Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类

        一个Method对象包含一个方法的所有信息:

                getName():返回方法名称,例如:"getScore";
                getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
                getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class};
                getModifiers():返回方法的修饰符,它是一个int,不同的value表示不同的访问修饰符;

         调用方法:        

                当我们获取到一个Method对象时,就可以对它进行调用。

                

public class Main {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring", int.class);
        
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        
        // 打印调用结果:
        System.out.println(r);
    }
}

             调用静态方法:

                调用静态方法只需要将invoke()方法的第一个参数改为null即可。

                例如:

                        

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Integer.parseInt(String)方法,参数为String:
        Method m = Integer.class.getMethod("parseInt", String.class);
        
        // 调用该静态方法并获取结果:
        Integer n = (Integer) m.invoke(null, "12345");
        
        // 打印调用结果:
        System.out.println(n);
    }
}

         对于非public的方法,类似Field,通过Method.setAccessible(true)允许其调用。

                            

public class Main {
    public static void main(String[] args) throws Exception {
        Person p = new Person();
        Method m = p.getClass().getDeclaredMethod("setName", String.class);
        m.setAccessible(true);
        m.invoke(p, "Bob");
        System.out.println(p.name);
    }
}

class Person {
    String name;
    private void setName(String name) {
        this.name = name;
    }
}

        多态

                 我们来考察这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也覆写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个?

        

public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Person的hello方法:
        Method h = Person.class.getMethod("hello");
        
        // 对Student实例调用hello方法:
        h.invoke(new Student());
    }
}

class Person {
    public void hello() {
        System.out.println("Person:hello");
    }
}

class Student extends Person {
    public void hello() {
        System.out.println("Student:hello");
    }
}

            运行上述代码,发现打印出的是Student:hello,因此,使用反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。

动态代理

        了解动态代理之前,先了解以下代理模式(Java设计模式的一种)

        代理模式:

                给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问。代理模式是一种结构型设计模式。

                代理模式角色分为3种

                        Subject(抽象主题角色):定义代理类和真实主题的公共对外方法,通常被设计成接口;
                        RealSubject(真实主题角色):真正实现业务逻辑的类;
                        Proxy(代理主题角色):用来代理和封装真实主题;

                

 如果根据字节码的创建时机来分类,可以分为静态代理和动态代理:
        所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类和真实主题角色的关系在运行前就确定了。
        而动态代理的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以在运行前并不存在代理类的字节码文件

        静态代理 

                假设有UserService接口及其实现类UserServiceImpl,我们需要在不改变实现类代码的基础上,增加日志记录的功能。

        

public interface UserService {
    public void select();   
    public void update();
}
public class UserServiceImpl implements UserService {  
    public void select() {  
        System.out.println("查询 selectById");
    }
    public void update() {
        System.out.println("更新 update");
    }
}

 我们将通过静态代理对 UserServiceImpl 进行功能增强,在调用select和update之前记录一些日志。
静态代理通过 UserServiceProxy实现,代理类同时也需要实现 UserService接口。

public class UserServiceProxy implements UserService {
    private UserService target; // 被代理的对象

    public UserServiceProxy(UserService target) {
        this.target = target;
    }
    public void select() {
        before();
        target.select();    // 这里才实际调用真实主题角色的方法
        after();
    }
    public void update() {
        before();
        target.update();    // 这里才实际调用真实主题角色的方法
        after();
    }

    private void before() {     // 在执行方法之前执行
        System.out.println(String.format("log start time [%s] ", new Date()));
    }
    private void after() {      // 在执行方法之后执行
        System.out.println(String.format("log end time [%s] ", new Date()));
    }
}

         测试:

                

public class Test {
    public static void main(String[] args) {
        UserService userServiceImpl = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(userServiceImpl);

        proxy.select();
        proxy.update();
    }
}

 通过静态代理,我们达到了功能增强的目的,而且没有侵入原代码,这是静态代理的一个优点。
虽然静态代理实现简单,且不侵入原代码,但是,当场景稍微复杂一些的时候,静态代理的缺点也会暴露出来。
1、 当需要代理多个类的时候,由于代理对象要实现与目标对象一致的接口,有两种方式:
        只维护一个代理类,由这个代理类实现多个接口,但是这样就导致代理类过于庞大
        新建多个代理类,每个目标对象对应一个代理类,但是这样会产生过多的代理类
2、 当接口需要增加、删除、修改方法的时候,目标对象与代理类都要同时修改,不易维护。

         JDK动态代理

                JDK动态代理主要涉及两个类:java.lang.reflect.Proxy java.lang.reflect.InvocationHandler。我们通过编写一个调用逻辑处理器 LogHandler 类案例来提供日志增强功能,并实现 InvocationHandler 接口;在 LogHandler 中维护一个目标对象,这个对象是被代理的对象(真实主题角色);在 invoke()方法中编写方法调用的逻辑处理。

         动态代理的处理类

                

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Date;

public class LogHandler implements InvocationHandler {
    Object target;  // 被代理的对象,实际的方法执行者

    public LogHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("方法被调用");
        Object result = method.invoke(target, args);  // 调用 target 的 method 方法
        System.out.println("方法调用完毕");
        return result;  // 返回方法的执行结果
    }
   
}

        //测试类

                

import proxy.UserService;
import proxy.UserServiceImpl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

public class Test {
    public static void main(String[] args) throws IllegalAccessException, InstantiationException {
        
        // 1. 创建被代理的对象,UserService接口的实现类
        UserServiceImpl userServiceImpl = new UserServiceImpl();
        
        // 2. 获取对应的 ClassLoader
        ClassLoader classLoader = userServiceImpl.getClass().getClassLoader();
        
        // 3. 获取所有接口的Class,这里的UserServiceImpl只实现了一个接口UserService,
        Class[] interfaces = userServiceImpl.getClass().getInterfaces();
        
        // 4. 创建一个将传给代理类的调用请求处理器,处理所有的代理对象上的方法调用
        //     这里创建的是一个自定义的日志处理器,须传入实际的执行对象 userServiceImpl
        InvocationHandler logHandler = new LogHandler(userServiceImpl);
        /*
		   5.根据上面提供的信息,创建代理对象 在这个过程中,
               a.JDK会通过根据传入的参数信息动态地在内存中创建和.class 文件等同的字节码
               b.然后根据相应的字节码转换成对应的class,
               c.然后调用newInstance()创建代理实例
		 */
        UserService proxy = (UserService) Proxy.newProxyInstance(classLoader, interfaces, logHandler);
        
        // 调用代理的方法
        proxy.select();
        proxy.update();
    }
}

         动态代理在Java中有着广泛的应用,比如Spring AOP、Hibernate数据查询、测试框架的后端mock、RPC远程调用、Java注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。

        Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;
        动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

        

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

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

相关文章

MySQL数据库(四)

前言 本文讲述索引和事务,以及JDBC。 是关于MySQL的最后一弹啦~~ 重点小知识:MySQL数据库的存储结构是B树!! 目录 前言 一、索引 (一)查看索引 (二)创建索引 (三&am…

【JVM】14. 堆外内存

文章目录 堆外内存的意义堆外内存(Off-heap memory)是指在计算机内存管理之外进行分配和使用的内存空间。与堆内内存(Heap memory)不同,堆外内存不受Java虚拟机(JVM)的垃圾回收机制控制,需要手动进行内存的分配和释放。 堆外内存通常由操作系统提供支持,可以通过直接…

3.利用matlab求平均值和中位数(matlab程序)

代码及运行结果 %% 算术平均值 clear all; A[1 2 3 4;4 4 4 4] m1mean(A) %对列元素求算术平均值 m2mean(A,2) %对行元素求算术平均值 %% 忽略非数计算算术平均值 clear all; A[1 4 nan 5;6 nan 7 nan] m1mean(A) %有nan就是nan…

深度学习-图像分类篇一:基础理论

开头言 学东西前总是爱问,这个学了有什么用,会用就行了么。能够回答你这个问题的人,都是学过的(只有学过才有资格告诉你有没有用),然而知识往往就是这样,学的人越多越没有用,我可以…

使用 Google 的 zx 库编写切换微信小程序环境的脚本

背景 微信小程序目前为止还没有提供API或者具体的配置方式,给我们设置环境变量,所以还得自己想办法。 创建一个 shell 脚本——一个由诸如 Bash 或 zsh 之类的 shell 执行的脚本——可以是自动化重复任务的好方法。Node.js 似乎是编写 shell 脚本的理想…

Apache Calcite 简介

这张图上列的,是直接使用 Apache Calcite 或者至少相关联的项目。大家肯定能在里面找到很多自己熟悉的项目。 那 Apache Calcite 究竟是干嘛的,又为什么能这么流行呢? 首先,摆一个应该没多少人会反对的共识:SQL 是编程领域最流行的语言。 有 MySQL、Oracle 之类使用 SQL…

Linux---函数库和Makefile

Linux---函数库和Makefile 函数库动态库静态库例 Linux项目自动化构建工具-make/Makefile背景实例代码原理项目清理 函数库 方法实现就是在库当中,库其实就是把源文件经过一定的翻译,然后打包,只给你提供一个文件计科,不用给你提…

Spring -- Bean的生命周期

BeanDefinition Spring容器在进行实例化时&#xff0c;会将xml配置的<bean>的信息封装成一个BeanDefinition对象&#xff0c;Spring根据BeanDefinition来创建Bean对象&#xff0c;里面有很多的属性用来描述Bean BeanDefinition 中几个重要方法如下 beanClassName&#…

阿里云——云服务器基础运维与管理

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;网络豆的主页​​​​​ 目录 写在前面 学习目标&#xff1a; 一.3个理由拥抱云服务器 1.什么是云服务器 2.使用…

人物百科词条创建教程分享,人物创建百科有什么要求

人物百科词条的创建是一个相对复杂的过程&#xff0c;需要遵循一定的规范和要求。以下是一个人物百科词条创建教程的分享&#xff0c;包括创建要求、步骤和注意事项&#xff0c;接下来伯乐网络传媒就来给大家好好讲一讲。 一、人物百科词条创建要求 1. 真实性&#xff1a;创建…

ELK-日志服务【filebeat-安装使用】

目录 【1】安装Filebeat 【2】配置-测试 【3】配置使用Filebeat 【4】filebeat-收集系统文件日志 【5】配置filebeat&#xff0c;将/var/log/all.log日志采集到es集群中 【6】定制索引名称 【7】收集多个web节点的日志&#xff0c;输出到相同的索引中 【8】filebeat-收…

【并查集+组合计数】ABC226 E

E - Just one (atcoder.jp) 题意&#xff1a; 思路&#xff1a; 首先有个条件&#xff1a;每个点只有一个出边 如果没有直接看出结论&#xff0c;可以画几个链和树等特殊的图&#xff0c;可以发现&#xff0c;树是不满足条件的&#xff0c;环是满足条件的&#xff0c;因此可…

管理类联考——数学——记忆篇——数字编码

&#x1f3e0;个人主页&#xff1a;fo安方的博客✨ &#x1f482;个人简历&#xff1a;大家好&#xff0c;我是fo安方&#xff0c;考取过HCIE Cloud Computing、CCIE Security、CISP、RHCE、CCNP RS、PEST 3等证书。&#x1f433; &#x1f495;兴趣爱好&#xff1a;b站天天刷&…

Javaweb开发环境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程

Javaweb开发环境Myeclipse6.5 JDK1.6 Tomcat6.0 SVN1.8配置教程 javaweb开发环境的配置也是比较繁琐的一件事情&#xff0c;虽然理论上使用记事本&#xff0c;完全可以写出一个Javaweb工程&#xff0c; 但是在团队大型开发的Javaweb过程中&#xff0c;你必须配置好Eclipse、SV…

【算法集训之线性表篇】Day 08

文章目录 题目基本设计思想思路一思路二 代码实现效果 题目 已知一个整数数列A{a0,a1,a2,…,an-1}&#xff0c;其中0<ai<n(0<i<n)。若存在ap1ap2ap3…apmx且m>n/2(0<pk<n,1<k<m)&#xff0c;则成x为A的主元素。例如A{0,5,5,3,5,7,5,5}&#xff0c;…

Python模拟MQTT v3.1.1服务器

示例代码 import logging import asyncio from hbmqtt.broker import Broker# 设置日志级别为DEBUG logging.basicConfig(levellogging.DEBUG)# 创建MQTT服务器 broker Broker()# 启动MQTT服务器 async def start_broker():await broker.start()# 停止MQTT服务器 async def s…

好用到哭!后悔没有早点看到这个微信多账号管理工具!

作为一款及时通讯软件&#xff0c;微信是许多企业进行客户运营的选择工具。企业的员工利用微信与客户进行沟通&#xff0c;并且许多客单也都在微信上如数完成。然而&#xff0c;微信本身并不具备运营需要的很多功能&#xff0c;所以需要借助第三方工具来实现。 安全吗&#xf…

day20 数组指针

int main(void) {char *name[5] {"hello","china","beijing","project","Competer"};int i;for(i0;i<5;i){printf("%s\n",name[i]);//结果打印五个字符串}return 0; }数组指针&#xff0c;存了5个指针&#x…

串行FLASH文件系统FatFs-实际应用

目录 串行FLASH文件系统FatFs-实际应用 功能展示 程序代码 程序过程分析 串行FLASH文件系统FatFs-实际应用 功能展示 实验主要使用我们移植好的FatFs的FLASH文件系统实现三个功能&#xff1a;设备信息获取、文件定位写入功能测试和文件信息获取测试功能。 最终串口输出结…

4.带你入门matlab排序最值标准差标差(matlab程序)

%% 学习目标&#xff1a;排序&#xff0c;最值&#xff0c;标准差&#xff0c;方差 代码及结果 %% 排序 clear all; X[1 3 4;8 3 5;2 7 4] y1sort(X) %按列由小到大排序 y2sort(X,2) %按行由小到大排序 y3sort(X,1,descend) %按列由大…