Java 8'in işlevsel stili Optional.ifPresent ve eğer mevcut değilse?


274

Java 8'de, Optionalvarsa bir nesneye bir şey yapmak ve yoksa başka bir şey yapmak istiyorum.

if (opt.isPresent()) {
  System.out.println("found");
} else {
  System.out.println("Not found");
}

Bu bir 'işlevsel tarz' değil.

Optionalbir ifPresent()yöntemi var, ama bir orElse()yöntemi zincirlemek mümkün değil .

Böylece yazamıyorum:

opt.ifPresent( x -> System.out.println("found " + x))
   .orElse( System.out.println("NOT FOUND"));

@Assylias adlı kişiye yanıt olarak Optional.map(), aşağıdaki dava için uygun olduğunu düşünmüyorum :

opt.map( o -> {
  System.out.println("while opt is present...");
  o.setProperty(xxx);
  dao.update(o);
  return null;
}).orElseGet( () -> {
  System.out.println("create new obj");
  dao.save(new obj);
  return null;
});

Bu durumda, optmevcut olduğunda , özelliğini günceller ve veritabanına kaydederim . Kullanılabilir olmadığında, yeni bir objveri oluşturur ve veritabanına kaydederim.

Geri dönmek zorunda iki lambdas not edin null.

Ancak optmevcut olduğunda , her iki lambda da idam edilecek. objgüncellenecek ve veritabanına yeni bir nesne kaydedilecektir. Bunun nedeni return nullilk lambdadaki. Ve orElseGet()idam etmeye devam edecek.


53
İlk örneğinizi kullanın. Çok güzel .
Sotirios Delimanolis

3
Bu davranış için tasarlanmamış bir API kullanırken belirli davranışları zorlamayı bırakmanızı öneririm. Rfirst örneğinde bana bazı küçük stil açıklamaları dışında iyi görünüyor, ancak bunlar optiated.
skiwi

4
@smallufo: değiştirme return null;ile return o;(her ikisi). Ancak yanlış yerde çalıştığınıza dair güçlü bir his var. Bunu üreten sitede çalışmalısınız Optional. Bu yerde, ara işlem yapılmadan istenen işlemi gerçekleştirmenin bir yolu olmalıdır Optional.
Holger

10
Java 9, sorununuz için bir çözüm uygular: iteratrlearning.com/java9/2016/09/05/java9-optional.html
pisaruk

2
Bunun kolay bir şekilde yapılmamasının nedeni bilerek yapılmış. İsteğe bağlı akış kontrolü değil, değer dönüşümü yapmalıdır. Bunun ifPresentçelişkilerini biliyorum . Diğer tüm yöntemler, eylemleri değil, değeri ifade eder.
AlikElzin-kilaka

Yanıtlar:


109

Benim için @Dane White'ın cevabı tamam, önce Runnable'ı kullanmayı sevmedim ama alternatif bulamadım, burada başka bir uygulama daha fazla tercih ettim

public class OptionalConsumer<T> {
    private Optional<T> optional;

    private OptionalConsumer(Optional<T> optional) {
        this.optional = optional;
    }

    public static <T> OptionalConsumer<T> of(Optional<T> optional) {
        return new OptionalConsumer<>(optional);
    }

    public OptionalConsumer<T> ifPresent(Consumer<T> c) {
        optional.ifPresent(c);
        return this;
    }

    public OptionalConsumer<T> ifNotPresent(Runnable r) {
        if (!optional.isPresent()) {
            r.run();
        }
        return this;
    }
}

Sonra :

Optional<Any> o = Optional.of(...);
OptionalConsumer.of(o).ifPresent(s ->System.out.println("isPresent "+s))
            .ifNotPresent(() -> System.out.println("! isPresent"));

Güncelleme 1:

değeriniz olduğunda ve işlemek istediğinizde geleneksel gelişim yolu için yukarıdaki çözüm, ancak işlevselliği ve yürütmeyi tanımlamak istersem, aşağıdaki geliştirmeyi kontrol edin;

