2016-04-15 23 views
11

Running Poniższy przykładowy kod kończy się:
"Wyjątek w wątku "main" java.lang.StackOverflowError"Java 8 strumienie - stackoverflow wyjątek

import java.util.stream.IntStream; 
import java.util.stream.Stream; 

public class TestStream { 

    public static void main(String[] args) { 
     Stream<String> reducedStream = IntStream.range(0, 15000) 
      .mapToObj(Abc::new) 
      .reduce(
       Stream.of("Test") 
       , (str , abc) -> abc.process(str) 
       , (a , b) -> {throw new IllegalStateException();} 
     ); 
     System.out.println(reducedStream.findFirst().get()); 
    } 

    private static class Abc { 
     public Abc(int id) { 
     } 

     public Stream<String> process(Stream<String> batch) { 
      return batch.map(this::doNothing); 
     } 

     private String doNothing(String test) { 
      return test; 
     } 
    } 
} 

Czym dokładnie jest przyczyną tego problemu? Która część tego kodu jest rekurencyjna i dlaczego?

+4

Dlaczego redukujesz strumień do innego strumienia? – Tunaki

+0

nie ma rekurencyjnego wywołania i działa dobrze na moim komputerze. – Andrew

+1

Problem pochodzi z wywołań 15000 do 'map'. Wewnętrznie, musi je jakoś połączyć. Możesz go odtworzyć bez 'Abc':' IntStream.range (0, 15000) .boxed(). Reduce (Stream.of ("Test"), (str, abc) -> str.map (s -> s) , (a, b) -> {throw new IllegalStateException();} ' – Tunaki

Odpowiedz

2

Twój kod nie rekursywnie zapętla się. Możesz testować mniejszymi numerami dla zakresu IntStream (np. 1 lub 100). W twoim przypadku to rzeczywisty limit wielkości stosu powoduje problem. Jak wskazano w niektórych komentarzach, jest to sposób, w jaki strumienie są procesami.

Każde wywołanie w strumieniu tworzy nowy strumień zawijania wokół oryginalnego. Metoda "findFirst()" prosi o poprzedni strumień dla elementów, który z kolei prosi o poprzedni strumień dla elementów. Ponieważ strumienie nie są prawdziwymi kontenerami, a jedynie wskaźnikami na elementach wyniku.

Eksplozja owijki dzieje się w "akumulatorach" metod redukcji (str, abc) -> abc.process (str) ". Wdrożenie metody tworzy nową obwolutę strumienia na wyniku (str) poprzedniej operacji, wprowadzając do następnej iteracji, tworząc nową powłokę na wyniku (result (str))). Tak więc mechanizm akumulacji jest jednym z owijki (rekursji), a nie aplikanta (iteracji). Więc tworzenie nowego strumienia rzeczywistego (spłaszczone), a nie na skutek odniesieniu do potencjalnego wyniku zatrzyma wybuch, czyli

public Stream<String> process(Stream<String> batch) { 
     return Stream.of(batch.map(this::doNothing).collect(Collectors.joining())); 
    } 

Metoda ta jest tylko przykładem, jak oryginalny przykład nie ma sensu ponieważ nic nie robi i nie ma tego przykładu. To tylko ilustracja. Zasadniczo spłaszcza elementy strumienia zwróconego przez metodę mapy w pojedynczy ciąg i tworzy nowy strumień na tym konkretnym łańcuchu, a nie na samym strumieniu, co stanowi różnicę w stosunku do oryginalnego kodu.

Można dostroić stacksize za pomocą parametru "-Xss", który definiuje rozmiar stosu na wątek. Domyślna wartość zależy od platformy, zobacz także to pytanie. 'What is the maximum depth of the java call stack?' Należy jednak zachować ostrożność podczas zwiększania, ustawienie to dotyczy wszystkich wątków.

+0

Dzięki. Muszę poświęcić nieco więcej czasu na debugowanie strumieni Java. To działa: 'return Stream.of (batch.map (this :: doNothing) .collect (Collectors.joining()));' , ale jest to bardzo dziwna linia kodu :) – slowikps

Powiązane problemy