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

news/2024/4/27 9:27:30/文章来源:https://blog.csdn.net/JustDI0209/article/details/132108464

文章目录

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

第十九章 类型信息

7. 动态代理

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

interface Interface {void doSomething();void somethingElse(String arg);
}class RealObject implements Interface {@Overridepublic void doSomething() {System.out.println("doSomething");}@Overridepublic void somethingElse(String arg) {System.out.println("somethingElse " + arg);}
}class SimpleProxy implements Interface {private Interface proxied;public SimpleProxy(Interface proxied) {this.proxied = proxied;}@Overridepublic void doSomething() {System.out.println("SimpleProxy doSomething");proxied.doSomething();}@Overridepublic 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;}@Overridepublic 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;@3b9a45b3bonobo
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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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?";@Overridepublic 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.luyixian.cn/news_show_340071.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Springboot部署ELK实战

Springboot部署ELK实战 1、部署docker、docker-compose环境安装docker安装docker-compose 2、搭建elk1、构建目录&&配置文件1、docker-compose.yml 文档2、Kibana.yml3、log-config.conf 2、添加es分词器插件3、启动 3、Springboot项目引入es、logStash配置1、引入依赖…

后端进阶之路——Spring Security构建强大的身份验证和授权系统(四)

前言 「作者主页」:雪碧有白泡泡 「个人网站」:雪碧的个人网站 「推荐专栏」: ★java一站式服务 ★ ★前端炫酷代码分享 ★ ★ uniapp-从构建到提升★ ★ 从0到英雄,vue成神之路★ ★ 解决算法,一个专栏就够了★ ★ 架…

WebDAV之π-Disk派盘 + DEVONthink

DEVONthink是由一家来自德国的老牌软件开发商发布的「知识管理」软件,运行于 Mac/iOS 平台。官方自己定位为全方位(中文环境下略有遗憾)帮助你实现知识管理,可以称之为“模块级”应用了。 DEVONthink还支持各种云服务同步,文件管理您的终极文件管理应用、文件、图片与连接远…

Centos7 安装yum

1、检查主机名和网络并且配置/etc/hosts文件 查看主机名:hostname 查看ip :ifconfig vi /etc/hosts//添加把主机名和IP配置进去hosts文件192.18.56.111 orcale12c2、关闭防火墙 systemctl status firewalld.service//检查防火墙状态 暂时关闭防火墙,下…

20.4 HTML 表单

1. form表单 <form>标签: 用于创建一个表单, 通过表单, 用户可以向网站提交数据. 表单可以包含文本输入字段, 复选框, 单选按钮, 下拉列表, 提交按钮等等. 当用户提交表单时, 表单数据会发送到服务器进行处理.action属性: 应指向一个能够处理表单数据的服务器端脚本或UR…

安卓证书生成教程

1.下载安装JDK文件&#xff08;如已安装请跳过&#xff09; 根据电脑系统版本下载JDK版本文件 下载地址&#xff1a;[https://www.oracle.com/java/technologies/downloads/](https://www.oracle.com/java/technologies/downloads/) 如果电脑上安装过JDK文件可以跳过2.生成密钥…

Spring源码篇(九)自动配置扫描class的原理

文章目录 前言ClassLoader如何加载jar包里的class自动配置扫描class的原理spring中的加载方式源码总结 前言 spring是怎样通过ComponentScan&#xff0c;或者自动配置扫描到了依赖包里class的&#xff1f; ClassLoader 这里涉及到了class Loader的机制&#xff0c;有些复杂&…

达芬奇架构 DaVinci Core - 小记

文章目录 官方文档 &#xff1a; HUAWEI Da Vinci Architecture https://support.huaweicloud.com/intl/en-us/odevg-A800_9000_9010/atlaste_10_0007.htmlPPT : DaVinci: A Scalable Architecture for Neural Network Computing https://www.cmc.ca/wp-content/uploads/2020/0…

一篇文章带你基本了解Java 集合框架、核心接口、以及需要掌握的各个数据结构

