Search code examples
javajava-8option-typefunctional-interface

Call function in Java Optional ifPresent with OrElse


Suppose I have an Excel like data row class DataRow and I would like to compare two rows and call an function to update one cell in a row if these two rows are different. My set up is currently this, using reflection to get each column's value (field):

try {
    Field[] fields = oldRow.getClass().getDeclaredFields();
    Arrays.stream(fields)
          .forEach(field -> field.setAccessible(true))
          .filter(field -> !field.get(oldRow).equals(field.get(newRow))
          .findAny()
          .ifPresent(newRow.updateACell())
          .orElse(newRow.udpateACell("new value"))
} catch (Exception e){
    System.out.println(e);
}

However, this code will give me an error because ifPresent does not allow 'void' type here. I understand the function should be invoked upon the value ifPresent receives, but is there anyway to achieve what I want to without using if else statements or for loop?


Solution

    1. The base of the code is not compilable. Stream#forEach returns void, therefore you cannot perform Stream#filter on that. Use either Stream#peek (please, read this) or Stream#map.

      Arrays.stream(fields)
            .peek(field -> field.setAccessible(true))
            .filter(field -> !field.get(oldRow).equals(field.get(newRow))
            ...
      

      Better use an appropriate method to avoid Stream#map/Stream#peek as of Java 9 (big thanks to @Slaw's comment):

      Arrays.stream(fields)
            .filter(field -> field.trySetAccessible() && !rowsAreEqual(field, oldRow, newRow))
            ...
      
    2. The method Field#get throws an exception that must be handled. The Stream API is not suitable for it, but you can create a wrapper to avoid the boilerplate.

      private static boolean rowsAreEqual(Field field, Row oldRow, Row newRow) {
          try {
              return field.get(oldRow).equals(field.get(newRow));
          } catch (IllegalAccessException e) {
              log.warn("Unexpected error", e);
              return false;
          }
      }
      
      Arrays.stream(fields)
          .peek(field -> !field.setAccessible(true))
          .filter(field -> rowsAreEqual(field, oldRow, newRow))
          ...
      

      Notice, that the outer try-catch is not needed.

    3. The argument of Optional#isPresent is a Consumer<T> and you put there void. Although the Consumer#accept has a void return type, it must be called first. You need to pass the implementation represented by the Consumer<T> and not its result:

      Arrays.stream(fields)
            .peek(field -> field.setAccessible(true))
            .filter(field -> rowsAreEqual(field, oldRow, newRow))
            .findAny()
            .ifPresentOrElse(
                  field -> newRow.updateACell(),
                  () -> newRow.udpateACell("new value"));