初识Java 17-4 反射

news2024/12/30 2:01:32

本笔记参考自: 《On Java 中文版》


接口和类型信息

        interface关键字的一个重要目标就是允许程序员隔离组件,减少耦合。但我们可以通过类型信息来绕过接口的隔离,这使得接口不一定能够保证解耦。

        为了演示这一实现,我们需要先创建一个接口:

package reflection.interfacea;

public interface A {
    void f();
}

        接下来的例子展示了如何绕过接口,偷偷访问实际的实现类型:

package reflection;

import reflection.interfacea.A;

class B implements A {
    @Override
    public void f() {
    }

    public void g() {
    }
}

public class InterfaceViolation {
    public static void main(String[] args) {
        A a = new B();
        a.f();
        // a.g(); // 此时还不能访问方法g()

        System.out.println(a.getClass().getName());
        if (a instanceof B) {
            B b = (B) a;
            b.g();
        }
    }
}

        通过反射,我们将a强制转换成了B类,以此来调用A中不存在的方法。

        很显然,这种实现是合理的。但当一些客户程序员使用这些代码时,他们也可能会通过这种方式的调用,使得其代码与我们的代码之间的耦合程度超出我们的预期。换言之,instanceof并不能够保护我们的代码。

    Windows系统就存在类似的问题……

        这时有两种解决方案:①直接声明,让客户程序员自己承当使用额外代码带来的后果。②而另一种方法,就是对代码的访问权限加以控制:

【例子:通过包访问权限隔绝包外的访问】

package reflection.packageaccess;

import reflection.interfacea.A;

class C implements A {
    @Override
    public void f() {
        System.out.println("public C.f()");
    }

    public void g() {
        System.out.println("public C.g()");
    }

    void u() {
        System.out.println("package C.u()");
    }

    protected void v() {
        System.out.println("protected C.v()");
    }

    private void w() {
        System.out.println("private C.w()");
    }
}

public class HiddenC {
    public static A makeA() {
        return new C();
    }
}

        我们创建了一个接口A的实现:C类,并将其放到一个单独的包中。代码中只有HiddenC类存在一个与外界通信的接口makeA()

        遗憾的是,我们依旧有办法绕过包的隐藏:

【例子:绕过包隐藏】

package reflection;

import reflection.packageaccess.HiddenC;
import reflection.interfacea.A;

import java.lang.reflect.Method;

public class HiddenImplementation {
    public static void main(String[] args)
            throws Exception {
        A a = HiddenC.makeA();
        a.f();
        // 通过反射可以得到隐藏的类名
        System.out.println(a.getClass().getName());
        // 编译错误,无法找到"C":
        /* if (a instanceof C) {
            C c = (C) a;
            c.g();
        } */

        // 但依旧可以通过反射调用被隐藏起来的方法:
        callHiddenMethod(a, "g");
        // 以及访问权限更小的方法:
        callHiddenMethod(a, "u");
        callHiddenMethod(a, "v");
        callHiddenMethod(a, "w");
    }

    static void callHiddenMethod(
            Object a, String methodName)
            throws Exception {
        Method g =
                a.getClass().getDeclaredMethod(methodName);
        g.setAccessible(true);
        g.invoke(a); // 将获得的方法g()重定位到a
    }
}

        程序执行的结果是:

        即使访问权限限制了使用者对类的直接访问,反射依旧提供了足以调用所有方法的能力,只需要我们知道类中方法的名字即可。

        并且,即使我们只发布代码的已编译版本,JDK自带的反编译器依旧可以展示出文件中所有的成员。JDK的反编译命令如下:

javap -private C

        执行上述命令,可得到如下结果:

通过这种方式,任何人都可以看到被隐藏的方法或签名(并调用它们)

        即使内部私有类也不能例外:

【例子:利用反射访问私有内部类】

package reflection;

import reflection.interfacea.A;

class InnerA {
    private static class C implements A {
        @Override
        public void f() {
            System.out.println("public C.f()");
        }

        public void g() {
            System.out.println("public C.g()");
        }

        void u() {
            System.out.println("package C.u()");
        }

        protected void v() {
            System.out.println("protected C.v()");
        }

        private void w() {
            System.out.println("private C.w()");
        }
    }

    public static A makeA() {
        return new C();
    }
}

public class InnerImplementation {
    public static void main(String[] args)
            throws Exception {
        A a = InnerA.makeA();
        a.f();
        System.out.println(a.getClass().getName());

        // 通过反射访问私有类内部:
        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
}

        程序执行的结果是:

