← Назад к вопросам

Как обрабатывать исключения внутри потока Stream?

2.0 Middle🔥 121 комментариев
#Stream API и функциональное программирование#Коллекции#Основы Java

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

Как обрабатывать исключения внутри потока Stream

Java Stream API не поддерживает checked исключения в лямбда-выражениях, что затрудняет их обработку. Рассмотрим различные подходы к решению этой проблемы.

Проблема с исключениями в Stream

По умолчанию Stream API выбрасывает исключение и останавливает обработку:

import java.util.*;
import java.util.stream.*;

public class StreamExceptionProblem {
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        // ❌ ПРОБЛЕМА: не компилируется с checked исключениями
        // urls.stream()
        //     .map(url -> fetchContent(url)) // fetchContent выбрасывает IOException
        //     .forEach(System.out::println);
    }
    
    // Выбрасывает checked исключение
    static String fetchContent(String url) throws IOException {
        // Имитация сетевого запроса
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

1. Обёрнуть в unchecked исключение (Try-Catch обёртка)

Способ 1: Обёрнуть в RuntimeException

import java.util.*;
import java.util.stream.*;

public class UncheckedExceptionWrapper {
    static class UncheckedIOException extends RuntimeException {
        public UncheckedIOException(IOException e) {
            super(e);
        }
    }
    
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        try {
            urls.stream()
                .map(url -> {
                    try {
                        return fetchContent(url);
                    } catch (IOException e) {
                        throw new UncheckedIOException(e);
                    }
                })
                .forEach(System.out::println);
        } catch (UncheckedIOException e) {
            System.err.println("Failed to fetch content: " + e.getCause().getMessage());
        }
    }
    
    static String fetchContent(String url) throws IOException {
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

Способ 2: Создать функциональный интерфейс с исключениями

import java.util.*;
import java.util.stream.*;
import java.util.function.*;

public class FunctionalExceptionHandling {
    // Функциональный интерфейс, который может выбросить исключение
    @FunctionalInterface
    interface FunctionThrowable<T, R> {
        R apply(T t) throws Exception;
    }
    
    // Утилита для обёртки
    static <T, R> Function<T, R> wrapper(FunctionThrowable<T, R> fe) {
        return arg -> {
            try {
                return fe.apply(arg);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }
    
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        try {
            urls.stream()
                .map(wrapper(UncheckedExceptionWrapper::fetchContent))
                .forEach(System.out::println);
        } catch (RuntimeException e) {
            System.err.println("Error: " + e.getCause().getMessage());
        }
    }
}

2. Использование Optional для обработки ошибок

import java.util.*;
import java.util.stream.*;

public class OptionalExceptionHandling {
    static class Result {
        private final String value;
        private final String error;
        
        public Result(String value, String error) {
            this.value = value;
            this.error = error;
        }
        
        public static Result success(String value) {
            return new Result(value, null);
        }
        
        public static Result failure(String error) {
            return new Result(null, error);
        }
        
        public Optional<String> getValue() {
            return Optional.ofNullable(value);
        }
        
        public Optional<String> getError() {
            return Optional.ofNullable(error);
        }
    }
    
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        urls.stream()
            .map(url -> {
                try {
                    return Result.success(fetchContent(url));
                } catch (IOException e) {
                    return Result.failure(e.getMessage());
                }
            })
            .forEach(result -> {
                if (result.getValue().isPresent()) {
                    System.out.println("Success: " + result.getValue().get());
                } else {
                    System.err.println("Error: " + result.getError().get());
                }
            });
    }
    
    static String fetchContent(String url) throws IOException {
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

3. Использование Either паттерна (Vavr/Javaslang)

// Добавить зависимость: io.vavr:vavr:0.10.4
import io.vavr.control.Either;
import java.util.*;
import java.util.stream.*;

public class EitherExceptionHandling {
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        urls.stream()
            .map(url -> {
                try {
                    return Either.right(fetchContent(url));
                } catch (IOException e) {
                    return Either.left(e);
                }
            })
            .forEach(result -> {
                if (result.isRight()) {
                    System.out.println("Success: " + result.get());
                } else {
                    System.err.println("Error: " + result.getLeft().getMessage());
                }
            });
    }
    
    static String fetchContent(String url) throws IOException {
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

4. Фильтрация с обработкой ошибок

import java.util.*;
import java.util.stream.*;

public class FilterWithErrorHandling {
    static class SafeValue {
        private final String value;
        private final boolean isValid;
        
        public SafeValue(String value, boolean isValid) {
            this.value = value;
            this.isValid = isValid;
        }
        
        public Optional<String> getValue() {
            return isValid ? Optional.of(value) : Optional.empty();
        }
    }
    
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        urls.stream()
            .map(url -> {
                try {
                    return new SafeValue(fetchContent(url), true);
                } catch (IOException e) {
                    System.err.println("Skipping " + url + ": " + e.getMessage());
                    return new SafeValue(null, false);
                }
            })
            .filter(SafeValue::isValid)
            .map(SafeValue::getValue)
            .flatMap(Optional::stream)
            .forEach(System.out::println);
    }
    
    static String fetchContent(String url) throws IOException {
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

5. Обработка в forEach

import java.util.*;
import java.util.stream.*;

public class ForEachExceptionHandling {
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        // Обработка исключений в forEach
        urls.stream().forEach(url -> {
            try {
                System.out.println(fetchContent(url));
            } catch (IOException e) {
                System.err.println("Error processing " + url + ": " + e.getMessage());
                // Логирование ошибки
                e.printStackTrace();
            }
        });
    }
    
    static String fetchContent(String url) throws IOException {
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

6. Собственная утилита обработки ошибок

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class StreamExceptionHandler {
    // Функциональный интерфейс для обработки исключений
    interface StreamFunction<T, R> {
        R apply(T t) throws Exception;
    }
    
    // Способ 1: Игнорировать ошибки
    static <T, R> Function<T, Optional<R>> safe(StreamFunction<T, R> fn) {
        return t -> {
            try {
                return Optional.of(fn.apply(t));
            } catch (Exception e) {
                System.err.println("Error: " + e.getMessage());
                return Optional.empty();
            }
        };
    }
    
    // Способ 2: Собрать ошибки
    static <T, R> Function<T, Result<R>> safeWithError(StreamFunction<T, R> fn) {
        return t -> {
            try {
                return Result.success(fn.apply(t));
            } catch (Exception e) {
                return Result.failure(e);
            }
        };
    }
    
    static class Result<R> {
        private final R value;
        private final Exception error;
        
        private Result(R value, Exception error) {
            this.value = value;
            this.error = error;
        }
        
        static <R> Result<R> success(R value) {
            return new Result<>(value, null);
        }
        
        static <R> Result<R> failure(Exception error) {
            return new Result<>(null, error);
        }
        
        Optional<R> getValue() {
            return Optional.ofNullable(value);
        }
        
        Optional<Exception> getError() {
            return Optional.ofNullable(error);
        }
    }
    
    public static void main(String[] args) {
        List<String> urls = Arrays.asList(
            "https://example.com/1",
            "https://example.com/2",
            "https://example.com/invalid",
            "https://example.com/4"
        );
        
        // Способ 1: Игнорировать ошибки
        urls.stream()
            .map(safe(StreamExceptionHandler::fetchContent))
            .flatMap(Optional::stream)
            .forEach(System.out::println);
        
        System.out.println("---");
        
        // Способ 2: Собрать информацию об ошибках
        urls.stream()
            .map(safeWithError(StreamExceptionHandler::fetchContent))
            .forEach(result -> {
                if (result.getValue().isPresent()) {
                    System.out.println("✓ " + result.getValue().get());
                } else {
                    System.err.println("✗ " + result.getError().get().getMessage());
                }
            });
    }
    
    static String fetchContent(String url) throws IOException {
        if (url.contains("invalid")) {
            throw new IOException("Invalid URL");
        }
        return "Content from " + url;
    }
}

7. parallelStream() с обработкой ошибок

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.*;

public class ParallelStreamExceptionHandling {
    public static void main(String[] args) {
        List<String> urls = IntStream.range(1, 101)
            .mapToObj(i -> "https://example.com/" + i)
            .collect(Collectors.toList());
        
        // Параллельная обработка с обработкой ошибок
        urls.parallelStream()
            .map(url -> {
                try {
                    return Optional.of(fetchContent(url));
                } catch (IOException e) {
                    System.err.println(Thread.currentThread().getName() + 
                        " - Error: " + e.getMessage());
                    return Optional.empty();
                }
            })
            .flatMap(Optional::stream)
            .forEach(System.out::println);
    }
    
    static String fetchContent(String url) throws IOException {
        // Имитация обработки
        Thread.sleep(10);
        if (Math.random() < 0.1) {
            throw new IOException("Random failure");
        }
        return "Processed: " + url;
    }
}

Сравнение подходов

ПодходПростотаКонтрольПроизводительностьКогда использовать
Try-catch обёрткаПростойПолныйХорошаяРедкие ошибки
OptionalСреднийСреднийХорошаяОпциональные результаты
Either (Vavr)СреднийПолныйХорошаяФункциональный подход
Собственная утилитаСреднийПолныйХорошаяПереиспользуемый код
Filter + catchПростойХорошийОтличнаяПропустить ошибки
forEach с catchПростойПолныйХорошаяПобочные эффекты

Лучшие практики

  1. Выбирайте подход в зависимости от случая:

    • Редкие ошибки → try-catch
    • Опциональные результаты → Optional
    • Обе стороны результата → Either
    • Пропустить ошибки → filter + safe
  2. Логируйте ошибки, а не молчите

  3. Используйте собственные утилиты для переиспользования

  4. Для parallelStream() обязательно обрабатывайте ошибки

  5. Документируйте, какие исключения выбрасываются

Эти подходы позволяют элегантно обрабатывать исключения при работе со Stream API.