为什么泛型无法编译?

28

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

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

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

Translator使用T作为字典中键的类型,可以是String或枚举等。子类提供具体的字典。但是由于“类型'String'不符合协议'Hashable'”而失败了。但是String确实符合Hashable。它也无法使用Int,尽管Int也符合Hashable。如果我删除类型约束进行测试(同时还必须禁用字典,因为不能将任何不可哈希的内容用作键),则它可以编译。
class Translator<T> {...}

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

我做错了什么?

2个回答

104

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

假设:

class Foo<T:Hashable> { }

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

这里令人困惑的是,我们期望“String”代表Swift定义的包含字符集合的结构体,但实际上并不是。

在此,“String”是我们给新子类SubFoo指定的泛型类型名称。如果进行一些更改,这一点就会非常明显:

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

这行代码对于T来说会产生一个错误,因为它使用了一个未声明的类型。

然后如果我们将这一行改为:

class SubFoo<T> : Foo<T> { }
我们回到你最初遇到的相同错误,即'T'不符合'Hashable'。这在这里很明显,因为'T'并不会让人迷惑地认为它是一个现有的Swift类型名称,恰好符合'Hashable'。很明显,'T'是一个泛型。

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


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

class Foo<T:Hashable> {

}

typealias StringFoo = Foo<String>

这是完全有效的 Swift 代码,可以正确编译。


如果我们真正想要的是对一个泛型类进行子类化并添加方法或属性,那么我们需要一个类或者协议来将我们的泛型更加具体化以满足我们的需求。

回到最初的问题,让我们首先摆脱这个错误:

class Foo<T: Hashable>

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

这是完全有效的Swift代码。但对于我们正在做的事情可能没有特别大的用处。

唯一的原因是我们不能执行以下操作:

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

这是因为String不是Swift类,而是一个结构体。对于任何结构体都不允许这样做。


如果我们编写一个继承自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 MyClass : Hashable { }

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

即使我们添加了必要的代码,Swift 仍然神秘地抱怨'T'不符合'Hashable'(甚至当我们添加必要的代码时)。


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

我们的子类接受一个 String 并不是那么重要。重要的是,我们的子类无论接受什么类型,都具有我们所需的必要方法和属性。


16
我不是Swift开发者,但在Java中看到类似的问题后,我怀疑问题出在你目前声明了一个名为String的类型参数,因为你声明了class FooTranslator<String> - 因此,在Translator<String>中的类型参数只是该类型参数,它没有约束条件。我怀疑你完全不需要类型参数(也就是说,你不想让FooTranslator成为一个泛型类)。
正如评论中指出的那样,在Swift中,泛型类的子类也必须是泛型的。你可以可能声明一个可抛弃的类型参数,如下所示:
class FooTranslator<T>:Translator<String>

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

这一切都建立在您确实需要子类的假设之上,例如添加或覆盖成员。另一方面,如果您只想要一个与Translator<String>完全相同的类型,则应该使用类型别名:

typealias FooTranslator = Translator<String>

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

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

(注意这里的 Int 故意不是 String,以显示 Translator 中的 TFooTranslator 中的 T 不同。)

不是这样的。这个问题涉及到Swift的一个问题/限制,请参见https://dev59.com/_2Af5IYBdhLWcg3w7mQ_。 - User
@lxx:我猜那仍然是原因,但你需要一个不同的解决方案。我已经编辑了我的答案,并提供了一个可能尝试的解决方案。 - Jon Skeet
@Ixx:没错 - 我不确定你是否需要子类来满足其他需求,而类型别名不适用的情况。我会进一步编辑答案。 - Jon Skeet

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接