        匿名类也是如此(代码结构与上面的大致相同):

【例子:通过反射访问匿名类】

package reflection;

import reflection.interfacea.A;

class AnnoymousA {
    public static A makeA() {
        return new A() {
            @Override
            public void f() {
                System.out.println("public C.f()");
            }

            public void g() {
                System.out.println("public C.g()");
            }

            void u() {
                System.out.println("package C.u()");
            }

            protected void v() {
                System.out.println("protected C.v()");
            }

            private void w() {
                System.out.println("private C.w()");
            }
        };
    }
}

public class AnnoymousImplementation {
    public static void main(String[] args)
            throws Exception {
        A a = AnnoymousA.makeA();
        a.f();
        System.out.println(a.getClass().getName());

        // 通过反射访问匿名类内部:
        HiddenImplementation.callHiddenMethod(a, "g");
        HiddenImplementation.callHiddenMethod(a, "u");
        HiddenImplementation.callHiddenMethod(a, "v");
        HiddenImplementation.callHiddenMethod(a, "w");
    }
}

        程序执行的结果是:

        除此之外,也可以通过反射访问字段:

【例子:通过反射访问字段】

package reflection;

import java.lang.reflect.Field;

class WithPrivateFinalField {
    private int i = 1;
    private final String s = "这条语句是private final的";
    private String s2 = "这条语句是private的";

    @Override
    public String toString() {
        return "该类拥有的private字段如下:\n\t" +
                "i = " + i + "\n\t" +
                "s = " + s + "\n\t" +
                "s2 = " + s2;
    }
}

public class ModifyingPrivateFields {
    public static void main(String[] args)
            throws Exception {
        WithPrivateFinalField pf =
                new WithPrivateFinalField();
        System.out.println(pf);

        System.out.println("\n通过反射访问字段:");
        // Field类可用于反射字段
        Field field = pf.getClass().getDeclaredField("i");
        field.setAccessible(true); // 允许访问
        System.out.println("f.getInt(pf):"
                + field.getInt(pf));
        field.setInt(pf, 47);
        System.out.println("使用setInt()改变i的值," + pf);

        System.out.println("=====");
        field = pf.getClass().getDeclaredField("s");
        field.setAccessible(true); // 允许访问
        System.out.println("f.get(pf):"
                + field.get(pf));
        field.set(pf, "尝试改变s");
        System.out.println("使用set()无法改变s的值," + pf);

        System.out.println("=====");
        field = pf.getClass().getDeclaredField("s2");
        field.setAccessible(true); // 允许访问
        System.out.println("f.get(s2):"
                + field.get(pf));
        field.set(pf, "不安全");
        System.out.println("使用set()改变s的值," + pf);
    }
}

        程序执行的结果是:

        不过final字段还是安全的,不会因为反射而反射变化。

        一般而言,反射带来的麻烦不会有想象中的那么大,因为如果有人使用了反射,那么他们也应该承受代码改变带来的风险。并且,Java提供这样一个后门来访问类,确实可以解决一些问题。

