首先,让我们解释一下错误:
假设:
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
并不是那么重要。重要的是,我们的子类无论接受什么类型,都具有我们所需的必要方法和属性。