Пакет-частный объем в Scala, видимый с Java

Я только узнал о довольно странном поведении Scala, когда байт-код, сгенерированный из кода Scala, используется из кода Java. Рассмотрим следующий фрагмент, используя 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

Если мы реконструируем эту проблему с помощью более простого примера:

package yuvie

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

И декомпилируйте это в Java:

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

Мы видим, что private[package] в Scala становится public в Java. Зачем? Это связано с тем, что частный пакет Java не эквивалентен частному пакету Scala. В этом сообщении есть приятное объяснение :

Важным отличием является то, что 'private [mypackage]' в Scala является а не Java-пакет-частный, как бы он ни выглядел. Scalaпакеты действительно иерархичны, а "частные [mypackage]" гранты доступ к классам и объектам до "mypackage" (включая все иерархические пакеты, которые могут находиться между ними). (У меня нет Scalaспецификация для этого, и мое занижение здесь может быть туманным, я используя [4] в качестве ссылки.) Пакеты Java не являются иерархическими, и Пакет-частные гранты доступа только к классам в этом пакете, а также как подклассы исходного класса, что-то, что Scala 'private [mypackage] 'не разрешает.

Итак, 'package [mypackage]' является более и менее ограничивающим, чем Java Пакет-частное. По обоим причинам JVM package-private не может использоваться для его реализации и единственный вариант, который позволяет использовать Scalaв компиляторе открыто "public".