Имеет ли Scala промежуточные/терминальные операции с Java8?

В Java8

Когда я пишу такой код:

Stream<Integer> xs = Arrays.asList(1, 3, 5, 6, 7, 10).stream();
xs.map(x -> x * x).filter (x -> x > 15).forEach(System.out::println);

потоки Java8 разделены на два раздела; промежуточные или терминальные операции, где фактическое действие -AFAIK (итерации под капотом) выполняется в операциях терминала, в то время как каждый промежуточный ops добавляет свое собственное имя - я назову его - Применить внутренние классы.

Таким образом, в списке будет только одна итерация.

Пример кода из JDK8:

@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
    Objects.requireNonNull(mapper);
    return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
                                 StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
        @Override
        Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
            return new Sink.ChainedReference<P_OUT, R>(sink) {
                @Override
                public void accept(P_OUT u) {
                    downstream.accept(mapper.apply(u));
                }
            };
        }
    };
}

В Scala

Когда я пишу такой код:

val xs = List(1, 3, 5, 6, 7, 10) 
xs map (x => x * x) filter (x => x > 15) foreach (println)

Я читал об этом некоторое время, но я никогда не слышал о таких терминах явно, кроме того, циклы реализации SDK (с использованием рекурсии или регулярных циклов) для каждой операции:

final override def map[B, That](f: A => B)(implicit bf: CanBuildFrom[List[A], B, That]): That = {
if (bf eq List.ReusableCBF) {
  if (this eq Nil) Nil.asInstanceOf[That] else {
    val h = new ::[B](f(head), Nil)
    var t: ::[B] = h
    var rest = tail
    while (rest ne Nil) {
      val nx = new ::(f(rest.head), Nil)
      t.tl = nx
      t = nx
      rest = rest.tail
    }
    h.asInstanceOf[That]
  }
}
else super.map(f)
}

Мои вопросы:

Можно ли считать, что реализация Java на одной и той же вещи будет намного быстрее. ( O (n) в Java vs O (кратные n) в Scala)

Ответ 1

Потоки Java 8 являются менее полнофункциональными кузенами итераторов Scala, сохраняя их способность вычислять параллельно.

Если вам не нужны параллельные вычисления (и в большинстве случаев накладные расходы не стоят - только для больших дорогостоящих заданий, вы этого хотите), то вы можете получить такую ​​же обработку с помощью .iterator в Scala (а затем to[Vector] или все, что вы хотите в конце).

Java 8 потоков специализированы вручную (Scala Iterator is not), поэтому существуют случаи, когда они быстрее, но это не из-за постоянного фактора из-за воссоздания коллекций по пути - по крайней мере, а не если вы выбрали .iterator. (Без .iterator, коллекции Scala оцениваются по умолчанию, коллекции Java не имеют этой опции.)

эквивалент Scala для кода Java 8, который вы написали, следующий:

val xsi = Array(1, 3, 5, 6, 7, 10).iterator
xsi.map(x => x*x).filter(_ > 15).foreach(println)

Нет никакой разницы в количестве созданных здесь коллекций с Scala vs. Java.

Вероятно, было бы неплохо принять очень понятный язык терминальной операции для документации Scala Iterator. Документы с потоком Java 8 отлично смотрятся в том, что они делают это изящно понятным, когда вы создаете описание работы и когда вы в конце концов это делаете.

Scala также предоставляет класс Stream, который запоминает старую работу (поэтому вам не нужно ее вычислять во второй раз, если вы повторно ее используете) и различные views, поэтому вам не нужно воссоздайте цепочку обработки каждый раз, когда вы хотите ее использовать. Например, с помощью квадрата можно

val xsv = Array(1, 3, 5, 6, 7, 10).view
val xsq = xsv.map(x => x*x)
xsq.filter(_ > 15).foreach(println)
xsq.filter(_ < 5).foreach(println)

тогда как с потоками Java 8 xsq будет исчерпан после первой операции терминала.

Итак, Scala действительно делает все (за исключением parallelism), что делает потоки Java 8, и совсем немного больше, и надолго.

Scala также имеет параллельные коллекции, но реализация Java 8 достаточно высока по производительности, и в этот момент я бы рекомендовал использовать их в первую очередь. И снова, если ручная специализация - это ваша вещь, потоки Java 8 имеют ее для Int, Double и Long, и это большой выигрыш в производительности. (Примечание: ваш пример, используя asList, не специализируется вручную.)

Но если вы просто хотите поставить в очередь операции и не иметь накладных расходов на создание промежуточных коллекций, это делает Scala. Вы просто должны спросить.

Ответ 2

Список в Scala нетерпелив, что означает, что (как вы говорите) есть несколько итераций по списку. Есть (по крайней мере) два способа обойти это.

Использование представления:

xs.view.map(x => x * x).filter(x => x > 15).force

Или путем преобразования списка в поток (который ленив):

xs.toStream.map(x => x * x).filter(x => x > 15)