2017-09-28 3 views
4

Ich habe eine ziemlich einfache Frage für euch. In Java 8 wurde der Typ Optional eingeführt. Ich habe zwei Objekte vom Typ Optional<String> und ich möchte wissen, welche die elegantere Art ist, sie zu verketten.Verketten Sie zwei oder mehr optionale Zeichenketten in Java 8

Optional<String> first = Optional.ofNullable(/* Some string */); 
Optional<String> second = Optional.ofNullable(/* Some other string */); 
Optional<String> result = /* Some fancy function that concats first and second */; 

Im Detail, wenn einer der beiden ursprünglichen Optional<String> Objekte Optional.empty() gleich war, möchte ich die ganze Verkettung leer zu sein.

Bitte beachten Sie, dass ich nicht zu fragen, wie die Auswertung von zwei Optional s in Java verketten, aber wie zwei String s verketten, die in einigen Optional sind.

Vielen Dank im Voraus.

Antwort

3

gefunden Die Lösung I ist die folgende:

first.flatMap(s -> second.map(s1 -> s + s1)); 

, die mit einem speziellen Verfahren gereinigt werden können, wie die folgenden:

first.flatMap(this::concat); 
Optional<String> concat(String s) { 
    second.map(s1 -> s + s1); 
} 

Ich denke jedoch, dass etwas Besseres gefunden werden kann.

Wenn wir auf eine Liste oder ein Array von Optional<String> verallgemeinern wollen, dann können wir etwas ähnliches wie folgt verwenden.

Optional<String> result = 
    Stream.of(Optional.of("value1"), Optional.<String>empty()) 
      .reduce(Optional.of(""), this::concat); 

// Where the following method id used 
Optional<String> concat(Optional<String> first, Optional<String> second) { 
    return first.flatMap(s -> second.map(s1 -> s + s1)); 
} 

Beachten Sie, dass, um den obigen Code zu kompilieren, müssen wir manuell die Art Variable Optional.empty() zu String binden.

+1

Es sei denn, Sie haben eine Semigruppe binäre Operator Flatmap ist der beste Weg, zwei Optionen zu kombinieren. Wie in scala könntest du 'first | + | 2. " – soote

+0

Nein, das ist das Beste, was Sie in Java 8 tun können. –

+0

