2016-08-04 4 views
4

Я пытаюсь передать ограниченное количество значений в набор, но мне нужно проверить, что они являются новыми элементами, прежде чем применять ограничение. Например:Могут ли последовательные потоковые операции иметь побочные эффекты?

Set<Integer> destination = ... 
Set<Integer> source = ... 
source.stream() 
     .filter(i -> !destination.contains(i)) 
     .limit(10) 
     .forEach(destination::add); 

Но избыточная проверка меня беспокоит, так как add() может как добавить элемент и отчет, является ли это новой для коллекции. Так что я думал сделать это:

source.stream() 
     .filter(destination::add) 
     .limit(10) 
     .forEach(i -> {}); // no-op terminal operation to force evaluation 

Игнорирование Hacky работу терминала, есть проблема с помощью операции фильтра с побочным эффектом, который, как правило, не рекомендуется. Я понимаю, почему было бы небезопасно использовать map() и filter() с побочными эффектами на параллельные потоки. Мой вопрос: приемлемо ли это для последовательного потока, как в этом случае? Если нет, почему бы и нет?

ответ

7

Не существует фундаментальной проблемы с побочными эффектами и последовательными потоками, но вторая реализация выше недействительна, поскольку API потока не гарантирует, что каждый этап будет выполняться на каждом элементе по очереди.

Во второй реализации до destination может быть добавлено более 10 элементов до достижения предела. Ваш no-op forEach будет видеть только 10, но вы можете получить больше в наборе.

В дополнение к потокам, java имеет петлевые конструкции, такие как for и while, которые могут упростить выражение таких вещей.

Если вам необходимо потоки использования, вы можете сделать это следующим образом:

int maxSize = destination.size()+10; 
source.stream().allMatch(x -> destination.size()<maxsize && (destination.add(x)||true)); 

allMatch остановит итерацию, как только предикат возвращает ложь.

+2

Я думаю, вы немного неправильно поняли мой сценарий. 'source' - это набор, поэтому он не содержит повторяющихся элементов. Я пытаюсь убедиться, что мы добавляем элементы 'source', которые не * уже * существуют в' destination'. Поэтому я думаю, что первое решение все равно будет работать. Но ваш общий момент интересен, а именно, что элемент потока может не перемещаться по всему трубопроводу сразу. У вас есть источник для этого, или вы полагаетесь на отсутствие документации в обратном? – shmosel

+0

Отсутствие документации, напротив, * и * тот факт, что множество оптимизаций в потоках включают элементы обработки в кусках. Код, который вы написали, сегодня работает, но он полагается на вещи, которые могут очень хорошо измениться в новых версиях Java или других реализациях. –

+0

Я действительно неправильно понял цель проверки. Я отредактировал ответ соответственно –

-1

ли эта работа для вас:

package be.objectsmith; 

import java.util.Arrays; 
import java.util.HashSet; 
import java.util.Set; 
import java.util.stream.Collectors; 
import java.util.stream.IntStream; 

public class Playground { 
    public static void main(String[] args) { 
     copy(
      IntStream.range(1, 20).boxed().collect(Collectors.toSet()), 
      new HashSet<>(Arrays.asList(2, 5))); 
     copy(
      IntStream.range(1, 5).boxed().collect(Collectors.toSet()), 
      new HashSet<>(Arrays.asList(2, 5))); 
    } 

    private static void copy(Set<Integer> source, Set<Integer> destination) { 
     source 
      .stream() 
      .map(destination::add) 
      .filter(resultOfAdding -> resultOfAdding) 
      .limit(10) 
      .collect(Collectors.toList()); // Need a terminal operation 
     System.out.println("source = " + source); 
     System.out.println("destination = " + destination); 
    } 

} 

Запуск этого Виль печать класса:

source = [1, 2, 3, 4] 
destination = [1, 2, 3, 4, 5] 
source = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19] 
destination = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] 

Как вы можете видеть, он только добавил 10 элементов. Второй вызов показывает, что он также работает, если вы добавили менее 10 элементов.

+1

Это всего лишь более сложная версия 2-й реализации в вопросе, разбивая фильтр (destination - add) на две операции. –

+0

@MattTimmermans Это исправление 2-й реализации, и она делает именно то, что OP просит: используйте тот факт, что 'Set.add()' сам сообщает, был ли это новый элемент. Тогда ему просто нужно подсчитать и ограничить успешные дополнения. Я бы даже заменил операцию терминала на '.count()'. В целом я бы предпочел первую реализацию; что успех 'add' reports - это удобство, которое позволяет некоторым случаям быть более элегантным, но, тем не менее, я вижу только дополнительные неудобства – bowmore

+0

@bowmore, что он исправить? Оригинал также работает с данными тестовыми примерами, использует возврат из Set :: add и не имеет проблем, которые также не находятся в этом ответе. –

Смежные вопросы