Search code examples
javalambdamapreducejava-streamfunctional-interface

Break or return from java 8 map reduce in functional interface


I have a java 8 functional interface that accepts a list of validators that are applied on an object and returns the validation result. The validation results are accumulated in the reduce phase. The code as follows:

public interface LogicalTableValidator extends Function<LogicalTable, ValidationResult> {

    static LogicalTableValidator addAll(LogicalTableValidator... validators) {

        //  Need to break out of this validator stream, based on the criticality of a particular validation error
        return logicalTable -> Arrays.stream(validators).map(v -> v.apply(logicalTable))
                .reduce(new ValidationResult(logicalTable.getUid()), (validationResult, currentResult) -> {
                    validationResult.addValidationMessages(currentResult.getValidationMessages());
                    return validationResult;
                });

    }

}

This validation logic gets called from here

LogicalTableValidator logicalTableValidators = LogicalTableValidator.addAll(getValidators());
List<ValidationResult> ltValidationResults = logicalTables.stream()
                                                          .parallel()
                                                          .map(logicalTableValidators)
                                                          .collect(Collectors.toList());

The problem I am facing is that, I am not able to break from the validation logic conditionally. This will be the case when I am applying the validators on the logicalObject, if the validation fails with a critical error, I dont need to run rest of the validators. Instead I need to stop the validation process right there.

A work around would be not to use lambda expression for validation and use the following code instead.

        return new LogicalTableValidator() {

            @Override
            public ValidationResult apply(LogicalTable t) {
                ValidationResult result = new ValidationResult(t.getUid());
                for (LogicalTableValidator validator : validators) {
                    ValidationResult currentResult = validator.apply(t);
                    List<ValidationMessage> messages = currentResult.getValidationMessages();
                    Boolean exit = false;
                    for (ValidationMessage message : messages) {
                        if(StringUtils.equalsIgnoreCase(message.getSeverity(),  "1")) {
                            exit = true;
                            break;
                        }
                    }
                    result.addValidationMessages(currentResult.getValidationMessages());
                    if (exit) break;
                }
                return result;
            }

        };

It seems, not using lambda expression in functional interface, defeats the purpose of using functional interface, but I couldn't figure out a way to conditionally break out of the validation loop. Is there any alternative I can use? Should this code be structured in a different way?


Solution

  • You can try something like below. In peek it collect ValidationMessages. In filter and findFirst it stop after first error message. It is replacement for takeWhile that was mentioned in comments, you can also check this.

    public interface LogicalTableValidator extends Function<LogicalTable, ValidationResult> {
    
        static LogicalTableValidator addAll(LogicalTableValidator... validators) {
    
            logicalTable -> {
                ValidationResult result = new ValidationResult(logicalTable.getUid());
                Arrays.stream(validators).map(v -> v.apply(logicalTable))
                        .peek(currentResult -> result.addValidationMessages(currentResult.getValidationMessages()))
                        .filter(currentResult -> currentResult.getValidationMessages().stream()
                                .filter(message -> StringUtils.equalsIgnoreCase(message.getSeverity(), "1"))
                                .count() > 0)
                        .findFirst()
                        .orElse(null);
                return result;
            }
        }
    
    }