public class OptionalConsumer<T> implements Consumer<Optional<T>> {
private final Consumer<T> c;
private final Runnable r;

public OptionalConsumer(Consumer<T> c, Runnable r) {
    super();
    this.c = c;
    this.r = r;
}

public static <T> OptionalConsumer<T> of(Consumer<T> c, Runnable r) {
    return new OptionalConsumer(c, r);
}

@Override
public void accept(Optional<T> t) {
    if (t.isPresent()) {
        c.accept(t.get());
    }
    else {
        r.run();
    }
}

Sonra şu şekilde kullanılabilir:

    Consumer<Optional<Integer>> c=OptionalConsumer.of(System.out::println, ()->{System.out.println("Not fit");});
    IntStream.range(0, 100).boxed().map(i->Optional.of(i).filter(j->j%2==0)).forEach(c);

Bu yeni kodda 3 şey var:

  1. nesnenin varlığından önce işlevselliği tanımlayabilir.
  2. her biri için nesne referansı oluşturmaz İsteğe bağlı, sadece bir tane, daha az GC'den daha az belleğe sahipsiniz.
  3. tüketiciyi diğer bileşenlerle daha iyi kullanmak için uyguluyor.

Bu arada adı daha açıklayıcı ve aslında Tüketici>


3
Optional.of (o) yerine Optional.ofNullable (o) kullanmalıdır
hain

2
Kullanacağınız değerin boş olup olmadığından emin değilseniz ve NPE ile yüz yüze kalmanız gerekmediğinden emin değilseniz ve boş olmadığından emin değilseniz veya NPE.
Bassem Reda Zohdy

1
Ben sınıf OptionalConsumer if / else kodundan daha iyi görünüyor düşünüyorum. Teşekkürler! :)
witek1902

207

Java 9+ kullanıyorsanız, ifPresentOrElse()yöntemi kullanabilirsiniz :

opt.ifPresentOrElse(
   value -> System.out.println("Found: " + value),
   () -> System.out.println("Not found")
);

3
Güzel çünkü neredeyse Scala'da desen eşleştirme kadar temiz
sscarduzio

İki kuzu böyle çirkin. Bence bu durumlar için / else çok daha temiz.
john16384

1
@ john16384 Tamam, çirkin bulursan, cevabımı silerim (hayır).
ZhekaKozlov

Bu çok güzel ama soru özellikle JDK8 içindir, çünkü ifPresentOrElse mevcut değildir.
hreinn

81

Java 9 tanıtıldı

ifPresentOrElse bir değer varsa, verilen eylemi değerle gerçekleştirir, aksi takdirde belirtilen boş tabanlı eylemi gerçekleştirir.

Bkz. Mükemmel Java 8 hile sayfasında İsteğe bağlı .

Çoğu kullanım durumu için tüm cevapları sağlar.

Aşağıdaki kısa özet

ifPresent () - İsteğe bağlı ayarlandığında bir şey yapar

opt.ifPresent(x -> print(x)); 
opt.ifPresent(this::print);

filter () - belirli Opsiyonel değerleri reddeder (filtreler).

opt.filter(x -> x.contains("ab")).ifPresent(this::print);

map () - varsa değeri dönüştür

opt.map(String::trim).filter(t -> t.length() > 1).ifPresent(this::print);

orElse () / orElseGet () - boş dönüyor Varsayılan T isteğe bağlı

int len = opt.map(String::length).orElse(-1);
int len = opt.
    map(String::length).
    orElseGet(() -> slowDefault());     //orElseGet(this::slowDefault)

orElseThrow () - boş isteğe bağlı olarak tembel istisnalar atma İsteğe bağlı

opt.
filter(s -> !s.isEmpty()).
map(s -> s.charAt(0)).
orElseThrow(IllegalArgumentException::new);

66
Bu aslında OP'nin sorusuna cevap vermiyor. Birçok ortak kullanıma cevap veriyor, ancak OP'nin sorduğu sorulara cevap vermiyor.
Kaptan Adam

1
@CaptainMan aslında öyle; opt.map ("bulundu") veyaElse ("bulunamadı") ifadesi faturayı doldurur.
Matt

4
@Matt no, OP özellikle isteğe bağlı olduğunda / mevcut olmadığında gerçekleştirdiği işlemleri ister, öyle olduğunda veya olmadığında bir değer döndürmemesini ister. OP bile soruda orElseGet kullanarak neden işe yaramadığını açıklayan benzer bir şeyden bahsediyor.
Kaptan Adam