Also, Sie sagen, dass ich es nicht besser machen kann :(Können Sie das" semigroup "Ding erklären? –

0

Sie so etwas wie verwenden:

Optional<String> result; 
result = first.isPresent() && second.isPresent() ? Optional.of(first.get() + second.get()) : Optional.empty(); 
+0

auch für das Szenario Konto, wo nur ein nicht vorhanden – ds011591

+0

@ ds011591 ist, ist nicht, ist der Zustand '' && – nullpointer

+0

so, wenn man nicht die anderen dann nichts zurückgeben? Ich denke, Sie möchten den Wert des jeweils aktuellen Werts zurückgeben. – ds011591

0

Jede Lösung, die eine flexible Anzahl von optionalen Strings erfordert, muss explizit einen StringBuilder verwenden, anstatt sich auf den Compiler zu verlassen, um einen für Sie zu generieren.

String concatThem(Stream<String> stringsin) { 
    StringBuilder sb = new StringBuilder(); 
    stringsin.forEach(s -> sb.append(s)); 
    return sb.toString(); 
} 

Wenn Sie eine Stream<Optional<String>> haben, dann wird es:

String concatThem(Stream<Optional<String>> stringsin) { 
    StringBuilder sb = new StringBuilder(); 
    stringsin.filter(Optional::isPresent).forEach(s -> sb.append(s.get())); 
    return sb.toString(); 
} 

Andernfalls, wenn Sie N haben optional Strings Sie Ende-up mit einem schweren Zyklus von Schöpfung und Zerstörung von N-1 Einweg- String Objekte (zur Kompilierzeit generiert) und N-1-Strings.

Edit: Ich habe falsch verstanden, so ist hier, wie es zu tun ist, wenn eine von ihnen fehlt es zu löschen alle:

String concatThem(Stream<Optional<String>> stringsin) { 
    StringBuilder sb = new StringBuilder(); 
    try { 
     stringsin.forEach(s -> { 
      if (!s.isPresent()) throw new IllegalArgumentException(); 
      sb.append(s.get()) 
     }); 
    } 
    catch(IllegalArgumentException ex) { 
     sb.setLength(0); 
    } 
    return sb.toString(); 
} 

Das ist natürlich, wenn Sie auf die neue API bestehen, die auf das Licht ist Syntax und schwer auf die Ausführung.

+0

Sie wissen, dass Ihre Lösung eine wichtige Grundbedingung nicht einhält? Wenn eines der '' '' '' '' '' '' '' '' '' '' '' '' 'leeren Zeichen leer ist, sollte auch das Ergebnis 'String' leer sein Wie auch immer, ich denke, es gibt einen funktionelleren Weg durchzulaufen. –

+0

Sehen Sie sich meine aktualisierte Antwort an. Ich habe einen eher funktionalen und idiomatischen Ansatz gewählt. –

1
@SafeVarargs 
public final Optional<String> concat(Optional<String>... inputs) 
{ 
    return Arrays.stream(inputs) 
     .reduce((left, right) -> left.flatMap(leftValue -> right.map(rightValue -> leftValue + rightValue))) 
     .get(); 
} 

@Test 
public void shouldReturnEmptyIfFirstItemIsEmpty() 
{ 
    assertThat(concat(Optional.empty(), Optional.of("B")), is(Optional.empty())); 
} 

@Test 
public void shouldReturnEmptyIfSecondItemIsEmpty() 
{ 
    assertThat(concat(Optional.of("A"), Optional.empty()), is(Optional.empty())); 
} 

@Test 
public void shouldConcatIfNoItemIsEmpty() 
{ 
    assertThat(concat(Optional.of("A"), Optional.of("B")), is(Optional.of("AB"))); 
} 

Hier ist eine Implementierung mit der Reduce-Methode für Stream.

+1

Sie wissen, dass jedes Mal, wenn Sie '@ SafeVarargs' benutzen, ein Kätzchen irgendwo stirbt? –

+0

Ich habe meine Antwort mit einer sichereren Variante Ihrer Lösung aktualisiert. Hör zu. –

+0

Ich denke, es gibt viele tote Kätzchen, da '@ SafeVarargs' auch in Dingen wie' Arrays.asList (T ... a) 'verwendet wird. Wenn es Sie betrifft, könnten Sie stattdessen immer eine Collection oder einen Stream als Parameter der Methode akzeptieren ... –

0

Hier ist eine andere hübsche Weise:

@Value.Immutable 
public abstract class Person { 

    public Optional<String> firstName() { 
     return Optional.of("John"); 
    } 

    public Optional<String> lastName() { 
     return Optional.of("Smith"); 
    } 

    public Optional<String> location() { 
     return Optional.of("Paris"); 
    } 

    @Value.Lazy 
    public String concat() { 

     return Stream.of(firstName(), lastName(), location()) 
       .filter(Optional::isPresent) 
       .map(Optional::get) 
       .filter(StringUtils::isNotBlank) 
       .reduce((first, second) -> first + '.' + second) 
       .orElse(""); 
    } 
} 

zu beachten, dass, wie in anderen Kommentaren erwähnt, die concat() -Methode String Verkettungen führt, ohne einen String mit (nicht performant sein könnte, wenn Sie eine Menge rufen Sie die Methode von Zeiten). Um dies zu beheben, verwenden wir im obigen Beispiel Immutables '[1] @ Value.Lazy, wodurch sichergestellt wird, dass die concat() -Methode einmal aufgerufen wird und das Ergebnis für weitere Aufrufe zwischengespeichert wird. Funktioniert super!

[1] https://immutables.github.io

Verwandte Themen