我正在尝试使用泛型实现以下结构.得到一个奇怪的编译器错误,无法弄清楚原因.
class Translator<T:Hashable> {...}
class FooTranslator<String>:Translator<String> {...}
Run Code Online (Sandbox Code Playgroud)
这个想法是Translator使用T作为字典中键的类型.这可以是例如String或枚举.子类提供具体的字典.
但它失败的原因是:"类型'字符串'不符合协议'Hashable'"
但String符合Hashable!我疯了吗?它也不适用于Int,它也符合Hashable.如果我将Hashable替换为Equatable也不起作用,这也应该由两者实现.
If I remove the type constraint, just for testing (where I also have to disable the dictionary, as I can't use anything not hashable as key there) - it compiles
class Translator<T> {...}
class FooTranslator<String>:Translator<String> {...}
Run Code Online (Sandbox Code Playgroud)
What am I doing wrong?
nhg*_*rif 102
首先,让我们解释一下错误:
鉴于:
class Foo<T:Hashable> { }
class SubFoo<String> : Foo<String> { }
Run Code Online (Sandbox Code Playgroud)
这里令人困惑的部分是我们希望"String"表示Swift定义的结构,它包含一组字符.但事实并非如此.
这里,"String"是我们给出新子类的泛型类型的名称SubFoo.如果我们做出一些改变,这将变得非常明显:
class SubFoo<String> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)
此行生成错误,T因为使用了未声明的类型.
然后,如果我们将行更改为:
class SubFoo<T> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)
我们回到了你原来的错误,'T'不符合'Hashable'.这里显而易见,因为T并不容易混淆现有Swift类型的名称恰好符合'Hashable'.很明显'T'是通用的.
当我们编写'String'时,它也只是泛型类型的占位符名称,而实际上并不是StringSwift中存在的类型.
如果我们想要一个特定类型的泛型类的不同名称,适当的方法几乎肯定是typealias:
class Foo<T:Hashable> {
}
typealias StringFoo = Foo<String>
Run Code Online (Sandbox Code Playgroud)
这是完全有效的Swift,它编译得很好.
如果我们想要的是实际上是子类并将方法或属性添加到泛型类,那么我们需要的是一个类或协议,它将使我们的泛型更具体到我们需要的东西.
回到原来的问题,让我们首先摆脱错误:
class Foo<T: Hashable>
class SubFoo<T: Hashable> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)
这是完全有效的Swift.但它对我们正在做的事情可能并不是特别有用.
我们不能做以下事情的唯一原因:
class SubFoo<T: String> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)
只是因为String不是Swift类 - 它是一个结构.任何结构都不允许这样做.
如果我们编写一个继承自的新协议Hashable,我们可以使用:
protocol MyProtocol : Hashable { }
class Foo<T: Hashable> { }
class SubFoo<T: MyProtocol> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)
这完全有效.
另请注意,我们实际上不必继承自Hashable:
protocol MyProtocol { }
class Foo<T: Hashable> { }
class SubFoo<T: Hashable, MyProtocol> { }
Run Code Online (Sandbox Code Playgroud)
这也完全有效.
但请注意,无论出于何种原因,Swift都不允许您在这里使用课程.例如:
class MyClass : Hashable { }
class Foo<T: Hashable> { }
class SubFoo<T: MyClass> : Foo<T> { }
Run Code Online (Sandbox Code Playgroud)
斯威夫特神秘地抱怨'T'不符合'Hashable'(即使我们添加了必要的代码来实现它.
最后,正确的方法和最适合Swift的方法将是编写一个继承自'Hashable'的新协议,并为您添加所需的任何功能.
我们的子类接受a不应该是非常重要的String.重要的是,无论我们的子类采用什么,它都具有我们所做的任何必要的方法和属性.
Jon*_*eet 16
I'm not a Swift developer, but having seen similar problems in Java, I suspect the problem is that at the moment you're declaring a type parameter called String because you're declaring class FooTranslator<String> - so the type argument in Translator<String> is just that type parameter, which has no constraints. You don't want a type parameter at all, I suspect (i.e. you don't want your FooTranslator to be a generic class itself.)
As noted in comments, in Swift subclasses of a generic class also have to be generic. You could possibly declare a throw-away type parameter, like this:
class FooTranslator<T>:Translator<String>
Run Code Online (Sandbox Code Playgroud)
which still avoids declaring a new type parameter called String, which was what was causing the problem. It means you're introducing a new type parameter when you don't want any type parameters, but it's possibly better than nothing...
This is all on the assumption that you really need a subclass, e.g. to add or override members. On the other hand, if you just want a type which is exactly the same as Translator<String>, you should use a type alias instead:
typealias FooTranslator = Translator<String>
Run Code Online (Sandbox Code Playgroud)
Or even mix the two in a horrible way, if you genuinely want a subclass but don't want to have to refer to it in a generic way:
class GenericFooTranslator<T>:Translator<String>
typealias FooTranslator = GenericFooTranslator<Int>
Run Code Online (Sandbox Code Playgroud)
(Note that the Int here is deliberately not String, to show that the T in Translator isn't the same as the T in FooTranslator.)