2
@CaptainMan puanını görebilir miyim. Eğer o sıfırdan dönmediyse işe yarayabileceğini düşünüyorum map, ancak DAO'yu arayabilmeniz için fonksiyonel bir çözüm istemek biraz garip. Bana öyle geliyor ki güncellenen / yeni nesneyi bu map.orElsebloktan döndürmek ve sonra döndürülen nesne ile yapmanız gerekeni yapmak daha mantıklı olacaktır .
Matt

1
mapAkışın kendisine odaklandığımı düşünüyorum ve "akıştaki bu öğenin durumuna bağlı olarak başka bir nesneye bir şeyler yapmak" için tasarlanmamıştır. ifPresentOrElseJava 9'a eklendiğini bilmek güzel
WesternGun

53

Bir alternatif:

System.out.println(opt.map(o -> "Found")
                      .orElse("Not found"));

Yine de okunabilirliği geliştirdiğini sanmıyorum.

Veya Marko'nın önerdiği gibi üçlü bir operatör kullanın:

System.out.println(opt.isPresent() ? "Found" : "Not found");

2
Teşekkürler @assylias, ancak Optional.map () dava için çalıştığını sanmıyorum (bağlam güncellememe bakın).
smallufo

2
@smallufo İlk lambda'nıza geri dönmeniz gerekiyor new Object();ama dürüst olmak gerekirse bu çok çirkinleşiyor. Güncellenmiş örneğiniz için bir if / else komutuna bağlı kalırım.
assylias

Katılıyorum, mapsadece Optionalzincirleme için geri dönmek için kullanmanın , mapkelimenin tam anlamıyla bir şeye eşlendiği varsayılırken kodun anlaşılmasını zorlaştırıyor .
Tiina

40

Başka bir çözüm, aşağıdaki gibi üst düzey işlevleri kullanmak olacaktır:

opt.<Runnable>map(value -> () -> System.out.println("Found " + value))
   .orElse(() -> System.out.println("Not Found"))
   .run();

8
Gözlerim JDK 9 olmadan şimdiye kadarki en iyi çözüm.
Semaphor

5
Bir açıklama harika olurdu. Kendime, neden çalıştırılabilir bir haritayı (?) Kullanmanız ve value -> () -> sysoparçanın ne anlama geldiğini soruyorum .
froehli

Bu çözüm için teşekkürler! Runnable kullanmanın sebebi, haritanın herhangi bir değer döndürmemesi ve Runnable ile lambda döndürmesi ve haritanın sonucu olarak lambda olması, sonra çalıştırmamızdır. Dönen değeriniz varsa, aşağıdakileri kullanabilirsiniz:String result = opt.map(value -> "withOptional").orElse("without optional");
nanotexnik

21

Kutudan çıkarmanın harika bir yolu yok. Temiz sözdiziminizi düzenli olarak kullanmak istiyorsanız, yardımcı olması için bir yardımcı program sınıfı oluşturabilirsiniz:

public class OptionalEx {
    private boolean isPresent;

    private OptionalEx(boolean isPresent) {
        this.isPresent = isPresent;
    }

    public void orElse(Runnable runner) {
        if (!isPresent) {
            runner.run();
        }
    }

    public static <T> OptionalEx ifPresent(Optional<T> opt, Consumer<? super T> consumer) {
        if (opt.isPresent()) {
            consumer.accept(opt.get());
            return new OptionalEx(true);
        }
        return new OptionalEx(false);
    }
}

Ardından, peşinde olduğunuza yakın bir sözdizimi almak için başka bir yerde statik bir içe aktarma kullanabilirsiniz:

import static com.example.OptionalEx.ifPresent;

ifPresent(opt, x -> System.out.println("found " + x))
    .orElse(() -> System.out.println("NOT FOUND"));

Teşekkürler. Bu çözüm çok güzel. (JDK bu yöntemi içermediği sürece) yerleşik bir çözüm olmadığını biliyorum. OptionalEx çok yardımcı oluyor. Her neyse, teşekkür ederim.
smallufo

Evet, sonucu seviyorum ve desteklediği stil. Peki, neden standart API'de değil?
guthrie

