十九、类型信息(4)

本章概要

  • 注册工厂
  • 类的等价比较
  • 反射:运行时类信息
    • 类方法提取器

注册工厂

Pet 层次结构生成对象的问题是,每当向层次结构中添加一种新类型的 Pet 时,必须记住将其添加到 LiteralPetCreator.java 的条目中。在一个定期添加更多类的系统中,这可能会成为问题。

你可能会考虑向每个子类添加静态初始值设定项,因此初始值设定项会将其类添加到某个列表中。不幸的是,静态初始值设定项仅在首次加载类时调用,因此存在鸡和蛋的问题:生成器的列表中没有类,因此它无法创建该类的对象,因此类不会被加载并放入列表中。

基本上,你必须自己手工创建列表(除非你编写了一个工具来搜索和分析源代码,然后创建和编译列表)。所以你能做的最好的事情就是把列表集中放在一个明显的地方。层次结构的基类可能是最好的地方。

我们在这里所做的另一个更改是使用_工厂方法_设计模式将对象的创建推迟到类本身。工厂方法可以以多态方式调用,并为你创建适当类型的对象。事实证明,java.util.function.SupplierT get() 描述了原型工厂方法。协变返回类型允许 get()Supplier 的每个子类实现返回不同的类型。

在本例中,基类 Part 包含一个工厂对象的静态列表,列表成员类型为 Supplier<Part>。对于应该由 get() 方法生成的类型的工厂,通过将它们添加到 prototypes 列表向基类“注册”。奇怪的是,这些工厂本身就是对象的实例。此列表中的每个对象都是用于创建其他对象的_原型_:

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

// 注册工厂到基础类
class Part implements Supplier<Part> {
    @Override
    public String toString() {
        return getClass().getSimpleName();
    }

    static List<Supplier<? extends Part>> prototypes =
            Arrays.asList(
                    new FuelFilter(),
                    new AirFilter(),
                    new CabinAirFilter(),
                    new OilFilter(),
                    new FanBelt(),
                    new PowerSteeringBelt(),
                    new GeneratorBelt()
            );

    private static Random rand = new Random(47);

    @Override
    public Part get() {
        int n = rand.nextInt(prototypes.size());
        return prototypes.get(n).get();
    }
}

class Filter extends Part {
}

class FuelFilter extends Filter {
    @Override
    public FuelFilter get() {
        return new FuelFilter();
    }
}

class AirFilter extends Filter {
    @Override
    public AirFilter get() {
        return new AirFilter();
    }
}

class CabinAirFilter extends Filter {
    @Override
    public CabinAirFilter get() {
        return new CabinAirFilter();
    }
}

class OilFilter extends Filter {
    @Override
    public OilFilter get() {
        return new OilFilter();
    }
}

class Belt extends Part {
}

class FanBelt extends Belt {
    @Override
    public FanBelt get() {
        return new FanBelt();
    }
}

class GeneratorBelt extends Belt {
    @Override
    public GeneratorBelt get() {
        return new GeneratorBelt();
    }
}

class PowerSteeringBelt extends Belt {
    @Override
    public PowerSteeringBelt get() {
        return new PowerSteeringBelt();
    }
}

public class RegisteredFactories {
    public static void main(String[] args) {
        Stream.generate(new Part())
                .limit(10)
                .forEach(System.out::println);
    }
}

输出结果:

在这里插入图片描述

并非层次结构中的所有类都应实例化;这里的 FilterBelt 只是分类器,这样你就不会创建任何一个类的实例,而是只创建它们的子类(请注意,如果尝试这样做,你将获得 Part 基类的行为)。

因为 Part implements Supplier<Part>Part 通过其 get() 方法供应其他 Part。如果为基类 Part 调用 get()(或者如果 generate() 调用 get()),它将创建随机特定的 Part 子类型,每个子类型最终都从 Part 继承,并重写相应的 get() 以生成它们中的一个。

类的等价比较

当你查询类型信息时,需要注意:instanceof 的形式(即 instanceofisInstance() ,这两者产生的结果相同) 和 与 Class 对象直接比较 这两者间存在重要区别。下面的例子展示了这种区别:

