求基于Java反射API递归遍历深度嵌套自定义对象实例的代码片段或工具库
Got it, you need to recursively walk through live object instances (not just class metadata) using Java Reflection, inspect which fields are null or non-null, and inject your custom business logic during the traversal. Let's break this down with a reusable code snippet tailored to your example classes, plus clear explanations for key parts.
Step 1: Define a Custom Logic Hook
First, create a functional interface to hold your business logic—this lets you easily swap in different behaviors without modifying the traversal code:
// Functional interface to encapsulate your custom field processing logic @FunctionalInterface interface FieldProcessor { void process(String fieldPath, Object fieldValue, boolean isNull); }
Step 2: Core Traversal Tool Class
This class handles the recursive traversal, handles edge cases like circular references, and applies your custom logic to each field:
import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.*; public class ObjectInstanceTraverser { // Track visited objects to avoid infinite loops from circular references private final Set<Object> visited = new HashSet<>(); public void traverse(Object rootObject, FieldProcessor processor) { if (rootObject == null) { return; } // Skip if we've already processed this object (prevents stack overflow from loops) if (!visited.add(rootObject)) { return; } Class<?> clazz = rootObject.getClass(); // Iterate over all declared fields (including private ones) for (Field field : clazz.getDeclaredFields()) { // Skip static fields—we only care about instance-specific data if (Modifier.isStatic(field.getModifiers())) { continue; } try { field.setAccessible(true); // Grant access to private fields Object fieldValue = field.get(rootObject); String fieldPath = clazz.getSimpleName() + "." + field.getName(); // Run your custom logic on this field processor.process(fieldPath, fieldValue, fieldValue == null); // Recurse into nested structures if the field isn't null if (fieldValue != null) { Class<?> fieldType = fieldValue.getClass(); if (isCustomObject(fieldType)) { // Drill into custom objects (like your AddOnSubscription or BeneficiaryExternalID) traverse(fieldValue, processor); } else if (fieldType.isArray()) { // Handle array types Object[] array = (Object[]) fieldValue; for (Object item : array) { traverse(item, processor); } } else if (fieldValue instanceof Collection<?>) { // Handle collections (like List<String> in AddOnSubscription) Collection<?> collection = (Collection<?>) fieldValue; for (Object item : collection) { traverse(item, processor); } } // Primitive/wrapper types, enums, and built-ins are skipped—no nested fields to traverse } } catch (IllegalAccessException e) { // Adjust error handling based on your needs throw new RuntimeException("Failed to access field: " + field.getName(), e); } } } // Helper to identify custom objects (exclude built-in Java types) private boolean isCustomObject(Class<?> clazz) { return !clazz.isPrimitive() && !clazz.isEnum() && !Collection.class.isAssignableFrom(clazz) && !Map.class.isAssignableFrom(clazz) && !clazz.getName().startsWith("java.lang.") && !clazz.getName().startsWith("java.util.") && !clazz.isArray(); } }
Step 3: Example Usage with Your Object Instance
Using your sample GetAddOnSubscriptionsRequest instance, here's how to apply the traverser and run your custom logic:
First, your object instance (adjusted with valid default values as you mentioned):
// Your sample object instance GetAddOnSubscriptionsRequest addOnSubscriptionsRequest = new GetAddOnSubscriptionsRequest() .setSubscriptionId("1234578") .setOffset(0L) .setPageSize(50L) .setAddOnSubscriptionsOutput( new GetAddOnSubscriptionsOutput.Builder() .setTotalAddOnSubscriptionsCount(10L) .setAddOnSubscriptions( new AddOnSubscription.Builder() .setAsin("B0XYZ123") .setAddOnIDs(List.of("ADDON_001", "ADDON_002")) .setBaseSubscriptionID("BASE_SUB_789") .setHasOpenProblems(false) .build() ).build() );
Then, run the traversal with your custom logic (e.g., print field details or validate non-null requirements):
public class Main { public static void main(String[] args) { ObjectInstanceTraverser traverser = new ObjectInstanceTraverser(); // Define your custom business logic here FieldProcessor myCustomProcessor = (fieldPath, fieldValue, isNull) -> { String status = isNull ? "NULL" : "NON-NULL"; String valueStr = isNull ? "null" : fieldValue.toString(); System.out.printf("Field: %s | Status: %s | Value: %s%n", fieldPath, status, valueStr); // Example custom logic: Enforce non-null on @NonNull annotated fields // if (field.isAnnotationPresent(NonNull.class) && isNull) { // throw new IllegalArgumentException("Required field is null: " + fieldPath); // } }; // Start traversing your object instance traverser.traverse(addOnSubscriptionsRequest, myCustomProcessor); } }
Sample Output
You'll get output like this (truncated for brevity):
Field: GetAddOnSubscriptionsRequest.subscriptionId | Status: NON-NULL | Value: 1234578 Field: GetAddOnSubscriptionsRequest.statusFilter | Status: NULL | Value: null Field: GetAddOnSubscriptionsRequest.pageSize | Status: NON-NULL | Value: 50 Field: GetAddOnSubscriptionsRequest.offset | Status: NON-NULL | Value: 0 Field: GetAddOnSubscriptionsRequest.addOnSubscriptionsOutput | Status: NON-NULL | Value: com.yourpackage.GetAddOnSubscriptionsOutput@1234abcd Field: GetAddOnSubscriptionsOutput.addOnSubscriptions | Status: NON-NULL | Value: com.yourpackage.AddOnSubscription@5678wxyz Field: AddOnSubscription.subscriptionID | Status: NULL | Value: null Field: AddOnSubscription.addOnIDs | Status: NON-NULL | Value: [ADDON_001, ADDON_002] ...
内容的提问来源于stack exchange,提问作者Voonna Sandeep