İyi cevap. Biz de aynısını yapıyoruz. API (veya dilde) olması gerektiğini kabul ediyorum, ancak reddedildi: bugs.openjdk.java.net/browse/JDK-8057557 .
Garrett Smith

Güzel. Bu, değerlendirilmek üzere JDK 8.1'in bir parçası olmalıdır.
peter_pilgrim

35
Optional.ifPresentOrElse()JDK 9'a eklendi.
Stuart Marks

9

Yalnızca Java 8 veya daha eski bir sürümü kullanabiliyorsanız:

1) spring-dataşimdiye kadar en iyi yolu yoksa :

opt.<Runnable>map(param -> () -> System.out.println(param))
      .orElse(() -> System.out.println("no-param-specified"))
      .run();

Şimdi biliyorum ki biri için anlaşılır ve hatta anlaşılması zor değil, ama şahsen benim için iyi görünüyor ve bu dava için güzel bir akıcı yol göremiyorum.

2) yeterince şanslıysanız ve spring-dataen iyi yolu kullanabiliyorsanız Optionals # ifPresentOrElse :

Optionals.ifPresentOrElse(opt, System.out::println,
      () -> System.out.println("no-param-specified"));

Java 9'u kullanabiliyorsanız, kesinlikle şunları yapmalısınız:

opt.ifPresentOrElse(System.out::println,
      () -> System.out.println("no-param-specified"));

2

Tarif edilen davranışı kullanılarak elde edilebilir Vavr en Scala yapıları uygular (Scala şekilde JVM inşa zengin tip sistem ile daha anlamlı bir dil olmak üzere) olduğu, (eski Javaslang olarak da bilinir), Java 8+ için bir amacı, işlevsel kitaplığı. Saf fonksiyonel kod yazmak için Java projelerinize eklemek için çok iyi bir kütüphane.

Vavr Option, Seçenek türüyle çalışmak için işlevler sağlayan monad sağlar:

  • fold: her iki durumda da seçeneğin değerini eşlemek için (tanımlanmış / boş)
  • onEmpty: Runnableseçeneği boş olduğunda bir zaman yürütülmesine izin verir
  • peek: seçeneğin değerini (tanımlandığında) tüketmeye izin verir.
  • ve bunun Serializabletersi Optionalde yöntem argümanı ve örnek üyesi olarak güvenle kullanabileceğiniz anlamına gelir.

Seçenek, Java'nın İsteğe bağlı "sahte monad" ından farklı olarak monad yasalarına uyar ve daha zengin bir API sağlar. Ve elbette bunu Java'nın İsteğe Bağlı (ve tam Option.ofOptional(javaOptional)tersi ) yöntemiyle yapabilirsiniz : –Vavr birlikte çalışabilirliğe odaklanmıştır.

Örneğe gidiyoruz:

// AWESOME Vavr functional collections (immutable for the gread good :)
// fully convertible to Java's counterparts.
final Map<String, String> map = Map("key1", "value1", "key2", "value2");

final Option<String> opt = map.get("nonExistentKey"); // you're safe of null refs!

final String result = opt.fold(
        () -> "Not found!!!",                // Option is None
        val -> "Found the value: " + val     // Option is Some(val)
);

daha fazla okuma

Boş başvuru, milyar dolarlık hata

Not: Bu, Vavr'ın sunduklarına sadece çok az bir örnektir (örüntü eşleme, tembel değerlendirilmiş listeler olarak akarsu, monadik tipler, değişmez koleksiyonlar, ...).


1

Başka bir çözüm aşağıdaki olabilir:

Bunu şu şekilde kullanırsınız:

    final Opt<String> opt = Opt.of("I'm a cool text");
    opt.ifPresent()
        .apply(s -> System.out.printf("Text is: %s\n", s))
        .elseApply(() -> System.out.println("no text available"));

Veya tersi kullanım durumunda doğru olmanız durumunda:

    final Opt<String> opt = Opt.of("This is the text");
    opt.ifNotPresent()
        .apply(() -> System.out.println("Not present"))
        .elseApply(t -> /*do something here*/);

Bu malzemeler:

  1. Sadece "elseApply" yöntemi için küçük modifiye İşlev arayüzü
  2. İsteğe bağlı geliştirme
  3. Biraz curring :-)

