scala雾中风景(28): private?public?

记录前些天遇到的一个问题,scala里protectedprivate修饰的方法可能在编译为class时变成了public,这已经不是第一次遇到,最早遇到是在写一个java子类时要覆盖一些父类方法,父类是scala写的一个trait,里面的方法修饰为protected,当时IDE提示我override的方法必须声明为public感到奇怪反编译了一下父trait果然被声明为了public

而这次遇到的稍有不同,跟继承没有关系,用下面的demo举例:

 ➜  cat A.scala
class A {
  private[this] def foo() = {
    List(1,2,3).map(i => bar(i))
  }

  private[this] def bar(i:Int):String = {
    "str:" + i
  }
}

当我们编译上面类之后,里面的foobar方法的修饰符最终在class里会有所不同,反编译后可看到bar修饰符变成了public:

 ➜  cfr-decompiler A
 ...
 public class A {
    private List<String> foo() {
        return (List)List..MODULE$.apply((Seq)Predef..MODULE$.wrapIntArray(new int[]{1, 2, 3})).map((Function1)new scala.Serializable(this){
            public static final long serialVersionUID = 0;
            private final /* synthetic */ A $outer;

            public final String apply(int i) {
                return this.$outer.A$$bar(i);
            }
        }, List..MODULE$.canBuildFrom());
    }

    public String A$$bar(int i) {
        return new StringBuilder().append((Object)"str:").append((Object)BoxesRunTime.boxToInteger((int)i)).toString();
    }
}

终归scala在jvm上要做一些妥协,按上面的实现,foo里面以闭包的方式使用bar的时候,如果保持scala private[this]的控制粒度,底层的匿名类其实已经无法访问bar了。所以scala在编译器的explicitouter环节做了一些向现实妥协的事情

 ➜  scalac -Xshow-phases
    phase name  id  description
    ----------  --  -----------
        parser   1  parse source into ASTs, perform simple desugaring
         namer   2  resolve names, attach symbols to named trees
packageobjects   3  load package objects
         typer   4  the meat and potatoes: type the trees
        patmat   5  translate match expressions
superaccessors   6  add super accessors in traits and nested classes
    extmethods   7  add extension methods for inline classes
       pickler   8  serialize symbol tables
     refchecks   9  reference/override checking, translate nested objects
       uncurry  10  uncurry, translate function values to anonymous classes
     tailcalls  11  replace tail calls by jumps
    specialize  12  @specialized-driven class and method specialization
 explicitouter  13  this refs to outer pointers
       erasure  14  erase types, add interfaces for traits
   posterasure  15  clean up erased inline classes
      lazyvals  16  allocate bitmaps, translate lazy vals into lazified defs
    lambdalift  17  move nested functions to top level
  constructors  18  move field definitions into constructors
       flatten  19  eliminate inner classes
         mixin  20  mixin composition
       cleanup  21  platform-specific cleanups, generate reflective calls
    delambdafy  22  remove lambdas
         icode  23  generate portable intermediate code
           jvm  24  generate JVM bytecode
      terminal  25  the last phase during a compilation run

在这个阶段,当编译器发现一些private的方法会被内部类访问的话,就删除这些private修饰符:

 ➜  scalac -Xprint:explicitouter A.scala
[[syntax trees at end of             explicitouter]] // A.scala
package <empty> {
  class A extends Object {
    def <init>(): A = {
      A.super.<init>();
      ()
    };
    private[this] def foo(): List[String] = immutable.this.List.apply[Int](scala.this.Predef.wrapIntArray(Array[Int]{1, 2, 3})).map[String, List[String]]({
      @SerialVersionUID(value = 0) final <synthetic> class $anonfun extends scala.runtime.AbstractFunction1[Int,String] with Serializable {
        def <init>($outer: A.this.type): <$anon: Int => String> = {
          $anonfun.super.<init>();
          ()
        };
        final def apply(i: Int): String = $anonfun.this.$outer.bar(i);
        <synthetic> <paramaccessor> <artifact> private[this] val $outer: A.this.type = _;
        <synthetic> <stable> <artifact> def $outer(): A.this.type = $anonfun.this.$outer
      };
      (new <$anon: Int => String>(A.this): Int => String)
    }, immutable.this.List.canBuildFrom[String]());
    
    final def bar(i: Int): String = "str:".+(i)
  }
}

上面barprivate[this]在这个阶段被删除,而scala不同于java,缺省就是public,最终在class里变成了public

scala的诊断方法(5) 用scalac-aspects诊断scalac各阶段耗时

在scala-user邮件列表里看到的,有人说他有两个类用scalac编译非常慢,别人给出了一个工具可以诊断scalac的编译过程在各环节耗时情况,这个工具是基于aspectj的,尝试了一把。

先下载aspectj,然后用java -jar aspectj-1.8.2.jar安装,它是个图形界面的安装程序,过程就是文件解压到指定路径。

然后设置一下环节变量:

$ export ASPECTJ_HOME=/data/tools/aspectj/1.8
$ export PATH=${ASPECTJ_HOME}/bin:${PATH}

从github上clone一份scalac-aspects,然后运行一下样例:

$ ./scalac-aspects PerUnitTiming.aj Foo.scala
...

Per-file timings (all times are in micro seconds)
Foo.scala 1260255
    parser                    170939
    namer                     144580
    packageobjects            60
    typer                     223685
    patmat                    10843
    superaccessors            7899
    extmethods                4354
    pickler                   35800
    refchecks                 78355
    selectiveanf              2013
    selectivecps              1724
    uncurry                   26039
    tailcalls                 16333
    specialize                59948
    explicitouter             86753
    erasure                   236839
    posterasure               855
    lazyvals                  11423
    lambdalift                38314
    constructors              43564
    flatten                   466
    mixin                     20772
    cleanup                   10301
    icode                     27698
    inliner                   626
    inlineExceptionHandlers   28
    closelim                  26
    dce                       18

对这个简单的只定义了一个方法的Foo.scala,编译过程最耗时也是在typererasure 阶段,估计大部分代码的编译过程类型相关的处理都会占大头。