    注意:面向对象编程语言要求,在任何可能的地方使用多态,而只在必要的地方使用反射(若一定要使用,可以将反射放到一个特定的类中进行使用。但我们也可能找到一个更好的替代方案)

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

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

相关文章

LabVIEW调用库函数节点无法显示DLL中的函数

LabVIEW调用库函数节点无法显示DLL中的函数 正在使用调用库函数节点来调用一个DLL文件。可是,当浏览该DLL时,却无法在Function Name下拉菜单中选择任何函数。为什么所有的DLL函数都无法选中呢? 解答: 调用的DLL可能是通过.NET封装的&#x…

1.77亿美元,安世被迫出售晶圆大厂NWF | 百能云芯

11月9日消息,安世半导体(Nexperia)与纽交所上市公司威世(Vishay)签署协议,作价1.77亿美元出售英国Newport Wafer Fab(以下简称NWF)的母公司NEPTUNE 6 LIMITED(以下简称“…

kubectl 资源管理命令-陈述式

目录 一、kubectl陈述式资源管理: 二、kubectl陈述式对象管理: 1.基础命令使用: 1.1 帮助手册: 1.2 查看版本信息: ​编辑 1.3 查看资源对象简写: 1.4 查看集群信息: 1.5 配置kubectl自动补全: 1.6 node节点查看日志…

使用Net2FTP轻松打造免费的Web文件管理器并公网远程访问

文章目录 1.前言2. Net2FTP网站搭建2.1. Net2FTP下载和安装2.2. Net2FTP网页测试 3. cpolar内网穿透3.1.Cpolar云端设置3.2.Cpolar本地设置 4.公网访问测试5.结语 1.前言 文件传输可以说是互联网最主要的应用之一,特别是智能设备的大面积使用,无论是个人…

【LeetCode百道热题】1.两数之和

一,题目描述 给定一个整数数组nums和一个整数目标值target,请你在改数组中找出和为目标值target的那两个整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案,但是,数组中同一个元素在答案里不能重复出现…

Win10 180天后怎么才能继续体验,自动保持续期,无需手动JH

环境: Win10 专业版 自制小程序 问题描述: Win10 180天后怎么才能继续体验,自动保持续期,无需手动JH 解决方案: 在执行本程序前需要以管理员身份运行!关闭杀毒软件,否则会失败,本方案只能在个人电脑测试体验, 只能用于学习测试体验 ,勿用与商业行为 1.先完全JH…

RFID电力资产全周期智能化管理应用解决方案

电力行业需求 国家电网提出了建设“泛在电力物联网”的计划,旨在利用现代信息技术和先进通信技术,实现电力系统各环节的万物互联,构建一个具备全面感知、高效处理和便捷灵活特征的智慧服务系统,其中,重点方向之一是围…

深眸科技聚焦3D机器视觉技术,从技术形态到应用前景实现详细分析

机器视觉技术的不断升级,使得对二维图像的处理逐渐扩展到了更复杂的三维领域,形成了3D机器视觉。3D机器视觉是机器视觉的重要应用领域之一,通过计算机能够在短时间内处理视觉传感器采集的图像信号,从而获得目标对象的三维信息。 …

2023云栖大会,Salesforce终敲开中国CRM市场

2015年被视为中国CRM SaaS元年,众多CRM SaaS创业公司和厂商在Salesforce的榜样作用下涌入了CRM SaaS赛道。在全球市场,Salesforce是CRM SaaS领域的领导厂商,连续多年占据了全球CRM SaaS第一大厂商地位。然而,Salesforce作为业务类…

22款奔驰GLS450升级HUD抬头显示简洁的展示

说起HUD抬头显示这个配置,最初是用在战斗机上的,它可以让战斗机驾驶员读取飞机的各种信息和状态,而无需移动头部,这样就能够有效的提高效率。但随着汽车技术的进步HUD这种配置也逐渐下放到民用车上。发展到今,车上的抬…

Day45 力扣动态规划 : 1143.最长公共子序列 |1035.不相交的线 | 53. 最大子序和

Day45 力扣动态规划 : 1143.最长公共子序列 |1035.不相交的线 | 53. 最大子序和 1143.最长公共子序列第一印象看完题解的思路实现中的困难感悟代码 1035.不相交的线第一印象感悟代码 53. 最大子序和第一印象dp递推公式初始化遍历顺序 实现中的困难感悟代…

面试分享 | 护网蓝队面试经验

关于蓝队面试经验 1.自我介绍能力 重要性 为什么将自我介绍能力放在第一位,实际上自我介绍才是面试中最重要的一点,因为护网面试并没有确定的题目,让面试官去提问 更多是的和面试官的一种 “交谈” ,面试的难易程度也自然就取决…

C++ —— map 和 multimap

一、map 1.介绍 1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元 素。 2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同&am…

二十、泛型(5)

本章概要 边界通配符 编译器有多聪明逆变无界通配符捕获转换 边界 边界(bounds)在本章的前面进行了简要介绍。边界允许我们对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是我们可以在…

获取AAC音频的ADTS固定头部信息

文章目录 前言一、AAC音频中的ADTS二、解析ADTS信息1.标准文档中介绍2.解析3.采样率索引和值4.下载AAC标准文档 前言 调试嵌入式设备中播放aac音频的过程中,了解了aac音频格式,记录在此,防止遗忘。 一、AAC音频中的ADTS ADTS(Audi…

代码随想录day2

目录 vscode 自定义代码模板Reference vscode 自定义代码模板 select User snippets from Settings on the bottom left corner. select a certain language for example: cpp create your own snippets 格式如下,防着写 第一行"cpp template",模板…

Kubernetes 中 RBAC、ServiceAccount 的区别和联系

Author:rab 目录 前言一、区别二、联系三、案例思考? 前言 首先,Kubernetes (K8s) RBAC (Role-Based Access Control) 和 ServiceAccount 都是 Kubernetes 中用于控制访问权限的两个重要概念,但是它们之间有一些区别和联系。 一…

chatgpt==对接API

来到首页 https://platform.openai.com/docs/overview quickstart turorial 生成API KEY https://platform.openai.com/api-keys 来体验下 setx OPENAI_API_KEY "your-api-key-here" echo %OPENAI_API_KEY% 编写PYTHON代码 pip install --upgrade openai from …

ChatGPT 报错“Sorry, you have been blocked…” 什么原因?如何解决?

原因: 频繁切换节点,使用免费代理,账号被锁定 解决办法: 遇到这种情况,请暂时先关闭代理(VPN),停止账号登陆,过段时间或隔天再试,防止账号被封。另外不建议使…

大模型在时间序列预测领域的最新15篇论文

最近在和大佬朋友们交流的时候,发现时间序列领域有一个很有潜力的新方向:大模型时间序列。 大模型可以处理不同类型的时间序列数据,例如文本、图像、音频等,也可以适应不同的时间序列数据的变化和异常情况,有助于提高…