二十、泛型(8)

news2024/11/24 7:54:19

本章概要

  • 潜在的类型机制
    • pyhton 中的潜在类型
    • C++ 中的潜在类型
    • Go 中的潜在类型
    • java 中的直接潜在类型

潜在类型机制

在本章的开头介绍过这样的思想,即要编写能够尽可能广泛地应用的代码。为了实现这一点,我们需要各种途径来放松对我们的代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,我们就可以编写出无需修改就可以应用于更多情况的代码,即更加“泛化”的代码。

Java 泛型看起来是向这一方向迈进了一步。当你在编写或使用只是持有对象的泛型时,这些代码将可以工作于任何类型(除了基本类型,尽管正如你所见到的,自动装箱机制可以克服这一点)。或者,换个角度讲,“持有器”泛型能够声明:“我不关心你是什么类型”。如果代码不关心它将要作用的类型,那么这种代码就可以真正地应用于任何地方,并因此而相当泛化。

还是正如你所见到的,当要在泛型类型上执行操作(即调用 Object 方法之外的方法)时,就会产生问题。擦除强制要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,你最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。

某些编程语言提供的一种解决方案称为_潜在类型机制_或_结构化类型机制_,而更古怪的术语称为_鸭子类型机制_,即“如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当作鸭子对待。”鸭子类型机制变成了一种相当流行的术语,可能是因为它不像其他的术语那样承载着历史的包袱。

泛型代码典型地只能在泛型类型上调用少量方法,而具有潜在类型机制的语言只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以 speak()sit() 即可。”由于不要求具体类型,因此代码就可以更加泛化。

潜在类型机制是一种代码组织和复用机制。有了它,编写出的代码相对于没有它编写出的代码,能够更容易地复用。代码组织和复用是所有计算机编程的基本手段:编写一次,多次使用,并在一个位置保存代码。因为我并未被要求去命名我的代码要操作于其上的确切接口,所以,有了潜在类型机制,我就可以编写更少的代码,并更容易地将其应用于多个地方。

支持潜在类型机制的语言包括 Python(可以从 www.Python.org 免费下载)、C++、Ruby、SmallTalk 和 Go。Python 是动态类型语言(几乎所有的类型检查都发生在运行时),而 C++ 和 Go 是静态类型语言(类型检查发生在编译期),因此潜在类型机制不要求静态或动态类型检查。

pyhton 中的潜在类型

如果我们将上面的描述用 Python 来表示,如下所示:

# generics/DogsAndRobots.py

class Dog:
    def speak(self):
        print("Arf!")
    def sit(self):
        print("Sitting")
    def reproduce(self):
        pass

class Robot:
    def speak(self):
        print("Click!")
    def sit(self):
        print("Clank!")
    def oilChange(self):
        pass

def perform(anything):
    anything.speak()
    anything.sit()

a = Dog()
b = Robot()
perform(a)
perform(b)

output = """
Arf!
Sitting
Click!
Clank!
"""

Python 使用缩进来确定作用域(因此不需要任何花括号),而冒号将表示新的作用域的开始。“#” 表示注释到行尾,就像Java中的 “ // ”。类的方法需要显式地指定 this 引用的等价物作为第一个参数,按惯例成为 self 。构造器调用不要求任何类型的“ new ”关键字,并且 Python 允许普通(非成员)函数,就像 perform() 所表明的那样。

注意,在 perform(anything) 中,没有任何针对 anything 的类型,anything 只是一个标识符,它必须能够执行 perform() 期望它执行的操作,因此这里隐含着一个接口。但是你从来都不必显式地写出这个接口——它是潜在的。perform() 不关心其参数的类型,因此我可以向它传递任何对象,只要该对象支持 speak()sit() 方法。如果传递给 perform() 的对象不支持这些操作,那么将会得到运行时异常。

输出规定使用三重引号创建带有内嵌换行符的字符串。

C++ 中的潜在类型

我们可以用 C++ 产生相同的效果:

// generics/DogsAndRobots.cpp

#include <iostream>
using namespace std;

class Dog {
public:
    void speak() { cout << "Arf!" << endl; }
    void sit() { cout << "Sitting" << endl; }
    void reproduce() {}
};

class Robot {
public:
    void speak() { cout << "Click!" << endl; }
    void sit() { cout << "Clank!" << endl; }
    void oilChange() {}
};

template<class T> void perform(T anything) {
    anything.speak();
    anything.sit();
}

int main() {
    Dog d;
    Robot r;
    perform(d);
    perform(r);
}
/* Output:
Arf!
Sitting
Click!
Clank!
*/

