返回List的方法:避免Null与不可变空列表的优化实现方案咨询
Great question! Let's break this down—you're absolutely right to avoid returning null (that's a critical best practice), and you're smart to weigh the tradeoffs between returning a new empty ArrayList (mutable, with negligible overhead) vs. Collections.emptyList() (immutable, zero overhead but breaks caller code that modifies the returned list).
Here are your best options to get the best of both worlds:
Option 1: Stick with new ArrayList<>() (most cases are perfectly fine)
First, let's put the "performance advantage" of Collections.emptyList() into perspective. Modern JVMs optimize object allocation extremely well, and an empty ArrayList uses a shared static empty array under the hood (in Java 8+). The overhead of creating a new ArrayList instance is negligible for all but the most hyper-high-throughput scenarios (think millions of calls per second).
This keeps your caller code clean—no changes needed, no risk of UnsupportedOperationException from modifying an immutable list, and you still avoid null entirely.
Option 2: Use Collections.emptyList() and fix the caller's merge logic
If you do want to reap the tiny performance benefit of reusing the immutable empty list, the problem isn't the empty list itself—it's the caller's approach to merging.
Your current caller code modifies the list returned by getItems() directly:
// Risky if getItems returns an immutable list List<Foo> result = getItems(bar, x); result.addAll(getFoo());
Instead, the caller should create a new mutable list to hold all merged elements. This is actually cleaner and more semantically correct (you're creating a new result list, not modifying one of the input lists):
// Safe, regardless of what getItems returns List<Foo> result = new ArrayList<>(); result.addAll(getItems(bar, x)); result.addAll(getFoo()); result.addAll(getAll()); result.addAll(getItems(bar, x));
Now you can safely return Collections.emptyList() from getItems()—the caller's addAll will just skip adding elements from the empty list, no exceptions, no extra null/empty checks needed.
Option 3: Use Java Streams for cleaner, more declarative merging
For a modern, concise approach, use streams to flatten and collect all elements in one go. This works seamlessly with any list (mutable or immutable) and eliminates repetitive addAll calls:
List<Foo> result = Stream.of( getItems(bar, x), getFoo(), getAll(), getItems(bar, x) ) .flatMap(List::stream) .collect(Collectors.toList());
Streams automatically handle empty lists (they contribute nothing to the final result), and you get a clean, readable way to merge all your data sources.
Key Takeaway
Avoiding null is always the right call. For most applications, returning new ArrayList<>() is perfect—it's simple, safe, and the performance cost is unnoticeable. If you need that extra tiny performance boost, adjust the caller's merging logic to not modify the returned list, and use Collections.emptyList().
内容的提问来源于stack exchange,提问作者Jim

