scala类型系统:4) 内部类,路径依赖类型&类型投影

先回顾一下java的内部类

class Outter{

    public class Inner {}

    public void foo(Inner c){
        System.out.println(c);
    }
}

public class Main {
    public static void main(String[] args)throws Exception{
        Outter o1 = new Outter();
        Outter o2 = new Outter();
        Outter.Inner i1 = o1.new Inner();
        Outter.Inner i2 = o2.new Inner();
        o1.foo(i2);
    }
}

上面在Outter类内部定义了Inner类,在后边main里创建了两个Inner实例,注意创建内部类的时候

Outter.Inner i1 = o1.new Inner();

在用new创建内部类时,前边必须限定外部对象(内部类实例必须要访问到外部对象引用):o1;如果是在 Outter类内部这个外部引用可以省略,它默认会用传递外部this引用。

class Outter {

    public class Inner{}

    public void test() {
        new Inner(); // 相当于this.new Inner(); 也可以写为Outter.this.new Inner();
    }
} 

同样的事情翻译为scala代码:

scala> class A { 
            class B; 
            def foo(b:B) = println(b) 
        }

scala> val a1 = new A
scala> val a2 = new A

scala> val b1 = new a1.B
scala> val b2 = new a2.B

在创建内部类的时候,语法上与java有所不同,不是 outter.new Inner() 而是 new outter.Inner(),看上去只是表象不同么?实际上,scala有很大差异,不同于java里 i1 和 i2 类型是相同的,否则 o1.foo(i2) 就不能执行了,scala里的 b1 和 b2 是不同的类型:

scala> a1.foo(b2)
<console>:12: error: type mismatch;
 found   : a2.B
 required: a1.B 

按照方法的提示: a1.foo方法接受的参数类型为:a1.B,而传入的b2 类型是 a2.B,两者不匹配。
验证一下:

scala> typeOf[a1.B] == typeOf[a2.B]
res2: Boolean = false

确实是不一样的类型,它跟外部的实例相关,那个foo方法参数类型B的写法是缩写,省略了路径:

def foo(b: B) // 相当于 this.B 或 A.this.B

这里要引入一个概念:路径依赖类型;比如上面的 A.this.B 就是一个路径依赖类型,B 前面的路径 A.this 随着不同的实例而不同,比如 a1 和 a2 就是两个不同的路径,所以a1.Ba2.B也是不同的类型。路径依赖类型的路径完整写法:

1) 内部类定义在object里面,路径:package.object.Inner

object Singleton {
    class Inner
}

val x = new p1.p2.p3.Singleton.Inner

2) 内部类定义在class/trait 里

//2.1) 直接在外部类中使用内部类型,路径:this 或 Outter.this 

class A {
    class B
    val b = new B // 相当于 A.this.B
}   

//2.2) 在子类中使用父类的内部类型,路径:super 或 Child.super

class A  { class B }
class C extends A { val x = new super.B } // 相当于 C.super.B

//2.3) 在其他类中使用,路径:outter(外部类实例)

class A  { class B }
class C { 
    val a = new A
    val x = new a.B  
}

那现在的问题来了,怎么让 a1.foo 方法可以接收 b2 参数 ?

class A { 
    class B; 
    def foo(b:B)  // 接收所有的B类型实例,而不只是foo的调用者实例(a1)路径下B类型的对象
         println(b) 
}

这又引出一个概念:类型投影(type projection)

在scala里,内部类型(排除定义在object内部的),想要表达所有的外部类A实例路径下的B类型,即对 a1.Ba2.B及所有的 an.B类型找一个共同的父类型,这就是类型投影,用 A#B的形式表示。

        A#B
        / \
       /   \
     a1.B  a2.B

这样,我们只要修改一下 foo 方法里的参数类型

def foo(b: A#B)

就可以调用 a1.foo(b2) 了。

我们回头来对比一下scala里的类型投影与java里的内部类型的概念,java里的内部类型在写法上是 Outter.Inner 它其实等同于scala里的投影类型 Outter#Inner,java里没有路径依赖类型的概念,比较简化。

scala类型系统:4) 内部类,路径依赖类型&类型投影》上有6条评论

  1. Wenchen Fan

    如果在类A的某个方法里面定义了类B, 那这个类B的路径依赖是怎么样的?
    是不是和 在类A里面定义一个内部类B一样,只不过可见范围变成了那个方法里?

    回复
    1. lcn

      感觉scope限制会使得无法访问在方法内部定义的新类。新类在方法内部仅提供辅助功能,没有办法将其任何实例传递出去——在外部是看不到其类型的。

      回复
  2. wujiahua

    你好,我想请问一下一个Scala问题,下面这段代码是我在阅读akka in action中看到一段,
    trait CreateTicketSellers { self:Actor =>
    def createTicketSeller(name:String) = context.actorOf(Props[TicketSeller], name)
    },
    我想问问self:Actor => 这几个字符在这里是什么意思?

    回复
  3. lcn

    def foo(b: A#B)和def foo(b:B)还是不能互相替代的,用途完全不同。

    回复

发表评论

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