Java函数式编程中如何合规地将值读入ArrayList?
Great question—you’re right to be thinking about avoiding mutable state and side effects when working with functional programming in Java. Let’s walk through how to handle input collection without breaking functional principles, addressing your concerns about Scanner variables and iterative adds.
Core Functional Principles to Keep in Mind
First, let’s ground this: functional programming in Java prioritizes:
- No side effects: Avoid modifying external state or relying on mutable variables.
- Pure functions: Functions that return the same output for the same input, with no hidden dependencies.
- Immutability: Prefer immutable data structures where possible (though we can work with
ArrayListif needed, with care).
Option 1: Use Stream API (Cleanest Functional Approach)
The Stream API is designed for functional-style operations, and we can leverage it to read input without explicit iteration or mutable variables. Scanner does have internal state, but we can isolate that side effect to a small, controlled block.
Here’s how to read a fixed number of doubles into an ArrayList:
import java.util.ArrayList; import java.util.Scanner; import java.util.stream.Collectors; import java.util.stream.Stream; public class FunctionalInputReader { public static void main(String[] args) { // Isolate Scanner usage to this try-with-resources block to contain side effects try (Scanner scanner = new Scanner(System.in)) { System.out.println("Enter 5 decimal numbers:"); // Generate a stream of inputs, limit to 5, collect into ArrayList ArrayList<Double> numbers = Stream.generate(scanner::nextDouble) .limit(5) .collect(Collectors.toCollection(ArrayList::new)); // Use the list (treat it as immutable here to stay functional) numbers.forEach(System.out::println); } } }
If you want to stop reading when a specific value is entered (e.g., 0), use takeWhile (Java 9+):
ArrayList<Double> numbers = Stream.generate(scanner::nextDouble) .takeWhile(num -> num != 0) .collect(Collectors.toCollection(ArrayList::new));
Why this works:
- No explicit loops (we let the Stream handle iteration).
- The
ArrayListis created and populated in one step—no manualadd()calls on a mutable external variable. - Scanner’s state changes are contained within the stream generation, so the rest of your code can stay pure.
Option 2: Recursion (As You Suspected)
Recursion is a functional alternative to iteration, but we need to avoid modifying existing collections (since that’s a side effect). Instead of adding to an existing ArrayList, create new lists at each recursive step (or use an immutable list implementation for better compliance).
With Immutable Lists (Most Functional)
Using Guava’s ImmutableList (or Java’s own List.of() for small lists) ensures we never modify existing collections:
import com.google.common.collect.ImmutableList; import java.util.Scanner; public class RecursiveInputReader { // Pure function (well, as pure as possible with Scanner input) private static ImmutableList<Double> readNumbersUntilZero(Scanner scanner) { double input = scanner.nextDouble(); if (input == 0) { return ImmutableList.of(); // Base case: empty list } // Recurse and build a new immutable list with the current input + rest return ImmutableList.<Double>builder() .add(input) .addAll(readNumbersUntilZero(scanner)) .build(); } public static void main(String[] args) { try (Scanner scanner = new Scanner(System.in)) { System.out.println("Enter numbers (0 to stop):"); ImmutableList<Double> numbers = readNumbersUntilZero(scanner); System.out.println("You entered: " + numbers); } } }
If You Must Use ArrayList
If you need an ArrayList specifically, you can still use recursion, but be careful to limit mutable operations to local scope:
import java.util.ArrayList; import java.util.Scanner; public class RecursiveArrayListInput { private static ArrayList<Double> readNumbers(Scanner scanner) { double input = scanner.nextDouble(); if (input == 0) { return new ArrayList<>(); } ArrayList<Double> rest = readNumbers(scanner); rest.add(0, input); // Modify local list only, not an external variable return rest; } public static void main(String[] args) { try (Scanner scanner = new Scanner(System.in)) { System.out.println("Enter numbers (0 to stop):"); ArrayList<Double> numbers = readNumbers(scanner); System.out.println(numbers); } } }
Addressing Your Scanner Concern
You’re right that Scanner has mutable state, but functional programming doesn’t require eliminating all side effects entirely—just isolating them. By wrapping Scanner in a try-with-resources block and only using it within stream generation or a dedicated input method, you prevent those side effects from leaking into the rest of your functional code.
Key Takeaways
- Prefer Stream API: It’s the most idiomatic functional approach in Java for this use case.
- Isolate side effects: Keep IO operations (like
Scannerusage) contained to specific blocks/methods. - Use immutability where possible: Immutable lists avoid accidental side effects and align better with functional principles.
内容的提问来源于stack exchange,提问作者George S.

