RSS-Feed anzeigen

Landei

Eine fehlende Optional-Methode und ein Workaround

Bewerten
Ein wirklich hässliches Pattern, um mehrere Dinge auszuprobieren, bis eines davon klappt, ist:

Java Code:
  1.  
  2. int x = ...
  3. int y = ...
  4. int z = ...
  5.  
  6. Integer result = null;
  7. if (x % 2 == 0) {
  8.     result = x / 2;
  9. }
  10. if (result == null && % 2 == 0) {
  11.     result = y / 2;
  12. }
  13. if (result == null && z % 2 == 0) {
  14.     result = z / 2;
  15. }
  16. String s = result == null
  17.     ? "all numbers were odd"
  18.     : "the result is " + result;

Eigentlich sollte Optional uns in diesen Fällen die Null-Checks abnehmen. In Scala und mit der Optional-Variante von Guava geht das auch sehr schön, da in beiden Fällen Methoden vorhanden sind, um einen vorhandenen Wert unverändert "durchzuschleusen", aber im "leeren" Fall stattdessen ein anderes Optional zu verwenden.

Unverständlicherweise fehlt eine solche Funktion in Java 8. Die direkte Übersetzung unseres Beispiels sieht deshalb auch nicht viel hübscher als das Original aus:

Java Code:
  1.  
  2. public static Optional<Integer> half(int x) {
  3.     return x % 2 == 0
  4.         ? Optional.of(x / 2)
  5.         : Optional.empty();
  6. }
  7.  
  8. ...
  9.  
  10. Optional<Integer> result = half(x);
  11. if (! result.isPresent()) {
  12.     result = half(y);
  13. }
  14. if (! result.isPresent()) {
  15.     result = half(z);
  16. }
  17. String s = result
  18.     .map(n -> "the result is " + n)
  19.     .orElse("all numbers were odd");

Zum Vergleich die elegante Scala-Version:

Code:
def half(n : Int) : Option[Int] = 
    if (n % 2 == 0) Some(n / 2) else None

val s = half(x)
    .orElse(half(y))
    .orElse(half(z))
    .map(n => "the result is " + n)
    .getOrElse("all numbers were odd")
Haben wir eine Chance, das wenigstens ungefähr so nachzubauen? Ja, es gibt einen Workaround. Das ist wieder eines jener Probleme, über das man durchaus eine Weile grübeln kann, aber wenn man einmal die Lösung kennt, erscheint sie ganz selbstverständlich:

Java Code:
  1.  
  2. String s = half(x).map(Optional::of)
  3.     .orElse(half(y)).map(Optional::of)
  4.     .orElse(half(z))
  5.     .map(n -> "the result is " + n)
  6.     .orElse("all numbers were odd");

Der Trick ist, mit einem map aus dem Optional&lt;Integer&gt; jeweils ein Optional<Optional<Integer>> zu machen, bevor wir mit der orElse-Methode wieder eine Optional-Ebene "abbauen". Insgesamt macht diese map-orElse-Kombination also genau dasselbe, was in Scala ein einzelnes orElse (oder im Optional von Guava ein or) erledigt.

Wir haben sogar die Chance, hier noch etwas besser zu machen. Was wäre, wenn die half-Aufrufe lange dauern würden oder ungewünschte Seiteneffekte hätten? Dann sollten die Aufrufe natürlich lazy erfolgen, und das geht, in dem man das orElse einfach durch orElseGet ersetzt:

Java Code:
  1.  
  2. String s = half(x).map(Optional::of)
  3.     .orElseGet(() -> half(y)).map(Optional::of)
  4.     .orElseGet(() -> half(z))
  5.     .map(n -> "the result is " + n)
  6.     .orElse("all numbers were odd");

Trotzdem hoffe ich sehr, dass die Verantwortlichen ein Einsehen haben, Optional in Java 9 ein wenig komfortabler gestalten, und uns damit solche Verrenkungen in Zukunft ersparen.

(der Beitrag ist auch im eSCALAtion Blog veröffentlicht worden: https://dgronau.wordpress.com/2016/0...in-workaround/ )
Stichworte: java 8, optional
Kategorien
Kategorielos

Kommentare

  1. Avatar von Fastjack
    Der Ausgangsvergleich würde eher so aussehen, meines Erachtens gut lesbar:

    Code:
            if (x % 2 == 0) {
                result = x / 2;
            } else if (y % 2 == 0) {
                result = y / 2;
            } else if (z % 2 == 0) {
                result = z / 2;
            }
Kommentar schreiben Kommentar schreiben

Trackbacks