Scala 中的包私有范围从 Java 可见

Package-private scope in Scala visible from Java

当从 Java 代码中使用从 Scala 代码生成的字节码时,我刚刚发现了 Scala 作用域的一个非常奇怪的行为。考虑以下使用 Spark(Spark 1.4、Hadoop 2.6)的片段:

import java.util.Arrays;
import java.util.List;

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaSparkContext;
import org.apache.spark.broadcast.Broadcast;

public class Test {
    public static void main(String[] args) {
        JavaSparkContext sc = 
            new JavaSparkContext(new SparkConf()
                                .setMaster("local[*]")
                                .setAppName("test"));

        Broadcast<List<Integer>> broadcast = sc.broadcast(Arrays.asList(1, 2, 3));

        broadcast.destroy(true);

        // fails with java.io.IOException: org.apache.spark.SparkException: 
        // Attempted to use Broadcast(0) after it was destroyed
        sc.parallelize(Arrays.asList("task1", "task2"), 2)
          .foreach(x -> System.out.println(broadcast.getValue()));
    }
}

这段代码失败了,这是预料之中的,因为我在使用它之前自愿销毁了一个 Broadcast,但问题是在我的心智模型中它甚至不应该编译,更不用说 运行 了。

的确,Broadcast.destroy(Boolean) 被声明为 private[spark],所以它不应该从我的代码中可见。我会尝试查看 Broadcast 的字节码,但这不是我的专长,这就是为什么我更喜欢发布这个问题。另外,抱歉,我懒得创建一个不依赖于 Spark 的示例,但至少你明白了。请注意,我可以使用 Spark 的各种包私有方法,而不仅仅是 Broadcast.

知道发生了什么吗?

如果我们用一个更简单的例子来重构这个问题:

package yuvie

class X {
  private[yuvie] def destory(d: Boolean) = true
}

并在 Java 中反编译:

[yuvali@localhost yuvie]$ javap -p X.class 
Compiled from "X.scala"
public class yuvie.X {
  public boolean destory(boolean);
  public yuvie.X();
}

我们看到 Scala 中的 private[package] 变成了 Java 中的 public。为什么?这是因为 Java 私有包不等同于 Scala 私有包。有一个很好的解释 in this post:

The important distinction is that 'private [mypackage]' in Scala is not Java package-private, however much it looks like it. Scala packages are truly hierarchical, and 'private [mypackage]' grants access to classes and objects up to "mypackage" (including all the hierarchical packages that may be between). (I don't have the Scala spec reference for this and my understating here may be hazy, I'm using [4] as a reference.) Java's packages are not hierarchical, and package-private grants access only to classes in that package, as well as subclasses of the original class, something that Scala's 'private [mypackage]' does not allow.

So, 'package [mypackage]' is both more and less restrictive that Java package-private. For both reasons, JVM package-private can't be used to implement it, and the only option that allows the uses that Scala exposes in the compiler is 'public.'