泛型不编译

时间:2015-01-13 10:56:55

标签: generics swift

我尝试使用泛型实现以下结构。得到编译器错误,无法找出原因。

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

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

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

但它失败的原因是:&#34;键入&#39; String&#39;不符合协议&#39; Hashable&#39;&#34;

但String符合Hashable。它也不适用于Int,它也符合Hashable。

如果我删除类型约束,只是为了测试(我也必须禁用字典,因为我不能使用任何不可用的东西作为密钥) - 它编译

class Translator<T> {...}

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

我做错了什么?

2 个答案:

答案 0 :(得分:101)

对于初学者,让我们解释错误:

假设:

class Foo<T:Hashable> { }

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

这里令人困惑的部分是我们期望&#34; String&#34;表示Swift定义的结构,它包含一组字符。但事实并非如此。

在这里,&#34; String&#34;是我们新的子类SubFoo给出的泛型类型的名称。如果我们做出一些改变,这就变得非常明显了:

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

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

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

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

我们回到了原来的错误,&#39; T&#39;不符合'Hashable&#39;。这里显而易见,因为T并不容易混淆现有Swift类型的名称恰好符合Hashable&#39;。很明显&#39; T&#39;是通用的。

当我们编写&#39; String&#39;时,它也只是泛型类型的占位符名称,而不是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> { }
斯威夫特神秘地抱怨说&#39; T&#39;不符合'Hashable&#39; (即使我们添加必要的代码来实现它。


最后,正确的方法和最适合Swift的方法是编写一个继承自Hashable&#39;的新协议。并添加你需要的任何功能。

我们的子类接受String并不是非常重要。重要的是,无论我们的子类采用什么,它都具有我们所做的任何必要的方法和属性。

答案 1 :(得分:17)

我不是Swift开发人员,但是在Java中遇到过类似的问题,我怀疑问题在于,您现在正在声明一个名为String的类型参数,因为您需要这样做。重新声明class FooTranslator<String> - 所以Translator<String>中的类型参数就是那个没有约束的类型参数。你根本不想一个类型参数,我怀疑(即你不希望你的FooTranslator本身就是一个通用类。)

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

class FooTranslator<T>:Translator<String>

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

这一切都假设您确实需要一个子类,例如:添加或覆盖成员。另一方面,如果您只想要与<{1}}完全相同的类型,则应使用类型别名:

Translator<String>

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

typealias FooTranslator = Translator<String>

(请注意,此处的class GenericFooTranslator<T>:Translator<String> typealias FooTranslator = GenericFooTranslator<Int> 故意不是Int,以表明String中的TTranslator相同} T。)