读书笔记-《ON JAVA 中文版》-摘要21第十九章 类型信息-2]

news2024/11/17 16:01:33

文章目录

  • 第十九章 类型信息
    • 7. 动态代理
    • 8. Optional类
    • 9. 接口和类型
    • 10. 本章小结

第十九章 类型信息

7. 动态代理

代理是基本的设计模式之一。一个对象封装真实对象,代替其提供其他或不同的操作—这些操作通常涉及到与“真实”对象的通信,因此代理通常充当中间对象。这是一个简单的示例,显示代理的结构:

interface Interface {
    void doSomething();

    void somethingElse(String arg);
}

class RealObject implements Interface {
    @Override
    public void doSomething() {
        System.out.println("doSomething");
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("somethingElse " + arg);
    }
}

class SimpleProxy implements Interface {
    private Interface proxied;

    public SimpleProxy(Interface proxied) {
        this.proxied = proxied;
    }

    @Override
    public void doSomething() {
        System.out.println("SimpleProxy doSomething");
        proxied.doSomething();
    }

    @Override
    public void somethingElse(String arg) {
        System.out.println("SimpleProxy somethingElse " + arg);
        proxied.somethingElse(arg);
    }
}

public class SimpleProxyDemo {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        consumer(new RealObject());
        System.out.println("------------");
        consumer(new SimpleProxy(new RealObject()));
    }
}

输出:

doSomething
somethingElse bonobo
------------
SimpleProxy doSomething
doSomething
SimpleProxy somethingElse bonobo
somethingElse bonobo

Java 的动态代理更进一步,不仅动态创建代理对象而且动态处理对代理方法的调用。在动态代理上进行的所有调用都被重定向到单个调用处理程序,该处理程序负责发现调用的内容并决定如何处理。这是 SimpleProxyDemo.java 使用动态代理重写的例子:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

class DynamicProxyHandler implements InvocationHandler {
    private Object proxied;

    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(
                "**** proxy: " + proxy.getClass() +
                        ", method: " + method + ", args: " + args);
        if (args != null) {
            for (Object arg : args) {
                System.out.println(" " + arg);
            }
        }
        return method.invoke(proxied, args);
    }
}

public class SimpleDynamicProxy {
    public static void consumer(Interface iface) {
        iface.doSomething();
        iface.somethingElse("bonobo");
    }

    public static void main(String[] args) {
        RealObject real = new RealObject();
        consumer(real);
        System.out.println("-------------");
        Interface proxy = (Interface) Proxy.newProxyInstance(Interface.class.getClassLoader(),
                new Class[]{Interface.class},
                new DynamicProxyHandler(real));
        consumer(proxy);
    }
}

输出:

doSomething
somethingElse bonobo
-------------
**** proxy: class typeinfo.$Proxy0, method: public abstract void typeinfo.Interface.doSomething(), args: null
doSomething
**** proxy: class typeinfo.$Proxy0, method: public abstract void typeinfo.Interface.somethingElse(java.lang.String), args: [Ljava.lang.Object;@3b9a45b3
 bonobo
somethingElse bonobo

可以通过调用静态方法 Proxy.newProxyInstance() 来创建动态代理,该方法需要一个类加载器(通常可以从已加载的对象中获取),希望代理实现的接口列表(不是类或抽象类),以及接口 InvocationHandler 的一个实现。

8. Optional类

Optional 对象可以防止你的代码直接抛出 NullPointException 。

9. 接口和类型

interface 关键字的一个重要目标就是允许程序员隔离组件,进而降低耦合度。使用接口可以实现这一目标,但是通过类型信息,这种耦合性还是会传播出去——接口并不是对解耦的一种无懈可击的保障。比如我们先写一个接口:

package typeinfo.interfacea;

public interface A {
    void f();
}

然后实现这个接口,你可以看到其代码是怎么从实际类型开始顺藤摸瓜的:

package typeinfo;

import typeinfo.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(); // 报错
        System.out.println(a.getClass().getName());
        if(a instanceof B){
            B b = (B) a;
            b.g();
        }
    }
}

输出:

typeinfo.B

