Bulduğu ilk öğe null ise neden findFirst () bir NullPointerException oluşturur?


90

Bu neden bir atıyor java.lang.NullPointerException?

List<String> strings = new ArrayList<>();
        strings.add(null);
        strings.add("test");

        String firstString = strings.stream()
                .findFirst()      // Exception thrown here
                .orElse("StringWhenListIsEmpty");
                //.orElse(null);  // Changing the `orElse()` to avoid ambiguity

İlk madde stringsIS nullmükemmel kabul edilebilir bir değerdir. Ayrıca, findFirst()URL'leri işleyebilmek için daha da anlamlı olan bir İsteğe Bağlı döndürür .findFirst()null

DÜZENLEME: orElse()daha az belirsiz olacak şekilde güncellendi .


4
null tamamen kabul edilebilir bir değer değil ... onun yerine "" kullanın
Michele Lacorte

1
@MicheleLacorte Stringburada kullanmama rağmen, ya DB'deki bir sütunu temsil eden bir listeyse ? O sütun için ilk satırın değeri olabilir null.
neverendingqs

Evet, ancak java'da null kabul edilemez ...
db'ye

10
@MicheleLacorte, nullJava'da genel anlamda kabul edilebilir bir değerdir. Özellikle, bir için geçerli bir öğedir ArrayList<String>. Bununla birlikte, diğer herhangi bir değer gibi, onunla yapılabilecekler konusunda sınırlamalar vardır. Kaçınamayacağınız için null" asla kullanma " yararlı bir tavsiye değildir.
John Bollinger

@NathanHughes - Aradığınızda findFirst()yapmak isteyeceğiniz başka bir şey olmadığından şüpheleniyorum .
neverendingqs

Yanıtlar:


72

Bunun nedeni Optional<T>dönüşte kullanılmasıdır. İsteğe bağlı olarak içermesine izin verilmez null. Esasen, "orada değil" ve "orada, ama ayarlanmış null" durumlarını ayırt etmenin bir yolunu sunmuyor .

Bu nedenle , dokümantasyonnull aşağıdakilerden seçildiğinde durumu açıkça yasaklar findFirst():

Atar:

NullPointerException - seçilen öğe ise null


3
Örnekleri içinde özel bir boole ile bir değer olup olmadığını izlemek basit olacaktır Optional. Her neyse, sanırım sınırda söylüyorum - eğer dil onu desteklemiyorsa, desteklemiyor.
neverendingqs

2
@neverendingqs Kesinlikle, booleanbu iki durumu ayırt etmek için a kullanmak çok mantıklı olacaktır. Bana göre Optional<T>burayı kullanmak şüpheli bir seçimmiş gibi görünüyor .
Sergey Kalinichenko

1
@neverendingqs Kendi boş değerinizi atmanın yanı sıra buna hoş görünen bir alternatif düşünemiyorum , bu da ideal değil.
Sergey Kalinichenko

1
Yineleyiciyi herhangi bir Iterabletürden alan, kontrol eden hasNext()ve uygun değeri döndüren özel bir yöntem yazdım .
neverendingqs

1
findFirstPO'nun durumunda boş bir İsteğe Bağlı değer döndürmesinin daha mantıklı olduğunu düşünüyorum
danny

48

Daha önce tartışıldığı gibi, API tasarımcıları, geliştiricinin nulldeğerleri ve eksik değerleri aynı şekilde ele almak istediğini varsaymaz .

Yine de bunu yapmak istiyorsanız, sırayı uygulayarak bunu açıkça yapabilirsiniz.

.map(Optional::ofNullable).findFirst().flatMap(Function.identity())

akıntıya. İlk öğe yoksa veya ilk öğe ise, sonuç her iki durumda da boş bir isteğe bağlı olacaktır null. Yani sizin durumunuzda kullanabilirsiniz

String firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().flatMap(Function.identity())
    .orElse(null);

nullilk öğe yoksa bir değer almak için veya null.

Bu durumları birbirinden ayırmak istiyorsanız, şu flatMapadımı atlayabilirsiniz :

Optional<String> firstString = strings.stream()
    .map(Optional::ofNullable).findFirst().orElse(null);
System.out.println(firstString==null? "no such element":
                   firstString.orElse("first element is null"));

Bu, güncellenmiş sorunuzdan çok farklı değil. Sadece değiştirmek zorunda "no such element"olan "StringWhenListIsEmpty"ve "first element is null"birlikte null. Ancak koşullu ifadelerden hoşlanmıyorsanız, şu şekilde de başarabilirsiniz:

String firstString = strings.stream().skip(0)
    .map(Optional::ofNullable).findFirst()
    .orElseGet(()->Optional.of("StringWhenListIsEmpty"))
    .orElse(null);

Şimdi, firstStringolacak nullbir unsur var ama eğer nullve öyle de olacak "StringWhenListIsEmpty"hiçbir unsur var olduğunda.


