我尝试使用泛型实现以下结构。得到编译器错误,无法找出原因。
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> {...}
我做错了什么?
答案 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
中的T
与Translator
相同} T
。)