// instanceof 与 class 的差别
class Base {
}

class Derived extends Base {
}

public class FamilyVsExactType {
    static void test(Object x) {
        System.out.println("Testing x of type " + x.getClass());
        System.out.println("x instanceof Base " + (x instanceof Base));
        System.out.println("x instanceof Derived " + (x instanceof Derived));
        System.out.println("Base.isInstance(x) " + Base.class.isInstance(x));
        System.out.println("Derived.isInstance(x) " + Derived.class.isInstance(x));
        System.out.println("x.getClass() == Base.class " + (x.getClass() == Base.class));
        System.out.println("x.getClass() == Derived.class " + (x.getClass() == Derived.class));
        System.out.println("x.getClass().equals(Base.class)) " + (x.getClass().equals(Base.class)));
        System.out.println("x.getClass().equals(Derived.class)) " + (x.getClass().equals(Derived.class)));
    }

    public static void main(String[] args) {
        test(new Base());
        test(new Derived());
    }
}

输出结果:

在这里插入图片描述

test() 方法使用两种形式的 instanceof 对其参数执行类型检查。然后,它获取 Class 引用,并使用 ==equals() 测试 Class 对象的相等性。令人放心的是,instanceofisInstance() 产生的结果相同, equals()== 产生的结果也相同。但测试本身得出了不同的结论。与类型的概念一致,instanceof 说的是“你是这个类,还是从这个类派生的类?”。而如果使用 == 比较实际的Class 对象,则与继承无关 —— 它要么是确切的类型,要么不是。

反射:运行时类信息

如果你不知道对象的确切类型,RTTI 会告诉你。但是,有一个限制:必须在编译时知道类型,才能使用 RTTI 检测它,并对信息做一些有用的事情。换句话说,编译器必须知道你使用的所有类。

起初,这看起来并没有那么大的限制,但是假设你引用了一个不在程序空间中的对象。实际上,该对象的类在编译时甚至对程序都不可用。也许你从磁盘文件或网络连接中获得了大量的字节,并被告知这些字节代表一个类。由于这个类在编译器为你的程序生成代码后很长时间才会出现,你如何使用这样的类?

在传统编程环境中,这是一个牵强的场景。但是,当我们进入一个更大的编程世界时,会有一些重要的情况发生。第一个是基于组件的编程,你可以在应用程序构建器_集成开发环境_中使用_快速应用程序开发_(RAD)构建项目。这是一种通过将表示组件的图标移动到窗体上来创建程序的可视化方法。然后,通过在编程时设置这些组件的一些值来配置这些组件。

这种设计时配置要求任何组件都是可实例化的,它公开自己的部分,并且允许读取和修改其属性。此外,处理_图形用户界面_(GUI)事件的组件必须公开有关适当方法的信息,以便 IDE 可以帮助程序员覆写这些事件处理方法。反射提供了检测可用方法并生成方法名称的机制。

在运行时发现类信息的另一个令人信服的动机是提供跨网络在远程平台上创建和执行对象的能力。这称为_远程方法调用_(RMI),它使 Java 程序的对象分布在许多机器上。这种分布有多种原因。如果你想加速一个计算密集型的任务,你可以把它分解成小块放到空闲的机器上。或者你可以将处理特定类型任务的代码(例如,多层次客户机/服务器体系结构中的“业务规则”)放在特定的机器上,这样机器就成为描述这些操作的公共存储库,并且可以很容易地更改它以影响系统中的每个人。分布式计算还支持专门的硬件,这些硬件可能擅长于某个特定的任务——例如矩阵转换——但对于通用编程来说不合适或过于昂贵。

Class 支持_反射_的概念, java.lang.reflect 库中包含类 FieldMethodConstructor(每一个都实现了 Member 接口)。这些类型的对象由 JVM 在运行时创建,以表示未知类中的对应成员。

然后,可以使用 Constructor 创建新对象,get()set() 方法读取和修改与 Field 对象关联的字段,invoke() 方法调用与 Method 对象关联的方法。此外,还可以调用便利方法 getFields()getMethods()getConstructors() 等,以返回表示字段、方法和构造函数的对象数组。(你可以通过在 JDK 文档中查找类 Class 来了解更多信息。)因此,匿名对象的类信息可以在运行时完全确定,编译时不需要知道任何信息。

