【问题标题】:Swift: Use wildcard as generic type parameterSwift:使用通配符作为泛型类型参数
【发布时间】:2014-08-12 20:10:58
【问题描述】:

我想在字典中存储从泛型类型派生的类的实例;也就是说,字典应该存储从这个泛型派生的 any 类的一个实例。

类似这样的:

class ParentClass {}
class ChildClass: ParentClass {}

class GenericClass<T: ParentClass> {
    var foo:T?
}

typealias DerivedClass = GenericClass<ChildClass>

class TestGenerics {

    var dict: Dictionary<String, GenericClass<**what goes here??**>> = [:]

    func test() {
        var derivedClassInstance = DerivedClass()
        dict.updateValue(derivedClassInstance, forKey: "name")
    }
}

这在 Java 中相当简单:

public class TestGenericsOuter {

    class ParentClass {}
    class ChildClass extends ParentClass {}

    class GenericClass<T extends ParentClass> {
        T foo;
    }

    class DerivedClass extends GenericClass<ChildClass> {}

    class TestGenerics {

        Dictionary<String, GenericClass<? extends ParentClass>> dict;

        void test() {
            DerivedClass derivedClassInstance = new DerivedClass();
            dict.put("name", derivedClassInstance);

        }
    }

}

这样的事情在 Swift 中是否可行?我让它工作的唯一方法是创建一个以“Any”作为值类型的字典。但是,我失去了一些类型安全性,所以我想尽可能避免这种解决方案。

【问题讨论】:

  • 对——这就是我的意思:我可以使用带有 Any 作为值类型的字典。 (我猜是我把尖括号和解析器吃掉了)。我想使用更具体的类型限制,这样我就不需要从字典中转换出所有内容。

标签: generics swift wildcard


【解决方案1】:

我认为您不能模仿 Java 的通配符,但您可能也不需要这样做。您可以在代码需要ParentClass 的任何地方使用ChildClass。所以用你的例子:

class TestGenerics {
    var dict: Dictionary<String, GenericClass<ParentClass>> = [:]

    func test() {
        var derivedClassInstance = GenericClass<ParentClass>()
        dict.updateValue(derivedClassInstance, forKey: "name")
    }
}

现在只需使用ChildClass 填写foo

let test = TestGenerics()
test.test()
test.dict["name"]?.foo = ChildClass()

该代码编译时没有错误。但是,Swift 不支持自定义泛型类的协变,因此您无法使用协变类型更改字典,因此以下内容无法编译:

test.dict = Dictionary<String, GenericClass<ChildClass>>()
//Error: 'ChildClass' is not identical to 'ParentClass'

这不是很直观,因为原生数组和字典确实支持协方差,所以这是允许的:

let array: Array<ParentClass> = Array<ChildClass>()
let dic: Dictionary<String, ParentClass> = Dictionary<String, ChildClass>()

但不是这个:

let generic: GenericClass<ParentClass> = GenericClass<ChildClass>()
//Error: 'ChildClass' is not identical to 'ParentClass'

基本上,Swift 将Array&lt;ChildClass&gt; 视为Array&lt;ParentClass&gt; 的子类型,但目前无法告诉编译器GenericClass&lt;ChildClass&gt; 是(或应该是)GenericClass&lt;ParentClass&gt; 的子类型。希望随着语言的发展,将添加一种将自定义类声明为协变的方法。

【讨论】:

  • 如果 Swift 将Array&lt;ChildClass&gt; 视为Array&lt;ParentClass&gt; 的子类型,那么当var vehicles: Array&lt;Vehicle&gt; = Array&lt;Bike&gt;() 然后vehicles.append(myCar) 时会发生什么?
  • 这是一个好问题@Andreas。刚刚在操场上尝试过,显然这是有效的代码。如果您执行vehices.dynamicType,它返回车辆是Array&lt;Vehicle&gt;,即使它是使用Array&lt;Bike&gt;() 构造的。这是出乎意料的。
  • @asayagogalvan Swift 中的数组类型是值类型。因此,类型是变量的一部分,而不是值(与 Java 的 List&lt;T&gt; 相比,它是一个引用类型)。 Swift 的Array.append() 创建一个新值并将其存储在原始变量中。如果变量具有足够许可的类型,则可以将新值存储在那里。这不取决于旧值的来源以及原始变量的类型,因为旧值和新值彼此不相关。
【解决方案2】:

您需要参数化包含要被设为通用的任何引用的类型,并在那里使用类型约束。此外,typealias 与参数化类型的约束有关,因此它位于该类型内部。

class ParentClass {}
class ChildClass: ParentClass {}

class GenericClass<T: ParentClass> {
    var foo:T?
}


class TestGenerics<T: ParentClass> {
    typealias DerivedClass = GenericClass<T>

    var dict: Dictionary<String, GenericClass<T>> = [:]

    func test() {
        var derivedClassInstance = DerivedClass()
        dict.updateValue(derivedClassInstance, forKey: "name")
    }
}

let blah = TestGenerics<ChildClass>()
let baz = GenericClass<ChildClass>()
baz.foo = ChildClass()
blah.dict = ["a":baz]

【讨论】:

  • 这似乎只是“推卸责任”给 TestGenerics。如果 TestGenerics 不应该被参数化怎么办?
  • 但是按照您的方式,特定字典只能保存一种类型的GenericClass,即仅适用于特定的T。在 Java 中,Dictionary&lt;String, GenericClass&lt;? extends ParentClass&gt;&gt; 之类的东西是一个字典,可以同时保存所有不同的 GenericClasses。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-05-16
  • 1970-01-01
  • 2016-09-28
  • 2013-07-03
  • 1970-01-01
  • 2019-11-01
  • 1970-01-01
相关资源
最近更新 更多