记录前些天遇到的一个问题,scala里protected
或private
修饰的方法可能在编译为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
}
}
当我们编译上面类之后,里面的foo
和bar
方法的修饰符最终在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)
}
}
上面bar
的private[this]
在这个阶段被删除,而scala不同于java,缺省就是public
,最终在class里变成了public
。