在 Python 和 C++ 中,DogRobot 没有任何共同的东西,只是碰巧有两个方法具有相同的签名。从类型的观点看,它们是完全不同的类型。但是,perform() 不关心其参数的具体类型,并且潜在类型机制允许它接受这两种类型的对象。

C++ 确保了它实际上可以发送的那些消息,如果试图传递错误类型,编译器就会给你一个错误消息(这些错误消息从历史上看是相当可怕和冗长的,是 C++ 的模版名声欠佳的主要原因)。尽管它们是在不同时期实现这一点的,C++ 在编译期,而 Python 在运行时,但是这两种语言都可以确保类型不会被误用,因此被认为是强类型的。潜在类型机制没有损害强类型机制。

Go 中的潜在类型

这里用 Go 语言编写相同的程序:

// generics/dogsandrobots.go

package main
import "fmt"

type Dog struct {}
func (this Dog) speak() { fmt.Printf("Arf!\n")}
func (this Dog) sit() { fmt.Printf("Sitting\n")}
func (this Dog) reproduce() {}

type Robot struct {}
func (this Robot) speak() { fmt.Printf("Click!\n") }
func (this Robot) sit() { fmt.Printf("Clank!\n") }
func (this Robot) oilChange() {}

func perform(speaker interface { speak(); sit() }) {
  speaker.speak();
  speaker.sit();
}

func main() {
  perform(Dog{})
  perform(Robot{})
}
/* Output:
Arf!
Sitting
Click!
Clank!
*/

Go 没有 class 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是定义为 struct ,在其中定义数据字段(此处不存在)。

对于每种方法,都以 func 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 this 来提醒您,就像在 C ++ 或 Java 中的 this 一样。 然后,在Go中像这样定义其余的函数。

Go也没有继承关系,因此这种“面向对象的目标”形式是相对原始的,并且可能是我无法花更多的时间来学习该语言的主要原因。 但是,Go 的组成很简单。

perform() 函数使用潜在类型:参数的确切类型并不重要,只要它包含了 speak()sit() 方法即可。 该接口在此处匿名定义,内联,如 perform() 的参数列表所示。

main() 证明 perform() 确实对其参数的确切类型不在乎,只要可以在该参数上调用 talk()sit() 即可。 但是,就像 C ++ 模板函数一样,在编译时检查类型。

语法 Dog {}Robot {} 创建匿名的 DogRobot 结构。

java中的直接潜在类型

因为泛型是在这场竞赛的后期才添加到 Java 中,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 Java 没有对这种特性的支持。所以,初看起来,Java 的泛型机制比支持潜在类型机制的语言更“缺乏泛化性”。(使用擦除来实现 Java 泛型的实现有时称为第二类泛型类型)例如,在 Java 8 之前如果我们试图用 Java 实现上面 dogs-and-robots 的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它:

Performs.java

public interface Performs {
    void speak();

    void sit();
}

DogsAndRobots.java

class PerformingDog extends Dog implements Performs {
    @Override
    public void speak() {
        System.out.println("Woof!");
    }

    @Override
    public void sit() {
        System.out.println("Sitting");
    }

    public void reproduce() {
    }
}

class Robot implements Performs {
    @Override
    public void speak() {
        System.out.println("Click!");
    }

    @Override
    public void sit() {
        System.out.println("Clank!");
    }

    public void oilChange() {
    }
}

class Communicate {
    public static <T extends Performs>
    void perform(T performer) {
        performer.speak();
        performer.sit();
    }
}

public class DogsAndRobots {
    public static void main(String[] args) {
        Communicate.perform(new PerformingDog());
        Communicate.perform(new Robot());
    }
}

Dog.java

public class Dog extends Pet {
    public Dog(String name) {
        super(name);
    }

    public Dog() {
        super();
    }
}

Individual.java

public class Pet extends Individual {
    public Pet(String name) {
        super(name);
    }

    public Pet() {
        super();
    }
}

Individual.java

import java.util.*;

public class Individual implements Comparable<Individual> {
    private static long counter = 0;
    private final long id = counter++;
    private String name;

    public Individual(String name) {
        this.name = name;
    }

    // 'name' is optional:
    public Individual() {
    }

    @Override
    public String toString() {
        return getClass().getSimpleName() +
                (name == null ? "" : " " + name);
    }

    public long id() {
        return id;
    }

    @Override
    public boolean equals(Object o) {
        return o instanceof Individual &&
                Objects.equals(id, ((Individual) o).id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, id);
    }