重要的是要意识到反射没有什么魔力。当你使用反射与未知类型的对象交互时,JVM 将查看该对象,并看到它属于特定的类(就像普通的 RTTI)。在对其执行任何操作之前,必须加载 Class 对象。因此,该特定类型的 .class 文件必须在本地计算机上或通过网络对 JVM 仍然可用。因此,RTTI 和反射的真正区别在于,使用 RTTI 时,编译器在编译时会打开并检查 .class 文件。换句话说,你可以用“正常”的方式调用一个对象的所有方法。通过反射,.class 文件在编译时不可用;它由运行时环境打开并检查。

类方法提取器

通常,你不会直接使用反射工具,但它们可以帮助你创建更多的动态代码。反射是用来支持其他 Java 特性的,例如对象序列化。但是,有时动态提取有关类的信息很有用。

考虑一个类方法提取器。查看类定义的源代码或 JDK 文档,只显示_在该类定义中_定义或重写的方法。但是,可能还有几十个来自基类的可用方法。找到它们既单调又费时。幸运的是,反射提供了一种方法,可以简单地编写一个工具类自动地向你展示所有的接口:

import java.lang.reflect.*;
import java.util.regex.*;

// 使用反射展示一个类的所有方法,甚至包括定义在基类中方法
public class ShowMethods {
    private static String usage =
            "usage:\n" +
                    "ShowMethods qualified.class.name\n" +
                    "To show all methods in class or:\n" +
                    "ShowMethods qualified.class.name word\n" +
                    "To search for methods involving 'word'";
    private static Pattern p = Pattern.compile("\\w+\\.");

    public static void main(String[] args) {
        if (args.length < 1) {
            System.out.println(usage);
            System.exit(0);
        }
        int lines = 0;
        try {
            Class<?> c = Class.forName(args[0]);
            Method[] methods = c.getMethods();
            Constructor[] ctors = c.getConstructors();
            if (args.length == 1) {
                for (Method method : methods) {
                    System.out.println(
                            p.matcher(method.toString()).replaceAll(""));
                }
                for (Constructor ctor : ctors) {
                    System.out.println(p.matcher(ctor.toString()).replaceAll(""));
                }
                lines = methods.length + ctors.length;
            } else {
                for (Method method : methods) {
                    if (method.toString().contains(args[1])) {
                        System.out.println(p.matcher(method.toString()).replaceAll(""));
                        lines++;
                    }
                }
                for (Constructor ctor : ctors) {
                    if (ctor.toString().contains(args[1])) {
                        System.out.println(p.matcher(ctor.toString()).replaceAll(""));
                        lines++;
                    }
                }
            }
        } catch (ClassNotFoundException e) {
            System.out.println("No such class: " + e);
        }
    }
}

在这里插入图片描述

输出结果:

在这里插入图片描述

Class 方法 getmethods()getconstructors() 分别返回 Method 数组和 Constructor 数组。这些类中的每一个都有进一步的方法来解析它们所表示的方法的名称、参数和返回值。但你也可以像这里所做的那样,使用 toString(),生成带有整个方法签名的 String。代码的其余部分提取命令行信息,确定特定签名是否与目标 String(使用 indexOf())匹配,并使用正则表达式删除名称限定符。

编译时无法知道 Class.forName() 生成的结果,因此所有方法签名信息都是在运行时提取的。如果你研究 JDK 反射文档,你将看到有足够的支持来实际设置和对编译时完全未知的对象进行方法调用。虽然最初你可能认为你永远都不需要这样做,但是反射的全部价值可能会令人惊讶。

上面的输出来自命令行:

com.example.test.ShowMethods

输出包含一个 public 无参数构造函数,即使未定义构造函数。你看到的构造函数是由编译器自动合成的。如果将 ShowMethods 设置为非 public 类(即只有包级访问权),则合成的无参数构造函数将不再显示在输出中。自动为合成的无参数构造函数授予与类相同的访问权。

