十九、类型信息(6)

news2024/11/16 9:45:33

接口和类型

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

public interface A {
    void f();
}

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

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

输出结果:

com.example.test.B

通过使用 RTTI,我们发现 a 是用 B 实现的。通过将其转型为 B,我们可以调用不在 A 中的方法。

这样的操作完全是合情合理的,但是你也许并不想让客户端开发者这么做,因为这给了他们一个机会,使得他们的代码与你的代码的耦合度超过了你的预期。也就是说,你可能认为 interface 关键字正在保护你,但其实并没有。另外,在本例中使用 B 来实现 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();
    }
}

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

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

import java.lang.reflect.*;

public class HiddenImplementation {
    public static void main(String[] args) throws Exception {
        A a = HiddenC.makeA();
        a.f();
        System.out.println(a.getClass().getName());
        // Compile error: cannot find symbol 'C':
        /* if(a instanceof C) {
            C c = (C)a;
            c.g();
        } */
        // Oops! Reflection still allows us to call g():
        callHiddenMethod(a, "g");
        // And even less accessible methods!
        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);
    }
}

输出结果:

在这里插入图片描述

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

本章小结

RTTI 允许通过匿名类的引用来获取类型信息。初学者极易误用它,因为在学会使用多态调用方法之前,这么做也很有效。有过程化编程背景的人很容易把程序组织成一系列 switch 语句,你可以用 RTTI 和 switch 实现功能,但这样就损失了多态机制在代码开发和维护过程中的重要价值。面向对象编程语言是想让我们尽可能地使用多态机制,只在非用不可的时候才使用 RTTI。

然而使用多态机制的方法调用,要求我们拥有基类定义的控制权。因为在你扩展程序的时候,可能会发现基类并未包含我们想要的方法。如果基类来自别人的库,这时 RTTI 便是一种解决之道:可继承一个新类,然后添加你需要的方法。在代码的其它地方,可以检查你自己特定的类型,并调用你自己的方法。这样做不会破坏多态性以及程序的扩展能力,因为这样添加一个新的类并不需要修改程序中的 switch 语句。但如果想在程序中增加具有新特性的代码,你就必须使用 RTTI 来检查这个特定的类型。

如果只是为了方便某个特定的类,就将某个特性放进基类里边,这将使得从那个基类派生出的所有其它子类都带有这些可能毫无意义的东西。这会导致接口更加不清晰,因为我们必须覆盖从基类继承而来的所有抽象方法,事情就变得很麻烦。举个例子,现在有一个表示乐器 Instrument 的类层次结构。

假设我们想清理管弦乐队中某些乐器残留的口水,一种办法是在基类 Instrument 中放入 clearSpitValve() 方法。但这样做会导致类结构混乱,因为这意味着打击乐器 Percussion、弦乐器 Stringed 和电子乐器 Electronic 也需要清理口水。在这个例子中,RTTI 可以提供一种更合理的解决方案。可以将 clearSpitValve() 放在某个合适的类中,在这个例子中是管乐器 Wind。不过,在这里你可能会发现还有更好的解决方法,就是将 prepareInstrument() 放在基类中,但是初次面对这个问题的读者可能想不到还有这样的解决方案,而误认为必须使用 RTTI。

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

我们已经看到,反射,因其更加动态的编程风格,为我们开创了编程的新世界。但对有些人来说,反射的动态特性却是一种困扰。对那些已经习惯于静态类型检查的安全性的人来说,Java 中允许这种动态类型检查(只在运行时才能检查到,并以异常的形式上报检查结果)的操作似乎是一种错误的方向。

有些人想得更远,他们认为引入运行时异常本身就是一种指示,指示我们应该避免这种代码。我发现这种意义的安全是一种错觉,因为总是有些事情是在运行时才发生并抛出异常的,即使是在那些不包含任何 try 语句块或异常声明的程序中也是如此。因此,我认为一致性错误报告模型的存在使我们能够通过使用反射编写动态代码。当然,尽力编写能够进行静态检查的代码是有价值的,只要你有这样的能力。但是我相信动态代码是将 Java 与其它诸如 C++ 这样的语言区分开的重要工具之一。

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

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

