为什么泛型不能编译?

Why generics don't compile?

我正在尝试使用泛型实现以下结构。遇到编译器错误,无法弄清楚原因。

class Translator<T:Hashable> {...}

class FooTranslator<String>:Translator<String> {...}

想法是翻译器使用 T 作为字典中键的类型。这可以是例如一个字符串或一个枚举。子类提供了具体的字典。

但它失败了,因为:"Type 'String' does not conform to protocol 'Hashable'"

但是String符合Hashable。它也不适用于同样符合 Hashable 的 Int。

如果我删除类型约束,只是为了测试(我还必须禁用字典,因为我不能在那里使用任何不可哈希的东西作为键)- 它编译

class Translator<T> {...}

class FooTranslator<String>:Translator<String> {...}

我做错了什么?

我不是 Swift 开发人员,但在 Java 中看到了类似的问题,我怀疑问题是目前你正在声明一个名为 [=13= 的类型参数] 因为你要声明 class FooTranslator<String> - 所以 Translator<String> 中的类型 argument 只是那个类型参数,没有约束。我怀疑你 根本不需要 类型参数(即你不希望你的 FooTranslator 本身是一个通用的 class。)

如评论中所述,在 Swift subclasses of a generic class also have to be generic 中。您可以声明一个一次性类型参数,如下所示:

class FooTranslator<T>:Translator<String>

这仍然避免声明一个名为 String 的新类型参数,这就是导致问题的原因。这意味着当您不 想要 任何类型参数时,您正在引入一个新的类型参数,但这可能总比没有好...

这一切都基于您确实需要一个潜艇的假设class,例如添加或覆盖成员。另一方面,如果您只想要 完全 Translator<String> 相同的类型,则应改用类型别名:

typealias FooTranslator = Translator<String>

如果你真的想要一个子class但又不想以通用的方式引用它,甚至可以以一种可怕的方式混合两者:

class GenericFooTranslator<T>:Translator<String>
typealias FooTranslator = GenericFooTranslator<Int>

(注意这里的Int故意不是String,表明Translator中的T和[=21=不一样] 在 FooTranslator.)

首先,让我们解释一下错误:

给定:

class Foo<T:Hashable> { }

class SubFoo<String> : Foo<String> { }

这里令人困惑的部分是我们期望 "String" 表示包含字符集合的 Swift 定义的结构。但事实并非如此。

这里,"String" 是我们为新子class、SubFoo 赋予的泛型类型的名称。如果我们进行一些更改,这将变得非常明显:

class SubFoo<String> : Foo<T> { }

此行生成 T 错误,因为使用了未声明的类型。

然后如果我们将此行更改为:

class SubFoo<T> : Foo<T> { }

我们又回到了您最初遇到的相同错误,'T' 不符合 'Hashable'。在这里很明显,因为 T 不是恰好符合 'Hashable' 的现有 Swift 类型的名称。很明显 'T' 是泛型。

当我们写'String'时,它也只是泛型类型的占位符名称,而不是Swift中实际存在的String类型。


如果我们想为泛型的特定类型取一个不同的名称 class,合适的方法几乎肯定是 typealias:

class Foo<T:Hashable> {

}

typealias StringFoo = Foo<String>

这是完全有效的 Swift,并且编译得很好。


如果我们想要的实际上是子 class 并向泛型 class 添加方法或属性,那么我们需要的是一个 class 或协议,使我们的更具体地满足我们的需求。

回到最初的问题,我们先排除错误:

class Foo<T: Hashable>

class SubFoo<T: Hashable> : Foo<T> { }

这是完全正确的 Swift。但是对于我们正在做的事情可能不是特别有用。

我们无法执行以下操作的唯一原因:

class SubFoo<T: String> : Foo<T> { }

只是因为 String 不是 Swift class——它是一个结构。这对于任何结构都是不允许的。


如果我们编写一个继承自 Hashable 的新协议,我们可以使用它:

protocol MyProtocol : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyProtocol> : Foo<T> { }

这是完全正确的。

此外,请注意我们实际上不必继承自 Hashable:

protocol MyProtocol { }
class Foo<T: Hashable> { }
class SubFoo<T: Hashable, MyProtocol> { }

这也是完全正确的。

但是请注意,无论出于何种原因,Swift 都不允许您在此处使用 class。例如:

class MyClass : Hashable { }

class Foo<T: Hashable> { }
class SubFoo<T: MyClass> : Foo<T> { }

Swift 神秘地抱怨 'T' 不符合 'Hashable'(即使我们添加了必要的代码来实现它。


最后,正确的方法,也是最 Swift 合适的方法是编写一个继承自 'Hashable' 的新协议,并向其添加您需要的任何功能。

我们的 subclass 接受 String 并不是严格意义上的重要。重要的是,无论我们的 subclass 采用什么,它都具有我们正在做的任何事情所需的必要方法和属性。