【问题标题】:Generics in a bidirectional association双向关联中的泛型
【发布时间】:2011-06-05 15:57:49
【问题描述】:

假设我有两个类 A 和 B,B 是 A 的子类型。显然,这只是更丰富的类型层次结构的一部分,但我认为这无关紧要。假设 A 是层次结构的根。有一个集合类 C 跟踪 A 的列表。但是,我想让 C 成为通用的,这样就可以创建一个只保留 B 而不会接受 A 的实例。

class A(val c: C[A]) {
    c.addEntry(this)
}
class B(c: C[A]) extends A(c)
class C[T <: A]{
    val entries = new ArrayBuffer[T]()
    def addEntry(e: T) { entries += e }
}
object Generic {
    def main(args : Array[String]) {
        val c = new C[B]()
        new B(c)
    }
}

上面的代码显然在new B(c) 行上给出了错误“类型不匹配:找到 C[B],需要 C[A]”。

我不确定如何解决这个问题。不可能使 C 在 T 中协变(如 C[+T &lt;: A]),因为 ArrayBuffer 在 T 中是非可变类型的。不可能使 B 的构造函数需要 C[B],因为 C 不能是协变的。

我在这里叫错树了吗?我是一个完整的 Scala 新手,所以任何想法和提示都可能会有所帮助。谢谢!

编辑: 基本上,我想要的是编译器同时接受这两个

val c = new C[B]()
new B(c)

val c = new C[A]()
new B(c)

但会拒绝

val c = new C[B]()
new A(c)

如果有帮助的话,可能可以将 C 中 ArrayBuffer 的类型放宽为 A 而不是 T,因此在 addEntry 方法中也是如此。

【问题讨论】:

    标签: generics inheritance scala bidirectional


    【解决方案1】:

    不可能使 C 在 T 中协变(如 C[+T &lt;: A]),因为 ArrayBuffer 在 T 中是非变类型的

    不仅仅是因为这个。 addEntry 的类型足以禁止它:

    val a: A = ...
    val b: B = ...
    
    val cb: C[B] = ...
    
    cb.addEntry(b) // works
    cb.addEntry(a) // doesn't and shouldn't
    

    【讨论】:

      【解决方案2】:

      Hacky,但似乎有效:

      class A(val c: C[A]) {
        c.addEntry(this.asInstanceOf[c.X])
      }
      
      class B(c: C[B]) extends A(c)
      
      class C[+T <: A] {
          type X <: T
          val entries = new ArrayBuffer[X]()
          def addEntry(e: X) { entries += e }
      }
      
      object Generic {
          def main(args : Array[String]) {
              val c = new C(){ type T = B }
              new B(c)
          }
      }
      

      当然,我也会对合适的解决方案感兴趣...

      【讨论】:

        【解决方案3】:

        如果要跟踪 A 的实例,则必须将 C[A] 的实例传递给 B 的构造函数,因为每个 B 也是 A:

        def main(args : Array[String]) {
            val c = new C[A]()
            new B(c)
        }
        

        但是,如果您想跟踪 B,那么您不能将其委托给 A,因为 A 对 B 一无所知。

        总的来说,我觉得你的问题有点不妥。

        【讨论】:

          【解决方案4】:

          假设这是可能的。然后你就可以这样做了:

          class A(val c: C[A]) {
              c.addEntry(this)
          }
          class B(c: C[A]) extends A(c)
          class C[+T <: A]{
              val entries: ArrayBuffer[T] @uncheckedVariance = new ArrayBuffer[T]()
              def addEntry(e: T @uncheckedVariance) { entries += e }
          }
          object Generic {
              def main(args : Array[String]) {
                  // Everything's fine so far...
                  val c = new C[B]()
                  c.addEntry(new B(c))
                  // but, suddenly...
                  val ca: C[A] = c
                  ca.addEntry(new A(ca))
                  // a problem appears!
                  c.entries forall {
                      case thing: B => true // ok
                      case otherThing => false // not ok -- c now contains an A!
                  }
              }
          }
          

          尝试运行此代码将导致类转换异常。

          编辑

          您添加了此要求:

          val c = new C[B]()
          new B(c)
          

          val c = new C[A]()
          new B(c)
          

          但会拒绝

          val c = new C[B]()
          new A(c)
          

          但是,如果BC[B] 初始化,并且假设B 扩展A,那么B 将用C[B] 初始化A,从而违反最后一个要求。

          【讨论】:

          • 如果 C 是协变的,由于您在此处描述的原因,它确实会出错,但这不是严格的要求。我认为没有必要让 C[B] 成为 C[A] 的子类型。
          • @Verhoevenv 如果C[B] 不是C[A] 的子类型,那么如果c 的类型为C[B],您希望new B(c) 如何工作?我现在正在查看您的修订,而您却在要求一些不可能的事情。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-07-26
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-03-07
          相关资源
          最近更新 更多