二十、泛型(8)

本章概要

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

潜在类型机制

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

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

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

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

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

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

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

pyhton 中的潜在类型

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

# generics/DogsAndRobots.pyclass Dog:def speak(self):print("Arf!")def sit(self):print("Sitting")def reproduce(self):passclass Robot:def speak(self):print("Click!")def sit(self):print("Clank!")def oilChange(self):passdef 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.gopackage 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 {@Overridepublic void speak() {System.out.println("Woof!");}@Overridepublic void sit() {System.out.println("Sitting");}public void reproduce() {}
}class Robot implements Performs {@Overridepublic void speak() {System.out.println("Click!");}@Overridepublic 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() {}@Overridepublic String toString() {return getClass().getSimpleName() +(name == null ? "" : " " + name);}public long id() {return id;}@Overridepublic boolean equals(Object o) {return o instanceof Individual &&Objects.equals(id, ((Individual) o).id);}@Overridepublic int hashCode() {return Objects.hash(name, id);}@Overridepublic 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.hqwc.cn/news/178885.html

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

相关文章

面试题-2

1.重绘和重排有什么区别 重排(回流):布局引擎会根据所有的样式计算出盒模型在页面上的位置和大小 重绘:计算好盒模型的位置,大小和其他一些属性之后,浏览器就会根据每个盒模型的特性进行绘制 浏览器的渲染机制: 对DOM的大小,位置进行修改后,浏览器需要重新计算元素的这些几何…

MySQL(17):触发器

概述 MySQL从 5.0.2 版本开始支持触发器。MySQL的触发器和存储过程一样&#xff0c;都是嵌入到MySQL服务器的一段程序。 触发器是由 事件来触发 某个操作&#xff0c;这些事件包括 INSERT 、 UPDATE 、 DELETE 事件。 所谓事件就是指用户的动作或者触发某项行为。 如果定义了触…

VUE基础的一些总结

首先推荐观看VUE官方文档 目录 创建一个 Vue 应用 要创建一个 Vue 应用&#xff0c;你需要按照以下步骤操作&#xff1a; 步骤 1&#xff1a;安装 Node.js 和 npm 确保你的计算机上已经安装了 Node.js。你可以在 Node.js 官网 上下载并安装它。安装完成后&#xff0c;npm&…

2023亚太杯数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

一文解释对比学习

对比学习是一种无监督学习技术&#xff0c;其核心思想是通过比较不同样本之间的相似性和差异性来学习数据的表示&#xff08;features&#xff09;。它不依赖于标签数据&#xff0c;而是通过样本之间的相互关系&#xff0c;使得模型能够学习到有意义的特征表示。 在对比学习中…

【ML】欠拟合和过拟合的一些判别和优化方法(吴恩达机器学习笔记)

吴恩达老师的机器学习教程笔记 减少误差的一些方法 获得更多的训练实例——解决高方差尝试减少特征的数量——解决高方差尝试获得更多的特征——解决高偏差尝试增加多项式特征——解决高偏差尝试减少正则化程度 λ——解决高偏差尝试增加正则化程度 λ——解决高方差 什么是…

接口测试 —— Jmeter 之测试片段的应用

一、什么是测试片段&#xff1f; 控制器上一种特殊的线程组&#xff0c;它与线程组处于一个层级。与线程组不同的就是&#xff1a;测试片段不会执行。它是一个模块控制器或者被控制器应用时才会被执行。通常与Include Controller或模块控制器一起使用。 1.1 那它有啥作用&…

前端跨界面之间的通信解决方案

主要是这两个方案&#xff0c;其他的&#xff0c;还有 SharedWorker 、IndexedDB、WebSocket、Service Worker 如果是&#xff0c;父子嵌套 iframe 还可以使用 window.parent.postMessage(“需要传递的参数”, ‘*’) 1、localStorage 核心点 同源&#xff0c;不能跨域(协议、端…

在docker下安装suiteCRM

安装方法&#xff1a; docker-hub来源&#xff1a;https://hub.docker.com/r/bitnami/suitecrm curl -sSL https://raw.githubusercontent.com/bitnami/containers/main/bitnami/suitecrm/docker-compose.yml > docker-compose.yml//然后可以在docker-compose.yml文件里修…

Mysql词法分析实验(二)

表名叫select123能不能创建一个表&#xff1f; 在 MySQL 中&#xff0c;可以创建一个名为 select123 的表&#xff0c;但由于 SELECT 是 MySQL 的一个保留关键字&#xff0c;通常建议避免使用它作为表名的一部分&#xff0c;以防止潜在的解析错误或混淆。如果确实需要使用这样…

缓存穿透、缓存击穿、缓存雪崩

目录 一、缓存的概念 1.为什么需要把用户的权限放入redis缓存 2.为什么减低了数据库的压力呢&#xff1f; 3.那么什么情况下用redis,什么情况下用mysql呢&#xff1f; 4.关于权限存入redis的逻辑&#xff1f; 二、使用缓存出现的三大情况 1.缓存穿透 1.1概念 1.2出现原…

五年制专转本备考中如何进行有效的自我管理

时间管理 0 1 一天中的4个记忆黄金时间 清晨起床后&#xff0c;适合学习难以记忆的内容&#xff1b;8&#xff1a;00—10&#xff1a;00&#xff0c;适宜学习需要周密思考、分析判断的内容&#xff0c;是攻克难题的最佳时间&#xff1b;18&#xff1a;00后的两个小时&#x…