    @Override
    public int compareTo(Individual arg) {
        // Compare by class name first:
        String first = getClass().getSimpleName();
        String argFirst = arg.getClass().getSimpleName();
        int firstCompare = first.compareTo(argFirst);
        if (firstCompare != 0) {
            return firstCompare;
        }
        if (name != null && arg.name != null) {
            int secondCompare = name.compareTo(arg.name);
            if (secondCompare != 0) {
                return secondCompare;
            }
        }
        return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
    }
}

在这里插入图片描述

但是要注意,perform() 不需要使用泛型来工作,它可以被简单地指定为接受一个 Performs 对象:

class CommunicateSimply {
    static void perform(Performs performer) {
        performer.speak();
        performer.sit();
    }
}

public class SimpleDogsAndRobots {
    public static void main(String[] args) {
        CommunicateSimply.perform(new PerformingDog());
        CommunicateSimply.perform(new Robot());
    }
}

在这里插入图片描述

在本例中,泛型不是必需的,因为这些类已经被强制要求实现 Performs 接口。

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

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

相关文章

macos死机后IDEA打不开,Cannot connect to already running IDE instance.

Cannot connect to already running IDE instance. Exception: Process 573 is still running 解决办法 进入&#xff1a;/Users/lzq/Library/Application Support/JetBrains 找到IDEA的目录删除隐藏文件夹 .lock rm -rf .lock

SLAM中提到的相机位姿到底指什么?

不小心又绕进去了&#xff0c;所以掰一下。 以我个人最直观的理解&#xff0c;假设无旋转&#xff0c;相机在世界坐标系的(5,0,0)^T的位置上&#xff0c;所谓“位姿”&#xff0c;应该反映相机的位置&#xff0c;所以相机位姿应该如下&#xff1a; Eigen::Matrix4d T Eigen::M…

Django之模版层