通过使用 RTTI,我们发现 a 是用 B 实现的。通过将其转型为 B ,我们可以调用不在 A 中的方法。这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 interface 关键字正在保护你,但其实并没有。

一种解决方案是直接声明,如果开发者决定使用实际的类而不是接口,他们需要自己对自己负责。

最简单的方式是让实现类只具有包访问权限,这样在包外部的客户端就看不到它了:

package typeinfo;

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();
    }
}

在这个包中唯一 public 的部分就是 HiddenC ,在被调用时将产生 A 接口类型的对象。这里有趣之处在于:即使你从 makeA() 返回的是 C 类型,你在包的外部仍旧不能使用 A 之外的任何方法,因为你不能在包的外部命名 C 。

如果你试着将其向下转型为 C ,则将被禁止,因为在包的外部没有任何 C 类型可用:

package typeinfo;

import typeinfo.interfacea.A;
import typeinfo.packageaccess.HiddenC;

import java.lang.reflect.Method;

public class HiddenImplementation {
    public static void main(String[] args) throws Exception {
        A a = HiddenC.makeA();
        a.f(); // PS:只能点出 f()
        System.out.println(a.getClass().getName());
//        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);
    }
}

输出:

public C.f()
typeinfo.packageaccess.C
public C.g()
package C.u()
protected C.v()
private C.w()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NuG8hQCf-1691139821261)(img/191.png)]

通过使用反射,仍然可以调用所有方法,甚至是 private 方法!如果知道方法名,你就可以在其 Method 对象上调用 setAccessible(true) ,就像在 callHiddenMethod() 中看到的那样。

你可能觉得,可以通过只发布编译后的代码来阻止这种情况,但其实这并不能解决问题。因为只需要运行 javap (一个随 JDK 发布的反编译器)即可突破这一限制。

那如果把接口实现为一个私有内部类,又会怎么样呢?下面展示了这种情况:

package typeinfo;

import typeinfo.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");
    }
}

输出:

public C.f()
typeinfo.InnerA$C
public C.g()
package C.u()
protected C.v()
private C.w()

这里对反射仍然没有任何东西可以隐藏。那么如果是匿名类呢?

package typeinfo;

import typeinfo.interfacea.A;

class AnonymousA {
    public static A makeA() {
        return new A() {
            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 AnonymousImplementation {
    public static void main(String[] args) throws Exception {
        A a = AnonymousA.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");
    }
}

看起来任何方式都没法阻止反射调用那些非公共访问权限的方法。对于字段来说也是这样,即便是 private 字段:

package typeinfo;

import java.lang.reflect.Field;

class WithPrivateFinalField {
    private int i = 1;
    private final String s = "I'm totally safe";
    private String s2 = "Am I safe?";

    @Override
    public String toString() {
        return "i = " + i + ", " + s + ", " + s2;
    }
}

public class ModifyingPrivateFields {
    public static void main(String[] args) throws Exception {
        WithPrivateFinalField pf = new WithPrivateFinalField();
        System.out.println(pf);
        Field f = pf.getClass().getDeclaredField("i");
        f.setAccessible(true);
        System.out.println("f.getInt(pf): " + f.getInt(pf));
        f.setInt(pf, 47);
        System.out.println(pf);
        f = pf.getClass().getDeclaredField("s");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));
        f.set(pf, "No, you're not!");
        System.out.println(pf);
        f = pf.getClass().getDeclaredField("s2");
        f.setAccessible(true);
        System.out.println("f.get(pf): " + f.get(pf));
        f.set(pf, "No, you're not!");
        System.out.println(pf);
    }
}

输出:

i = 1, I'm totally safe, Am I safe?
f.getInt(pf): 1
i = 47, I'm totally safe, Am I safe?
f.get(pf): I'm totally safe
i = 47, I'm totally safe, Am I safe?
f.get(pf): Am I safe?
i = 47, I'm totally safe, No, you're not!

但实际上 final 字段在被修改时是安全的。运行时系统会在不抛出异常的情况下接受任何修改的尝试,但是实际上不会发生任何修改。

—PS:final 不可修改,例如上面的变量 s

推荐大佬文章:

Java反射(通俗易懂)

Java动态代理

什么是java rtti_浅析Java RTTI 和 反射的概念

10. 本章小结

RTTI 允许通过匿名类的引用来获取类型信息。

面向对象编程语言是想让我们尽可能地使用多态机制,只在非用不可的时候才使用 RTTI。

然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。

最后一点,RTTI 有时候也能解决效率问题。假设你的代码运用了多态,但是为了实现多态,导致其中某个对象的效率非常低。这时候,你就可以挑出那个类,使用 RTTI 为它编写一段特别的代码以提高效率。然而必须注意的是,不要太早地关注程序的效率问题,这是个诱人的陷阱。最好先让程序能跑起来,然后再去看看程序能不能跑得更快,下一步才是去解决效率问题(比如使用 Profiler)5。

我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。

自我学习总结:

