0

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?

Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
Liumx31
  • 1,190
  • 1
  • 16
  • 33
  • Are you looking for `ifPresentOrElse(unused -> newRow.updateACell(), () -> newRow.updateACell("new value"))`? – Slaw Jun 21 '22 at 04:03
  • 2
    By the way: `forEach(...).filter(...)` should fail to compile, as `forEach` returns `void`. – Slaw Jun 21 '22 at 04:04

1 Answers1

2
  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"));
    
Nikolas Charalambidis
  • 40,893
  • 16
  • 117
  • 183
  • Using `peek` for this seems wrong to me (though it's probably fine). If using Java 9, I would just change the `filter` call to `filter(field -> field.trySetAccessible() && !rowsAreEqual(field, oldRow, newRow))`. Or better, move the attempt to make the field accessible to the `rowsAreEqual` method. – Slaw Jun 21 '22 at 04:14
  • @Slaw A good note! I have edited the answer and mentioned you there if you don't mind. Thanks. – Nikolas Charalambidis Jun 21 '22 at 04:19
  • I have also added a link about the appropriate usage of `peek` for sure. – Nikolas Charalambidis Jun 21 '22 at 04:20