• 二十、泛型(8)


    本章概要

    • 潜在的类型机制
      • 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!
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

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

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

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

    C++ 中的潜在类型

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

    // generics/DogsAndRobots.cpp
    
    #include 
    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!
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    在 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!
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    Dog.java

    public class Dog extends Pet {
        public Dog(String name) {
            super(name);
        }
    
        public Dog() {
            super();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Individual.java

    public class Pet extends Individual {
        public Pet(String name) {
            super(name);
        }
    
        public Pet() {
            super();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    在这里插入图片描述

    但是要注意,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());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

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

  • 相关阅读:
    设备树,通过键名获取数据相关API
    调整Windows键盘上只能看到拼音而无法看到实际的文本以及关闭输入法悬浮窗方法
    Linux 之 arm linux 与 windows 使用 tftp 进行文件传输的简单整理
    同源策略——CORS和JSONP劫持漏洞
    我的面试心得
    OpenGL简介
    55 黑客攻击
    LED驱动器 DC恒流电源板模块
    【leetcode】【初级算法】【链表3】反转链表
    C语言进阶第三课-----------指针的进阶----------后续版
  • 原文地址:https://blog.csdn.net/GXL_1012/article/details/134427165