二十、泛型(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!
"""

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!
*/

在 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));
    }
}

二十、泛型(8)_第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());
    }
}

二十、泛型(8)_第2张图片

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

你可能感兴趣的:(#,On,Java,基础卷,潜在的类型机制,pyhton,中的潜在类型,C++,中的潜在类型,Go,中的潜在类型,java,中的直接潜在类型)