尝试运行 java ShowMethods java.lang.String,并附加一个 charintString 等参数。

编程时,当你不记得某个类是否有特定的方法,并且不想在 JDK 文档中搜索索引或类层次结构时,或者如果你不知道该类是否可以对 Color 对象执行任何操作时,该工具能节省不少时间。

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

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

相关文章

深入内核buddy分配器(芯驰X9/杰发8015 buddy系统明明还有几十M到100多M内存,却分配4k内存失败)

如上图内核打印分配4K内存失败&#xff0c;但是normal 类型的buddy系统还有大量内存。居然分配失败。源码分析&#xff1a; 根据logfaddr2line定位到&#xff0c;调用栈为__alloc_pages_slowpath——》get_page_from_freelist——》zone_watermark_fast 可以看到buddy内存低于…

【ChatGPT瀑布到水母】AI 在驱动软件研发的革新与实践

这里写目录标题 前言内容简介作者简介专家推荐读者对象目录直播预告 前言 计算机技术的发展和互联网的普及&#xff0c;使信息处理和传输变得更加高效&#xff0c;极大地改变了金融、商业、教育、娱乐等领域的运作方式。数据分析、人工智能和云计算等新兴技术&#xff0c;也在不…

什么是互动广告

随着数字技术的迅速发展和消费者行为的转变&#xff0c;互动广告已成为现代广告行业的重要组成部分。互动广告以其独特的优势和形式&#xff0c;不断刷新人们对广告的认知&#xff0c;为广告行业带来新的机遇和挑战&#xff0c;那么就来一起了解互动广告吧。 一、互动广告的定义…

机器学习---使用 TensorFlow 构建神经网络模型预测波士顿房价和鸢尾花数据集分类

1. 预测波士顿房价 1.1 导包 from __future__ import absolute_import from __future__ import division from __future__ import print_functionimport itertoolsimport pandas as pd import tensorflow as tftf.logging.set_verbosity(tf.logging.INFO) 最后一行设置了Ten…

单目深度估计之图像重构原理解析

一、参考资料 浅析自监督深度估计中的光度损失(Photometric Loss) 二、图像重构原理 设输入位姿估计网络的3帧连续单目序列为 < I t − 1 , I t , I t 1 > <I_{t-1},I_{t},I_{t1}> <It−1​,It​,It1​>&#xff0c;其中 t t t 为时间索引&#xff0c;…

问题 C: 搬寝室(DP)

算法分析&#xff1a; 题目意思为求n个物品&#xff0c;拿k对使得消耗的体力最少&#xff0c; 或者说是这k对物品&#xff0c;每一对中两件物品的质量差平方最小&#xff0c; 所以要使得质量差的平方小&#xff0c;只能排序后取质量相邻两个物品作为一对&#xff1b; 现在设f…

学习Python,为什么可以轻松应对工作大小事?

Python&#xff0c;大名鼎鼎&#xff0c;它在工作中到底能发挥什么样的作用&#xff1f;在现代职场&#xff0c;Python如同一把瑰丽的多功能钥匙&#xff0c;能打开各行各业的大门。无论你是行政助手、财务分析师、电商经营者&#xff0c;还是数据研究员&#xff0c;Python都能…

四、[mysql]索引优化-1