文章目录 模版语法传值模版语法传值特性模版语法标签语法格式if模板标签for模板标签with起别名 模版语法过滤器常用过滤器 自定义过滤器、标签、inclusion_tag自定义过滤器自定义标签自定义inclusion_tag 模版导入模版继承 模版语法传值 模板层三种语法{{}}:主要与数据值相关{%…

【LeetCode刷题-滑动窗口】--1456.定长子串中元音的最大数目

1456.定长子串中元音的最大数目 方法&#xff1a;使用滑动窗口 class Solution {public int maxVowels(String s, int k) {int n s.length();int sum 0;for(int i 0;i<k;i){sum isVowel(s.charAt(i));}int ans sum;for(int i k;i<n;i){sum sum isVowel(s.charAt…

【echarts】实现单线与多线滚轮联动、隐藏拖拽、关闭动画

单线滚轮联动 <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>ECharts DataZoom</title><script src"https://cdn.jsdelivr.net/npm/echarts5.2.0/dist/echarts.min.js"></script> </hea…

京东数据挖掘(京东数据采集):2023年Q3电脑行业数据分析报告

近年来&#xff0c;在远程办公、远程教育等需求的刺激下&#xff0c;电脑的销售增长较为显著。不过&#xff0c;随着市场的成熟乃至饱和&#xff0c;电脑销售市场也逐渐出现增长困难、需求疲软等问题。 2023年第三季度&#xff0c;电脑市场的出货量同比下滑。根据鲸参谋电商数据…

【ROS导航Navigation】四 | SLAM与导航 | 自主移动的地图构建 (更新ing)

致谢&#xff1a;ROS赵虚左老师 Introduction Autolabor-ROS机器人入门课程《ROS理论与实践》零基础教程 参考赵虚左老师的实战教程 实现比较简单&#xff0c;步骤如下: 编写launch文件&#xff0c;集成SLAM与move_base相关节点&#xff1b;执行launch文件并测试。 <la…

入门后端开发得学什么?这份超详细的后端开发学习路线图值得推荐!

后端开发, 无疑是一个极为关键的领域&#xff0c;涉及到我们每日互联网生活的每个细节。每当你在网上浏览、搜索或进行购物等活动时&#xff0c;背后都有大量的后端技术作为支撑。而随着技术的日益进步&#xff0c;人们对于高效、稳定和安全的网络服务的需求也越来越高。 另一…

[C/C++] 数据结构 链表OJ题:相交链表(寻找两个链表的相交起始结点)

题目描述: 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函数返…

Unity中Shader矩阵的乘法

文章目录 前言一、矩阵乘以标量二、矩阵和矩阵相乘1、第一个矩阵的列数必须 与 第二个矩阵的行数相等&#xff0c;否则无法相乘&#xff01;2、相乘的结果矩阵&#xff0c;行数由第一个矩阵的行数决定&#xff0c;列数由第二个矩阵的列数决定&#xff01; 三、单位矩阵四、矩阵…

Django模版层

解析: forloop内置对象:运行结果解析 counter0: 从0开始计数 counter : 从1开始计数 first: True,判断循环的开始 last : Tues,判断循环的结束模版变量的书写 我们可以在html中编写python代码。 演示&#xff1a; {{ 填写变量 }}{% 填写类的 %}{{ d.0 }} {{ d.1 }…

找风景视频素材,就上这5个网站。

找风景视频素材那一定要上这6个网站&#xff0c;免费下载&#xff0c;赶紧收藏&#xff01; 1、菜鸟图库 https://www.sucai999.com/video.html?vNTYxMjky 菜鸟图库网素材非常丰富&#xff0c;网站主要还是以设计类素材为主&#xff0c;高清视频素材也很多&#xff0c;像风景…

C++: 模板初阶

文章目录 一. 泛型编程二. 函数模板函数模板的原理函数模板的实例化隐式实例化: 让编译器根据实参推演模板参数的实际类型显示实例化: 在函数名后的<>中制定模板参数的世纪类型 模板参数的匹配原则 三. 类模板类模板的定义格式类模板的实例化 一. 泛型编程 如何实现一个…

汽车FMCW毫米波雷达信号处理流程(推荐---基础详细---清楚的讲解了雷达的过程---强烈推荐)------假设每个Chirp采集M个样本点

毫米波雷达在进行多目标检测时,TX发射一个Chirp,在不同距离下RX会接收到多个反射Chirp信号(仅以单个chirp为例)。 雷达通过接收不同物体的发射信号,并转为IF信号,利用傅里叶变换将产生一个具有不同的分离峰值的频谱,每个峰值表示在特定距离处存在物体。 请问,这种多目标…

《持续交付:发布可靠软件的系统方法》- 读书笔记(十四)

持续交付&#xff1a;发布可靠软件的系统方法&#xff08;十四&#xff09; 第 14 章 版本控制进阶14.1 引言14.2 版本控制的历史14.2.1 CVS14.2.2 SVN14.2.3 商业版本控制系统14.2.4 放弃悲观锁 14.3 分支与合并14.3.1 合并14.3.2 分支、流和持续集成 14.4 DVCS14.4.1 什么是 …

云课五分钟-04一段代码学习-大模型分析C++

前篇&#xff1a; 云课五分钟-03第一个开源游戏复现-贪吃蛇 经过01-03&#xff0c;基本了解云课最大的优势之一就是快速复现&#xff08;部署&#xff09;。 视频&#xff1a; 云课五分钟-04一段代码学习-大模型分析C AIGC大模型时代&#xff0c;学习编程语言的方式&#xf…

腾讯待办停止运营怎么办?导出的ics文件数据怎么打开查看

待办提醒类工具是日常办公及生活中必不可少的工具&#xff0c;使用待办提醒类工具可以记录很多容易忘记的事情&#xff0c;其可以帮助大家轻松管理各项事务和提高办事的效率。而随着工作的不断变动&#xff0c;大家选择待办提醒类工具也会不断的发生改变。 比如就拿我自己的使…

2023测试工程师做哪些准备,才能从众人中脱颖而出,不看后悔10年

最近&#xff0c;裁员的声音此起披伏。貌似我们只有努力奔跑&#xff0c;这一块带有命运诅咒的“石头”才不会轻易的落到我们的头上。 在不是金三银四、金九银十的求职旺季外&#xff0c;还会有机会吗&#xff1f;我想&#xff0c;对于有能力的人来说&#xff0c;任何时候都可…

(三)什么是Vite——Vite 主体流程(运行npm run dev后发生了什么?)

什么是vite系列目录: &#xff08;一&#xff09;什么是Vite——vite介绍与使用-CSDN博客 &#xff08;二&#xff09;什么是Vite——Vite 和 Webpack 区别&#xff08;冷启动&#xff09;-CSDN博客 &#xff08;三&#xff09;什么是Vite——Vite 主体流程(运行npm run dev…

慧眼APP开发项目

目录 第1章 项目概述... 3 第2章 可行性分析... 4 2.1 技术可行性... 4 2.2 数据可行性... 4 2.3 应用可行性... 4 第3章 环境搭建... 5 3.1 模型搭建... 5 3.1.1 安装PaddleDetection. 5 3.1.2 自定义数据集... 5 3.2 APP搭建... 5 3.2.1 安装Android Studio. 5 3.…