How to implement the Elvis operator in Java 8?

21,313

Solution 1

Maybe I'm overlooking something, but is there a reason that you can't use Optional#map?

The following example prints nothing, as Optional is short-circuiting in the sense that, if the value inside the Optional doesn't exist (it's null or the Optional is empty), it's treated as empty.

Optional.ofNullable("test")
        .map(s -> null)
        .ifPresent(System.out::println);

For that reason, I'd think you could just do the following:

return Optional.ofNullable(thing)
               .map(x -> x.nullableMethod1(a))
               .map(y -> y.nullableMethod2(b))
               .map(Z::nullableMethod3);

This would map your thing if it exists, or return an empty Optional otherwise.

Solution 2

In Java 8, the Elvis operator can be simulated by chaining .map(...) calls on an Optional.ofNullable(...) and capping it with .orElse(...):

Optional.ofNullable(dataObject)
.map(DataObject::getNestedDataObject)
.map(NestedDataObject::getEvenMoreNestedDataObject)
...
.orElse(null);

A full example:

import java.util.Optional;

class Main {
  // Data classes:
  static class Animal {
    Leg leg;

    Animal(Leg leg) {
      this.leg = leg;
    }

    Leg getLeg(){return this.leg;}

    public String toString(){
      String out = "This is an animal";
      out += leg != null ? " with a leg" : "";
      return out;
    }
  }

  static class Leg {
    Toes toes;

    Leg(Toes toes) {
      this.toes = toes;
    }

    Toes getToes(){return this.toes;}

    public String toString(){
      String out = "This is a leg";
      out += toes != null ? " with a collection of toes" : "";
      return out;
    }
  }

  static class Toes {
    Integer numToes;

    Toes(Integer numToes) {
      this.numToes = numToes;
    }

    Integer getNumToes(){return this.numToes;}

    public String toString(){
      String out = "This is a collection of ";
      out += numToes != null && numToes > 0 ? numToes : "no";
      out += " toes";
      return out;
    }
  }

  // A few example Elvis operators:
  static Integer getNumToesOrNull(Animal a) {
    return Optional.ofNullable(a)
      .map(Animal::getLeg)
      .map(Leg::getToes)
      .map(Toes::getNumToes)
      .orElse(null);
  }

  static Toes getToesOrNull(Animal a) {
    return Optional.ofNullable(a)
      .map(Animal::getLeg)
      .map(Leg::getToes)
      .orElse(null);
  }

  static Leg getLegOrNull(Animal a) {
    return Optional.ofNullable(a)
      .map(Animal::getLeg)
      .orElse(null);
  }

  // Main function:
  public static void main(String[] args) {
    // Trying to access 'numToes':
    System.out.println(getNumToesOrNull(new Animal(new Leg(new Toes(4))))); // 4
    System.out.println(getNumToesOrNull(new Animal(new Leg(new Toes(null))))); // null
    System.out.println(getNumToesOrNull(new Animal(new Leg(null)))); // null
    System.out.println(getNumToesOrNull(new Animal(null))); // null
    System.out.println(getNumToesOrNull(null)); // null

    // Trying to access 'toes':
    System.out.println(getToesOrNull(new Animal(new Leg(new Toes(4))))); // This is a collection of 4 toes
    System.out.println(getToesOrNull(new Animal(new Leg(new Toes(null))))); // This is a collection of no toes
    System.out.println(getToesOrNull(new Animal(new Leg(null)))); // null
    System.out.println(getToesOrNull(new Animal(null))); // null
    System.out.println(getToesOrNull(null)); // null

    // Trying to access 'leg':
    System.out.println(getLegOrNull(new Animal(new Leg(new Toes(4))))); // This is a leg with a collection of toes
    System.out.println(getLegOrNull(new Animal(new Leg(new Toes(null))))); // This is a leg with a collection of toes
    System.out.println(getLegOrNull(new Animal(new Leg(null)))); // This is a leg
    System.out.println(getLegOrNull(new Animal(null))); // null
    System.out.println(getLegOrNull(null)); // null
  }
}
Share:
21,313
Mickalot
Author by

Mickalot

Updated on July 05, 2022

Comments

  • Mickalot
    Mickalot almost 2 years

    I have the classic "Elvis operator" case, where I'm calling methods that each may return null and chaining them together:

    thing?:nullableMethod1(a)?:nullableMethod2(b)?:nullableMethod3()
    

    In Java 8, the most faithful implementation I've found is something like this:

    return Optional.ofNullable(thing)
        .flatMap(x -> Optional.ofNullable(x.nullableMethod1(a)))
        .flatMap(y -> Optional.ofNullable(y.nullableMethod2(b)))
        .flatMap(z -> Optional.ofNullable(z.nullableMethod3()))
    

    I wish that Java's Optional had something akin to the elvis operator:

    public<U> Optional<U> elvisOperator(Function<? super T, ? extends U> mapper) {
        return flatMap(t -> Optional.ofNullable(mapper.apply(t));
    }
    

    So that I wouldn't have to wrap each return value:

    return Optional.ofNullable(thing)
        .elvisOperator(x -> x.nullableMethod1(a))
        .elvisOperator(y -> y.nullableMethod2(b))
        .elvisOperator(Z::nullableMethod3); // also nice
    

    Is there a more efficient and idiomatic way to implement the Elvis operator pattern in Java 8?

  • Holger
    Holger over 5 years
    It’s worth pointing to the documentation of Optional.map to show that the behavior is intentional: “If the mapping function returns a null result then this method returns an empty Optional”.
  • melston
    melston over 5 years
    @Holger, and this explains why, in the long run, that was a bad idea. Or, at least, sub-optimal (from a FP perspective).
  • Mickalot
    Mickalot about 5 years
    I think what you're adding to the accepted answer is that some languages blur the concepts of optionality and nullability in such a way that the Elvis operator can be used without needing to unpack the result at the end. In Java, the method implementations sometimes blur those concepts, but the type system has distinct notions, so yes, you're right that if one wanted a @Nullable Foo instead of an Optional<Foo>, one would likely want to use orElse at the end.
  • Abhishek Divekar
    Abhishek Divekar about 5 years
    @Mickalot yes, that was the point I was trying to make. I had seen a few other answers and was confused by how to do it without an Optional, until I tried this example. I thought it might benefit others.
  • yeoman
    yeoman over 3 years
    They implemented a very important feature but for some reason decided to severely damage trusty old map in the process. It would be easier to forgive if they had actually named the necessary second method with this behavior "elvis" 🤣