Scala 中的包私有范围可以从 Java 中访问

14

我刚刚发现一个有趣的现象,当从Scala代码生成的字节码被Java代码使用时,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 的。

你有什么想法?

1个回答

23

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

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的私有包。这篇文章中有一个很好的解释

重要的区别在于,在Scala中,“private [mypackage]”并不是Java的包级私有属性,无论它看起来多像。Scala包是真正的分层结构,“private [mypackage]”允许访问类和对象直到“mypackage”(包括可能在其中的所有分层包)。 (我没有Scala规范参考这一点,我的理解可能有些模糊,我正在使用[4]作为参考。)Java的包不是分层结构,包级私有只授予对该包中的类以及原始类的子类的访问权限,这是Scala的“private[mypackage]”不允许的。

因此,“package [mypackage]”比Java包级私有更加严格和宽松。由于这两个原因,JVM包级私有无法用于实现它,而唯一的选项是Scala在编译器中公开使用的“public”。


1
谢谢你的回答。你不觉得这对API编写者来说有点危险吗?他们从未想要公开的功能最终变得明显可见,从Java中可以轻易访问到。我想知道他们是否可以使用一些注释技巧,在用户尝试使用原本应该是私有的成员时生成警告。 - Dici
1
如果你计划与Java进行交互,那么我认为这是你必须考虑的事情,特别是如果这会暴露你不希望客户端调用的内部内容。尽管在这种特殊情况下,你也可以调用公共的Broadcast.destory方法,相当于自己给自己惹麻烦。 - Yuval Itzchakov
1
是的,我的意思是现在我知道所有Spark内部声明为包私有的内容都通过Java API公开了,我认为可能应该有更多的Java包装器来隐藏那些不打算公开的功能。我的例子只是为了展示这个方法实际上被调用了。 - Dici
@Dici 这或许是一个适合在Spark邮件列表上提问的问题。 - Yuval Itzchakov
4
任何想要访问他们无权访问的内容的人都可以使用反射。 - Antimony
6
@Antimony,你说得对,但使用反射需要付出的努力通常会让人觉得不值得,不像直接调用一个可用的方法。我同意这可能是有问题的。 - Yuval Itzchakov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接