相关文章

Java实验二类编程实验

1.编写一个代表三角形的类(Triangle.java)。 其中,三条边a,b,c(数据类型为double类型)为三角形的属性,该类封装有求三角形的面积和周长的方法。分别针对三条边为3、4、5和7、8、9的两个三角形进行测试&…

软件测试:postman使用总结

一、为何使用postman postman是一款简单高效的接口测试工具,能够很方便发送接口请求,易于保存接口请求脚本,postman提供接口响应数据比对功能,可以设置预期结果作断言,还能把测试用例放在一个集合中批量执行&#xff…

七月论文审稿GPT第二版:从Meta Nougat、GPT4审稿到mistral、llama longlora

前言 如此前这篇文章《学术论文GPT的源码解读与微调:从chatpaper、gpt_academic到七月论文审稿GPT》中的第三部分所述,对于论文的摘要/总结、对话、翻译、语法检查而言,市面上的学术论文GPT的效果虽暂未有多好,可至少还过得去&am…

图论问题建模和floodfill算法

目录 引入:leetcode695.岛屿的最大面积 分析与转换 一维二维转换 四联通 完整代码解答: 1)显示的创建图解决问题的代码 2)不显示的创建图解决此问题的代码 floodfill算法 定义 引入:leetcode695.岛屿的最大面…

精准测试:提高软件质量和用户满意度的利器

📢专注于分享软件测试干货内容,欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!📢交流讨论:欢迎加入我们一起学习!📢资源分享:耗时200小时精选的「软件测试」资…

c语言练习(9周)(16~20)

输入12个一位整数,创建二维数组a[3][4],显示二维数组及各列的平均值,平均值四舍五入到小数点后一位。 题干输入12个一位整数,创建二维数组a[3][4],显示二维数组及各列的平均值,平均值四舍五入到小数点后一…

华为云服务器,在线安装MySQL

需求 在华为云服务器上,部署MySQL数据库,通过 公网IP 访问数据库。 通过 yum ,在线安装MySQL;配置远程连接,开放3306端口,能够通过公网访问。 云服务器配置说明 本文所使用的 华为云服务器 配置如下。 …

有关常见的#define定义的函数的陷阱和修正(详解)

一、#define f(x) x*x #include<stdio.h> #define f(x) x*x int main() {int a6,b2,c;cf(a)/f(b);printf("f(a)%d\n",f(a));//6*6printf("f(b)%d\n",f(b));//2*2printf("f(b1)%d\n",f(b1));//21*21; printf("f(b2)%d\n",f(b2))…

如何回答好“测得怎么样了?”

有测试员抱怨开发很糟糕&#xff0c;但我们没办法要求开发在会写代码的同时还要把代码写好&#xff0c;没有过多的bug&#xff0c;因为这就是我们的工作&#xff1b;测试员吐槽自己的老板很较真&#xff0c;但我们没办法拒绝领导们的批评指责&#xff0c;因为批评代表我们还有继…

VFIO的使用及原理

vfio设备透传主要用于将设备直通给虚拟机以提高性能&#xff0c;本篇以一张网卡为例讲述VFIO设备的配置使用及底层原理。其中涉及的技术背景主要有linuxqemukvmvfio。 一、VFIO网卡的配置使用 1.host配置iommu 首先是宿主机host必须支持硬件虚拟化技术&#xff0c;如x86架构…

是谁家班主任还不知道 怎么发布期中成绩啊。