Maalesef sorumun null1) ilk öğe olduğu nullveya 2) listede hiçbir öğe bulunmadığı için geri dönmek istediğimi ima etmiş olabileceğini fark ettim . Belirsizliği gidermek için soruyu güncelledim.
neverendingqs

1
3. kod parçacığında bir Optionalatanabilir null. Yana Optionalbir "değer türü" olması gerekiyordu, boş olmamalıdır. Ve bir İsteğe Bağlı asla ile karşılaştırılmamalıdır ==. Kod Java 10 :) 'da başarısız olabilir veya Java'ya değer türü tanıtıldığında.
ZhongYu

1
@ bayou.io: belgeler söylemiyor referanslar değer türlerine olamaz nullve süre örnekleri ile karşılaştırıldığında asla ==, referans için test edilebilir nullkullanılarak ==işte olduğu gibi sadece bunu test etmek yolu null. nullTüm örnek değişkenleri ve dizi öğeleri için varsayılan değer olsa bile, böyle bir "asla " geçişinin mevcut kod için nasıl çalışması gerektiğini göremiyorum null. Parçacık kesinlikle en iyi kod değildir, ancak nulls'yi mevcut değerler olarak ele alma görevi de değildir .
Holger

bkz john gül - ne de hatta bir null ile “==” operatörü ile karşılaştırılabilir
Zhongyu

1
Bu kod bir Generic API kullandığından, bu kavram kutulu gösterimi çağırır ve bu olabilir null. Bununla birlikte, böyle bir varsayımsal dil değişikliği derleyicinin burada bir hata yayınlamasına (kodu sessizce kırmamasına) neden olacağından, muhtemelen Java 10'a uyarlanması gerekeceği gerçeğiyle yaşayabilirim. Sanırım StreamAPI, o zaman da oldukça farklı görünüyor…
Holger

20

java.util.Objects.nonNullBulmadan önce listeyi filtrelemek için kullanabilirsiniz

gibi bir şey

list.stream().filter(Objects::nonNull).findFirst();

1
İstediğim firstStringolmak nulldeki ilk öğe ise stringsolduğunu null.
neverendingqs

3
ne yazık ki Optional.ofboş güvenli olmayan kullanır . Yapabilirsin mapiçin Optional.ofNullable ve daha sonra kullanmak findFirstancak İsteğe bir İsteğe ile sona erecek
Mattos

15

Aşağıdaki kod cümledeki findFirst()ile limit(1)ve cümledeki orElse()ile reduce():

String firstString = strings.
   stream().
   limit(1).
   reduce("StringWhenListIsEmpty", (first, second) -> second);

limit()sadece 1 elemanın erişmesine izin verir reduce. BinaryOperatorGeçirilen reducedöndürdüğü 1 elemanı veya başka "StringWhenListIsEmpty"hiçbir öğe ulaşırsanızreduce .

Bu çözümün güzelliği, Optionaltahsis edilmemiş olması ve BinaryOperatorlambda'nın hiçbir şey tahsis etmemesidir.


1

İsteğe bağlı bir "değer" türü olması gerekir. ( javadoc'daki ince baskıyı okuyun :) JVM , tüm kutulama ve kutudan çıkarma maliyetlerini ortadan kaldırarak hepsini Optional<Foo>sadece ile değiştirebilir Foo. Bir nullFoo boş anlamına gelir Optional<Foo>.

Boole bayrağı eklemeden, boş değerli İsteğe Bağlı seçeneğe izin vermek olası bir tasarımdır - sadece bir sentinel nesne ekleyin. (hatta kullanabilirthis nöbetçi olarak ; bkz. Throwable.cause)

İsteğe Bağlı'nın boş değeri kaydıramayacağı kararı, çalışma zamanı maliyetine dayalı değildir. Bu oldukça tartışmalı bir sorundu ve posta listelerini araştırmanız gerekiyor. Karar herkesi ikna edici değil.

Her durumda, Optional null değeri sarmayamadığı için, bizi bir köşeye iter findFirst. Boş değerlerin çok nadir olduğunu düşünmüş olmalılar (hatta Stream'in boş değerleri barındığı düşünülüyordu), bu nedenle boş akışlar yerine null değerlere istisna atmak daha uygundur.

Bir geçici çözüm kutuya koymaktır null, örneğin

class Box<T>
    static Box<T> of(T value){ .. }

Optional<Box<String>> first = stream.map(Box::of).findFirst();

(Her OOP sorununun çözümünün başka bir tür tanıtmak olduğunu söylüyorlar :)


1
Başka bir Boxtür oluşturmaya gerek yoktur . OptionalTip kendisi bu amaca hizmet edebilir. Bir örnek için cevabıma bakın .
Holger

@Holger - evet, ancak bu İsteğe Bağlı'nın amaçlanan amacı olmadığı için kafa karıştırıcı olabilir. OP'nin durumunda, nulldiğerleri gibi geçerli bir değerdir, ona özel bir muamele yoktur. (bir süre sonrasına kadar :)
ZhongYu
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.