scala雾中风景(6): 内部类与模式匹配

这个问题是在旺旺群里讨论的,讨论的时候思路比较发散,过程中有些说法也是错误的,重新总结一下。

问题的由来是聚石在使用actor接受消息时发现模式匹配的奇怪问题,他把问题简化后,大致是这样的(忽略代码风格问题,仅分析为何是这样的匹配结果):

trait A {
    case class B(i: Int)
}

class C extends A {
    def !(a:Any) = a match {
        case B(0) => println("never be here")   // 注1
        case b: B => println("never be here2")  // 注2
        case B => println("never be here3")     // 注3
        case x => println(s"Oops, received $x")
    }
}

class D extends A {
    new C ! B(0)
}

new D

执行过程,不会匹配到case B(0)case b:B 以及 case B 这3行,只能到 case x 这一句。

如果不是因为把case类定义成了内部类的话,就没有这些诡异问题,这里的问题正是内部类、内部半生对象的限定问题。

第1个问题,在不同的类中用相同的参数创建的内部case类实例,是否equals为true?

编译器帮我们对case类实现了equals方法,关于case类参考这篇:话说模式匹配(6) case类的细节。编译器默认实现的equals在比较时,主要对比其构造参数是否相等:

scala> case class X(i:Int)

scala> new X(0) == new X(0) // 参数相同就相等
res0: Boolean = true

但对于内部case类,就不仅仅是参数了:

scala> trait A { case class X(i:Int) }

scala> class C extends A; class D extends A ;

scala> val x1 = new c.X(0)
x1: c.X = X(0)

scala> val x2 = new d.X(0)
x2: d.X = X(0)

scala> x1 == x2
res1: Boolean = false

scala> val x3 = new d.X(0)
x3: d.X = X(0)

scala> x2 == x3
res2: Boolean = true

之前我们已经提到过c.Xd.X是不同的类型(参考这里),两个内部case类实例要equals为true的话,必须类型相同,即得是同一个外部实例才行。

第2个问题,因为case类同时产生一个伴生对象,那么c实例中拥有的单例B与d实例中拥有的单例B是不是同一个单例对象?
scala> trait A { case class B(i:Int) }

scala>  class C extends A; class D extends A;

scala>  val b1 = (new C).B
b1: C#B.type = B

scala>  val b2 = (new D).B
b2: D#B.type = B

scala> b1 == b2
res0: Boolean = false

可看到不同的外部实例,其内部的case类伴生对象也是不同的。

回到最初的问题,正因为 B(0) 这个构造方法在不同子类里,含义是有所不同的,假如在同一个上下文里:

trait A { case class B(i: Int) } 

class C extends A {

    def foo(a:Any) = a match {
        case B(0) => println("never be here")
        case b: B => println("never be here2")
        case B => println("never be here3")
        case x => println("Oops, received ")
    }

    foo( B(0) ) // foo执行与foo定义在同一个上下文中,B实例类型是一致的
} 

new C

上面是能够匹配到 case B(0) 的。 如果把 case B(0)这行删掉,也是能匹配到case b:B的。

但当B是在另一个子类D中构造时,case B(0)case b:B 都匹配不到了,因为B的类型不同!回顾这篇文章,内部类B,是一个路径依赖类型,所以在D中创建的B实例,它的类型是 D.this.B 这与在C中的C.this.B 不是同一个类型,所以造成了匹配失败。 要类型匹配成功的话,需要改用类型投影:

case b : A#B => …  // ok 可以匹配

那第一句 case B(0) 呢?它一样也是类型不符合,让这一句匹配上需要修改在D里构造B的方式:

class D extends A {
    val c = new C
    val b = new c.B(0)   
    c  !  b
}

这样构造出来的 B实例b 与 C里面的 B 类型就一致了。

scala雾中风景(6): 内部类与模式匹配》上有2个想法

发表评论

电子邮件地址不会被公开。 必填项已用*标注