你知道吗&#xff1f;居然还有班主任不知道怎么发布期中成绩&#xff01; 发布成绩并不是一件难事&#xff0c;只需几个步骤&#xff0c;就能轻松搞定&#xff01; 给大家讲一下成绩查询是什么。成绩查询是指学生通过一定的方式&#xff0c;如输入学号、姓名等&#xff0c;在指…

小程序如何设置自动使用物流账号发货

小程序支持自动使用物流账号发货并生成运单号。商家需要与物流公司合作&#xff0c;获取物流账号&#xff0c;支持快递物流和同城外卖配送平台。具体方法请参考公众号之前发布的文章&#xff0c;例如可以搜索“快递账号”。 导入物流账号后&#xff0c;在小程序管理员后台->…

Ansible中的变量及加密

目录 1.变量命名 2.变量级别 3.变量设定和使用方式 在playbook中直接定义变量 在文件中定义变量 使用变量 设定主机变量和清单变量 目录设定变量 用命令覆盖变量 使用数组设定变量 ​编辑 注册变量 事实变量 ​编辑 魔法变量 JINJA2模板 j2模板书写规则 ​编辑 f…

【MySQL】 索引(上)

文章目录 1. 索引的概念2. MySQL与磁盘 的交互基本单位3. 建立共识4. 现象与结论如何理解mysql中page概念为什么 要采用page的方案 进行交互 而不是用多少加载多少&#xff1f; 5. 页目录为什么要引入 页目录概念单页情况多页情况使用B树 构建索引为什么不用其他数据结构为什么…

打造更智能的移动端平台,蚂蚁mPaaS5.0亮相云栖大会

11月2日&#xff0c;在云栖大会上&#xff0c;蚂蚁数科宣布mPaaS正式升级至5.0版本。mPaaS5.0融合了蚂蚁大模型框架下的多种算法能力&#xff0c;以AI智能引擎驱动移动端平台的运营、服务和体验管理实现全链路升级&#xff0c;助力金融机构及企业打造更加智能、更懂用户的移动端…

数据结构与算法:使用数组模拟环形队列Java版

文章目录 如何使用数组模拟队列环形队列逻辑分析自己写的听课笔记实现代码部分方法说明 如何使用数组模拟队列 不知道如何使用数组模拟队列的可以看上一篇文章 使用数组模拟队列点击跳转 环形队列逻辑分析 自己写的听课笔记 实现代码 package com.haimeng.queue;import java…

Java实验三类的继承与派生

1.定义一个Person类&#xff0c;包含姓名&#xff08;name&#xff09;、性别&#xff08;sex&#xff09;、年龄&#xff08;age&#xff09;等字段&#xff1b;继承Person类设计Teacher 类&#xff0c;增加职称&#xff08;pro&#xff09;、部门&#xff08;department&…

配置git并把本地项目连接github

一.配置git 1.下载git&#xff08;Git&#xff09;&#xff0c;但推荐使用国内镜像下载&#xff08;CNPM Binaries Mirror&#xff09; 选好64和版本号下载&#xff0c;全部点下一步 下载完成后打开终端&#xff0c;输入 git --version 出现版本号则说明安装成功 然后继续…

《巴渝小将》少儿电视综艺走进江小白金色黄庄拍摄圆满成功!

巴渝小将&#xff0c;乘风破浪&#xff01; 张扬巴渝魅力&#xff0c;展示少年风采&#xff0c;本期拍摄我们来到了位于江津的江小白金色黄庄。 江小白金色黄庄位于永兴镇黄庄村&#xff0c;是一座充满诗意又不乏童趣的农文旅综合体&#xff0c;基于当地良好的酿酒高粱产业基础…

Diango项目-简易个人博客项目

项目实现功能 在admin后台自定义添加上传文档。对展示在首页的文章分页显示。在首页点击文章的阅读全文按钮可进入该文章全文详情页进行浏览。对文章实现了内容分类何以发布时间进行归档分类。使用django的whoose搜索引擎对全文实现内容的搜索。 项目涉及技术 Mysql Djan…