  1. RTTI 是在编译期,反射是在运行期
  2. 通过反射可以获取类的所有方法和字段
  3. 在java的java.lang.reflect包下提供了一个Proxy类和一个InvocationHandler接口,通过这个类和这个接口可以生成JDK动态代理类和动态代理对象
    在这里插入图片描述
    (图网,侵删)

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

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

相关文章

Shell脚本学习-for循环结构4

案例1: 批量创建10个系统账号chang01~chang10,并设置密码(密码不能相同)。(密码随机8位字符) 脚本: #!/bin/bashfor i in seq -w 10 douseradd chang$i &&\echo "root$i" …

网络——初识网络

网络基础 文章目录 网络基础计算机网络产生的背景认识网络协议网络协议初识协议分层OSI七层模型TCP/IP四层模型网络传输基本流程协议报头 认识IP地址认识MAC地址ifconfig查看主机地址ifconfig查看主机地址 计算机网络产生的背景 独立模式:计算机之间相互独立 早期的…

Windows安装JDK和JRE的方法

原文网址:Windows安装JDK和JRE的方法_IT利刃出鞘的博客-CSDN博客 简介 本文介绍Windows安装JDK和JRE(Java8)的方法。 下载 下载入口:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html jdk-8…

docker compose一键部署lnmt环境

创建docker compose 目录 [rootlocalhost ~]# mkdir -p /compose_lnmt 编写nginx的dockerfile文件 创建目录 [rootlocalhost compose_lnmt]# mkdir -p nginx 编写nginx配置文件 [rootlocalhost nginx]# vim nginx.conf user root; #运行身份#nginx自动设置进程…

socket server服务器开发常见的并发模型

两种高效的事件处理模式 服务器程序通常需要处理三类事件:I/O 事件、信号及定时事件。有两种高效的事件处理模式:Reactor和 Proactor,同步 I/O 模型通常用于实现Reactor 模式,异步 I/O 模型通常用于实现 Proactor 模式。 无论是 …

MySQL正则表达式检索数据

目录 一、使用正则表达式进行基本字符匹配 1.使用regexp关键字 2.使用正则表达式 . 二、进行OR匹配 1.为搜索两个串之一,使用 | 2.匹配几个字符之一[] 3.匹配范围 4.匹配特殊字符 过滤数据允许使用匹配、比较、通配符操作来寻找数据,但是随…

Maven依赖爆红的几种解决思路

说明:本文介绍Maven依赖爆红,排查错误的几种思路; 思路一:删除本地仓库.lastupdate文件; 找到本地maven仓库,全局搜索.lastupdate文件,把搜索出来的文件全部删除。.lastupdate后缀名的文件&am…

基础实验篇 | uORB消息读写与自定义实验(二)

导读 uORB是PX4/Pixhawk系统中非常重要且关键的模块之一,是用于无人机模块间通信的协议机制。本篇将详细介绍uORB并详细拆解uORB消息读写与自定义实验全流程(二)。 基础实验篇 | uORB消息读写与自定义实验(二) 01 RflySim平台的uORB消息读写…

分析Python招聘数据,可视化展示招聘信息详情

前言 一. 数据来源分析 1. 明确需求 明确采集网站以及数据内容 数据: 职位信息 网址: https://we.51job.com/pc/search?keywordpython&searchType3&sortType0&metro2. 抓包分析 通过开发者工具进行抓包分析 I. 打开开发者工具: F12 / 右键点击检查选择networ…

Windows下JDK安装与环境变量配置

文章目录 每日一句正能量前言安装步骤配置环境变量验证环境变量是否配置成功后记 每日一句正能量 生命,就像一场永无休止的苦役,不要惧怕和拒绝困苦,超越困苦,就是生活的强者。任何经历都是一种累积,累积的越多,人就越成熟;经历的越多,生命就越有厚度。 本来不想写JDK的安装的&…

基于LLM的SQL应用程序开发实战(二)

基于LLM的SQL应用程序开发实战(二) 16.2 使用LangChain SQL代理 回到案例应用本身,我们使用“Run All”的方式重新运行一下,让大家看见更多内部的内容,如图16-5所示,因为在VSCode代码编辑器中,可以看见Jupyter关于当前应用的变量(variable)。 图16- 5 查询Jupyter V…

新版发布 | Cloudpods v3.10.4 和 v3.9.12 正式发布

Cloudpods v3.10.4 功能优化 【主机】支持 PVE 资源的生命周期管理 【费用】优化阿里云账单资源类型名称(企业版) 【主机】选择 VMware 平台镜像,磁盘支持容量变更 【主机】在线修改密码需先探测 QGA 状态 【主机】热迁移取消”快速收敛…

【ARM64 常见汇编指令学习 13 -- ARM 汇编 ORG 伪指令学习】

文章目录 ARM ORG 指令介绍UEFI 中对 ORG 指令的使用 ARM ORG 指令介绍 在ARM汇编中,"org"是一个汇编器伪指令,用于设置下一条指令的装入地址。"org"后面跟着的是一个表达式,这个表达式的值就是下一条指令的装入地址。如…

基于STM32设计的智能空调

一、项目背景 随着人们生活水平的不断提高,对居住环境的舒适度要求也越来越高。空调作为一种重要的家电设备,已经成为了现代家庭中必不可少的一部分。本文介绍了一种基于STM32的智能空调设计方案,可以自动地根据环境温度进行温度调节。 二、…

单价20块蓝牙耳机卖爆越南市场,现象级爆款出现?

以儒道为文化底蕴的越南,是与中国最为相近的东南亚国家,"快速增长的劳动人口相对年轻的社会群体"是很多人对越南这个国家的基本认知。背靠庞大的Z世代用户群体,越南社会年轻化消费需求暴涨,手机与数码品类商品作为“年轻…

OpenCL编程指南-9.1命令、队列、事件

概述 命令队列是OpenCL的核心。平台定义了一个上下文,其中包含一个或多个计算设备。每个计算设备可以有一个或多个命令队列。提交到这些队列的命令将完成OpenCL程序的具体工作。 在一个简单的OpenCL程序中,提交到一个命令队列的命令会按顺序执行。一个…

ThinkPHP5使用phpqrcode生成二维码

生成指定跳转地址二维码图片: 首先将下载好的phpqrcode.php文件放到指定目录内(我这里用的放在public/phpqrcode目录下),准备调用 之后控制器中调用 public function qrcode(){require_once "./phpqrcode/phpqrcode.php&quo…

浅谈能源管理系统在电子厂房中的应用

贾丽丽 安科瑞电气股份有限公司上海嘉定201801 摘要:以能耗管理系统在工业厂房的应用为例,介绍了系统架构及功能。重点分析能耗管理系统在工业厂房实施过程中遇到的难点,并对系统采集的数据进行分析,提出了相应的节能措施&#…

【百问百答】可靠性基础知识第七期

1.什么是振动频率范围? 振动频率范围表示振动试验由某个频率点到另一个频率点进行往复扫频。 例如:试验频率范围5~500Hz,表示5Hz到500Hz进行往复扫频 2.什么是振动量? 振动量:通常用加速度和位移来表示; 加速度:表…

轻松延长手机待机时长,2步设置让你告别电量焦虑

在现代社会中,手机成为了我们生活中不可或缺的伙伴。然而,随着手机功能的日益增多和使用频率的提高,电池续航时间成为了让许多人苦恼的问题。谁不曾遇到过电量不足的尴尬情况?当我们需要手机时,却发现它只剩下最后一丝…