目录 前言一、场景举例1.联合索引第一个字段用范围查询不走索引(分情况&#xff09;2.强制走指定索引3.覆盖索引优化4.in和or在表数据量比较大的情况会走索引&#xff0c;在表记录不多的情况下会选择全表扫描5.like 后% 一般情况都会走索引(索引下推) 二、Mysql如何选择合适的索…

2021年06月 Python(二级)真题解析#中国电子学会#全国青少年软件编程等级考试

Python等级考试(1~6级)全部真题・点这里 一、单选题(共25题,每题2分,共50分) 第1题 执行下列代码后,运行结果是? seq=[hello,good,morning] s=*.join(seq

处理固定资产折旧报错 AFAB “根据记帐循环, 您必须接下来对期间 001记帐”

会计在运用进行固定资产折旧时&#xff0c;发现有个报错“根据记帐循环, 您必须接下来对期间 001记帐”&#xff0c; 根据记帐循环, 您必须接下来对期间 001记帐 消息编号 AA683 诊断 不可以在指定的期间过帐折旧&#xff0c;因为此操作会遗漏过帐期间。 系统响应 该期间不能进…

软件测试面试,一定要准备的7个高频面试题(附答案,建议收藏)

问题1&#xff1a;请自我介绍下&#xff1f; 核⼼要素&#xff1a;个⼈技能优势⼯作背景经验亮点参考回答&#xff1a; 第一种&#xff1a;基本信息离职理由 ⾯试官您好&#xff0c;我叫张三&#xff0c;来⾃番茄市&#xff0c;在软件测试⾏业有 3 年的⼯作经验。做过 Web/APP…

剑指offer(C++)-JZ5:替换空格(算法-其他)

作者&#xff1a;翟天保Steven 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 题目描述&#xff1a; 请实现一个函数&#xff0c;将一个字符串s中的每个空格替换成“%20”。 例如&#xff0c;当字符串为We A…

网页2D/3D的开发框架

开发2D和3D网页的框架有很多&#xff0c;具体选择取决于您的项目需求和个人偏好。以下是一些常用的2D和3D网页开发框架&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 2D 网页开发框架&#xff1a; …

uniapp开发app,在ios真机上出现的css样式问题

比如下面的问题&#xff0c;在iphone 13上出现&#xff0c;在iphone xR上正常。 问题一&#xff1a;border:1rpx造成边框显示不全 在iphone13上border边框有一部分不显示&#xff1a; 在iphone xR上显示正常&#xff1a; 解决办法是&#xff1a; 将border边框设置中的1rpx改…

Spring Security 6.1.x 系列(3)—— 基于过滤器的基础原理(二)

四、SecurityFilterChain 在Serlvet中&#xff0c;一组Security Filter组成SecurityFilterChain&#xff0c;SecurityFilterChain的概念就比较好理解&#xff0c;是Spring Security 提供的过滤器链&#xff0c;用于管理本身所有的过滤器&#xff0c;在上面的流程图中已有说明。…

2023年软件测试工具总结 —— 单元测试工具

在应用程序中&#xff0c;单元是具有一个或多个输入和单个输出的软件中最小可测试部分。单元测试是一种测试软件代码单元的方法&#xff0c;通常包括一个或两个输入&#xff0c;产生一个输出。单元测试主要关注独立模块的功能正确性&#xff0c;目的是确保每个单元都按照预期的…

360加固APP后启动崩溃—注意加固前后签名是否一致

如下截图所示&#xff0c;我今天就是遇到了这个问题&#xff0c;这个问题是比较好解决&#xff0c;但如果官网有显眼指引说明会不会对开发者更友好些呢&#xff1f; 首先我们给360的加固包是带有自己的签名的&#xff0c;然后经360加固过后&#xff08;免费的加固服务&#xf…

软件测试简历没有邀约,为什么?8类细节通通告诉你(附赠高薪简历)

求职不顺&#xff0c;没有邀约&#xff0c;大概率是你的简历出现了问题。 本篇文章列出高薪简历应该注意的细节&#xff0c;合计36处&#xff0c;涉及简历的八大组成部分。 现在就讲&#xff1a; 一、简历样式要求&#xff08;3点要求&#xff09; 1、简历格式&#xff0c;…

Ansible的安装及部署

目录 一、ansible的简介 二、ansible的安装 1、下载epel仓库 2、安装ansible 3、全局测试 4、构建Anisble清单 三、Ansible配置文件参数详解 1. 配置文件的分类与优先级 2. 常用配置参数 四、构建用户级Ansible操作环境 一、ansible的简介 1、ansible是新出现的自…

arcpy.describe

描述 根据输入的数据&#xff0c;返回输入数据的属性 arcpy.da.Describe与arcpy.Describe返回的数据是一样的但是返回的的类型不一样&#xff0c;arcpy.da.Describe返回的是字典&#xff0c;arcpy.Describe返回的是string 如果要访问数据对象不存在的属性&#xff0c;将返回…