"Kozmetik" gelişmiş Fonksiyon arayüzü.

@FunctionalInterface
public interface Fkt<T, R> extends Function<T, R> {

    default R elseApply(final T t) {
        return this.apply(t);
    }

}

Ve geliştirme için isteğe bağlı sarma sınıfı:

public class Opt<T> {

    private final Optional<T> optional;

    private Opt(final Optional<T> theOptional) {
        this.optional = theOptional;
    }

    public static <T> Opt<T> of(final T value) {
        return new Opt<>(Optional.of(value));
    }

    public static <T> Opt<T> of(final Optional<T> optional) {
        return new Opt<>(optional);
    }

    public static <T> Opt<T> ofNullable(final T value) {
        return new Opt<>(Optional.ofNullable(value));
    }

    public static <T> Opt<T> empty() {
        return new Opt<>(Optional.empty());
    }

    private final BiFunction<Consumer<T>, Runnable, Void> ifPresent = (present, notPresent) -> {
        if (this.optional.isPresent()) {
            present.accept(this.optional.get());
        } else {
            notPresent.run();
        }
        return null;
    };

   private final BiFunction<Runnable, Consumer<T>, Void> ifNotPresent = (notPresent, present) -> {
        if (!this.optional.isPresent()) {
            notPresent.run();
        } else {
            present.accept(this.optional.get());
        }
        return null;
    };

    public Fkt<Consumer<T>, Fkt<Runnable, Void>> ifPresent() {
        return Opt.curry(this.ifPresent);
    }

    public Fkt<Runnable, Fkt<Consumer<T>, Void>> ifNotPresent() {
        return Opt.curry(this.ifNotPresent);
    }

    private static <X, Y, Z> Fkt<X, Fkt<Y, Z>> curry(final BiFunction<X, Y, Z> function) {
        return (final X x) -> (final Y y) -> function.apply(x, y);
    }
}

Bu hile yapmalı ve bu gereksinimlerle nasıl başa çıkılacağı temel bir şablon olarak hizmet edebilir.

Buradaki temel fikir şudur. İşlevsel olmayan bir stil programlama dünyasında muhtemelen iki parametre alan bir yöntem uygularsınız, burada birincisi, değerin mevcut olması durumunda çalıştırılması gereken bir tür çalıştırılabilir koddur ve diğer parametre, değeri mevcut değil. Daha iyi okunabilirlik için, iki parametrenin işlevini, her birinin bir parametresinin iki işlevine bölmek için curring kullanabilirsiniz. Temelde burada yaptığım şey bu.

İpucu: Opt ayrıca, değerin mevcut olmaması durumunda bir kod parçası yürütmek istediğiniz diğer kullanım durumunu da sağlar. Bu, Optional.filter.stuff aracılığıyla da yapılabilir, ancak bunu çok daha okunabilir buldum.

Umarım yardımcı olur!

İyi programlama :-)


Neyin işe yaramadığını söyleyebilir misiniz? Tekrar test ettim ve benim için çalışıyor mu?
Alessandro Giusa

Affedersiniz, ama ckass Fkt nerede tanımlanır? Son fakat en az değil bir derleme sorunum var: Hata: (35, 17) java: isteğe bağlı değişken başlatılmamış olabilir
Enrico Giurin

Fkt yukarıda arayüz olarak tanımlanmıştır. Sadece makalenin tamamını okuyun :-)
Alessandro Giusa

Evet. EFunction arayüzünü Fkt olarak isim olarak değiştirdim. Bir yazım hatası vardı. İnceleme için teşekkürler :-) bunun için üzgünüm.
Alessandro Giusa

Böyle bir kod yazmak için güzel bir fikir olduğunu sanmıyorum ... Yapabildiğinizde jdk yardımcı programı kullanmalısınız.
Tyulpan Tyulpan

0

Değeri saklamak istiyorsanız:

Pair.of<List<>, List<>> output = opt.map(details -> Pair.of(details.a, details.b))).orElseGet(() -> Pair.of(Collections.emptyList(), Collections.emptyList()));

0

Bir listeniz olduğunu varsayarsak ve mevcut isPresent()sorun .iterator().hasNext()olup olmadığını kontrol etmek için (seçeneklerle ilgili) kaçınabilirsiniz .

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.