Java 9中如何利用Stream API将含单一非空值字段的Person对象列表转换为值对象列表
Great question! Let's break this down step by step since you want to use Java's Stream API for the conversion.
First, we need a target value object class to store the converted results (since you mentioned a "value object list"). Then we'll use the Stream API's map() operation to transform each Person instance, with the core logic being identifying the single initialized non-null value field in each Person.
Step 1: Define the Target Value Object
Create a simple value object to hold the person's name and their corresponding valid value:
import lombok.AllArgsConstructor; import lombok.Getter; @Getter @AllArgsConstructor public class PersonValue { // Name of the person private String personName; // The single initialized value from the Person instance private Object effectiveValue; }
If you need stricter type control, you could use generics or split into subclasses, but this generic version works perfectly for the given requirement.
Step 2: Stream API Conversion Logic
Next, we'll use the Stream API's map() operation to iterate over each Person, find the unique valid value by checking fields in sequence, then map it to a PersonValue:
Basic Implementation
import java.util.List; import java.util.Optional; import java.util.stream.Collectors; public class PersonConverter { public static List<PersonValue> convertToValueObjects(List<Person> personList) { return personList.stream() .map(person -> { // Use Optional to safely check for non-null reference fields Object effectiveValue = Optional.ofNullable(person.getValue_string()) .orElseGet(() -> Optional.ofNullable(person.getValue_integer()) .orElseGet(() -> Optional.ofNullable(person.getValue_text()) // Handle the primitive boolean field (can't be null) .orElseGet(() -> person.isValue_bool()))); return new PersonValue(person.getName(), effectiveValue); }) .collect(Collectors.toList()); } }
Key Details:
- Primitive vs Reference Types: Note that
value_boolis a primitiveboolean(it can't benull), so we handle it last. Per your description, when all other reference fields arenull, this boolean field is the initialized one. - Optional Chaining: Using
Optional.ofNullable()with chainedorElseGet()calls lets us cleanly check each reference field in sequence, avoiding messy nestedif-elseblocks.
Step 3: Add Validation (Optional but Recommended)
To ensure your input Person list strictly follows the "only one initialized field" rule, add validation before conversion to catch invalid data:
.map(person -> { // Count how many fields are non-null/initialized long validFieldCount = Stream.of( person.getValue_string(), person.getValue_integer(), person.getValue_text(), // Convert primitive boolean to Boolean to include in null check Boolean.valueOf(person.isValue_bool()) ).filter(Objects::nonNull).count(); if (validFieldCount != 1) { throw new IllegalArgumentException( String.format("Person '%s' has %d initialized value fields (expected exactly 1)", person.getName(), validFieldCount)); } // Retrieve the effective value (same logic as before) Object effectiveValue = Optional.ofNullable(person.getValue_string()) .orElseGet(() -> Optional.ofNullable(person.getValue_integer()) .orElseGet(() -> Optional.ofNullable(person.getValue_text()) .orElseGet(() -> person.isValue_bool()))); return new PersonValue(person.getName(), effectiveValue); })
Alternative: If value_bool is a Boolean (Reference Type)
If your Person class uses Boolean (instead of primitive boolean) for value_bool, you can simplify both validation and value retrieval since all fields are reference types:
Object effectiveValue = Optional.ofNullable(person.getValue_string()) .orElseGet(() -> Optional.ofNullable(person.getValue_integer()) .orElseGet(() -> Optional.ofNullable(person.getValue_text()) .orElseGet(() -> person.getValue_bool())));
The validation logic also becomes simpler—no need to convert the boolean to a wrapper type, just pass person.getValue_bool() directly to the stream.
内容的提问来源于stack exchange,提问作者Kirill