一篇文章带你基本了解Java 集合框架 基本概念&#xff1a; ​ 早在 Java 2 中之前&#xff0c;Java 就提供了特设类。比如&#xff1a;Dictionary, Vector, Stack, 和 Properties 这些类用来存储和操作对象组。 ​ Java集合框架&#xff08;Java Collections Framework&…

Pytorch Tutorial【Chapter 2. Autograd】

Pytorch Tutorial 文章目录 Pytorch TutorialChapter 2. Autograd1. Review Matrix Calculus1.1 Definition向量对向量求导1.2 Definition标量对向量求导1.3 Definition标量对矩阵求导 2.关于autograd的说明3. grad的计算3.1 Manual手动计算3.2 backward()自动计算 Reference C…

极光笔记 | 浅谈企业级SaaS产品的客户成长旅程管理(上)—— 分析篇

本文作者&#xff1a;陈伟&#xff08;极光用户体验部高级总监&#xff09; “企业级SaaS产品与C端互联网产品特征差异很大&#xff0c;有些甚至是截然相反&#xff0c;这些特征也会成为后续客户成长旅程的重要影响变量。本文就如何设计并服务好企业级SaaS产品客户成长旅程进行…

全网最强,Python接口自动化测试实战-接口参数关联(购物实例)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 什么是参数关联&a…

Spring之浅谈AOP技术

目录 前言 1.AOP的作用 2.AOP核心 Spring实现AOP 3.AOP工作流程 4.AOP核心概念 5.AOP通知类型 5.1类型介绍 5.2通知类型的使用 前置通知 后置通知 ​​​​​​​环绕通知 前言 AOP&#xff1a;Aspect Oriented Programming&#xff08;面向切面编程&#xff09;&…

收藏!9款好用的前端可视化工具推荐

“可视化开发”是上个世纪90年代软件界最大的热点之一。 当初&#xff0c;可视化开发主要专注于用户界面的构建&#xff0c;让开发者通过简单的拖拽操作&#xff0c;快速搭建用户界面&#xff0c;一些成熟产品更是实现了“所见即所得”。在与当时最先进的高级编程语言相比较时&…

01-序言

文章作者&#xff1a;里海 来源网站&#xff1a;https://blog.csdn.net/WangPaiFeiXingYuan 简介&#xff1a; 此专栏是学习“线性代数”课程做的笔记&#xff0c;教程来自B站的3Blue1Brown​​​​​​​d​​​​​​​。 视频作者是Grant Sanderson&#xff0c; 他本人是斯坦…

Redis两种持久化方案RDB持久化和AOF持久化

Redis持久化 Redis有两种持久化方案&#xff1a; RDB持久化AOF持久化 1.1.RDB持久化 RDB全称Redis Database Backup file&#xff08;Redis数据备份文件&#xff09;&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启…

【ASP.NET MVC】使用动软(三)(11)

一、问题 上文中提到&#xff0c;动软提供了数据库的基本操作功能&#xff0c;但是往往需要添加新的功能来解决实际问题&#xff0c;比如GetModel&#xff0c;通过id去查对象&#xff1a; 这个功能就需要进行改进&#xff1a;往往程序中获取的是实体的其他属性&#xff0c;比如…

Vue2 第十八节 插槽

1.默认插槽 2.具名插槽 3.作用域插槽 插槽 ① 作用&#xff1a;让父组件可以向子组件指定位置插入html结构&#xff0c;也是一种组件间通信的方式&#xff0c;适用于父组件和子组件间通信 ② 分类&#xff1a;默认插槽&#xff0c;具名插槽&#xff0c;作用域插槽 一.默认…

【Linux】创建与删除用户

新增用户&#xff1a; adduser 用户名【添加用户】 passwd 用户名【设置用户密码】删除用户&#xff1a; userdel -r 用户名【删除用户】

【机器学习】在 MLOps构建项目 ( MLOps2)

My MLOps tutorials: Tutorial 1: A Beginner-Friendly Introduction to MLOps教程 2&#xff1a;使用 MLOps 构建机器学习项目 一、说明 如果你希望将机器学习项目提升到一个新的水平&#xff0c;MLOps 是该过程的重要组成部分。在本文中&#xff0c;我们将以经典手写数字分类…