Neden Objects.requireNonNull () kullanılmalı?


214

Oracle JDK'da verilen nesne (argüman) ise Objects.requireNonNull()dahili olarak atılan birçok Java 8 yönteminin kullanıldığını belirttim .NullPointerExceptionnull

public static <T> T requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;
}

Ancak NullPointerExceptionbir nullnesne kayıttan çıkarılırsa yine de atılır . Öyleyse, neden bu ekstra null kontrol ve atışı yapmalı NullPointerException?

Açık bir cevap (ya da yarar) kod daha okunabilir hale ve ben katılıyorum olmasıdır. Objects.requireNonNull()Yöntemin başında kullanmanın başka nedenlerini bilmek isterim .




1
Argüman kontrolüne yönelik bu yaklaşım, birim testleri yazarken hile yapmanıza olanak tanır. Spring'de bunun gibi araçlar da vardır (bkz. Docs.spring.io/spring/docs/current/javadoc-api/org/… ). Bir "if" değerine sahipseniz ve yüksek birim test kapsamına sahip olmak istiyorsanız, her iki dalı da kapsamalısınız: koşul karşılandığında ve koşul karşılanmadığında. Eğer kullanırsanız Objects.requireNonNullbir birim testinin tek geçişli :-) size% 100 kapsama alacak, böylece kod hiçbir dallanma vardır
xorcus

Yanıtlar:


214

Çünkü bunu yaparak işleri açık hale getirebilirsiniz . Sevmek:

public class Foo {
  private final Bar bar;

  public Foo(Bar bar) {
    Objects.requireNonNull(bar, "bar must not be null");
    this.bar = bar;
  }

Veya daha kısa:

  this.bar = Objects.requireNonNull(bar, "bar must not be null");

Artık biliyorsunuz :

  • ne zaman bir Foo nesne başarıyla kullanılarak oluşturuldunew()
  • daha sonra da çubuğu alan bir garanti olmayan boş değer.

Bunu şununla karşılaştırın: bugün bir Foo nesnesi oluşturursunuz ve yarın o alanı kullanan ve fırlatan bir yöntem çağırırsınız. Büyük olasılıkla, yarın bu referansın neden kurucuya geçtiğinde null olduğunu bilmeyeceksiniz !

Başka bir deyişle: Gelen referansları kontrol etmek için bu yöntemi açıkça kullanarak istisnanın atılacağı noktayı kontrol edebilirsiniz . Ve çoğu zaman, mümkün olduğunca hızlı başarısız olmak istersiniz !

Başlıca avantajları:

  • söylendiği gibi, kontrollü davranış
  • daha kolay hata ayıklama - çünkü nesne yaratma bağlamında ortaya çıkarsınız. Belli bir şansın olduğu bir noktada, günlüklerinizin / izlerinizin neyin yanlış gittiğini söylemesi!
  • ve yukarıda gösterildiği gibi: bu fikrin gerçek gücü nihai alanlarla birlikte gelişir . Çünkü artık sınıfınızdaki diğer tüm kodlarbar bunun boş olmadığını varsayabilir ve bu nedenle if (bar == null)başka yerlerde herhangi bir kontrole ihtiyacınız yoktur !

23
Kodunuzu yazarak daha kompakt hale getirebilirsinizthis.bar = Objects.requireNonNull(bar, "bar must not be null");
Kirill Rakhman

3
@KirillRakhman GhostCat'a bilmediğim harika bir şey öğretmek -> GhostCat upvote piyangosunda bir bilet kazanmak.
GhostCat

1
Bence buradaki # 1 yorum asıl yararı Objects.requireNonNull(bar, "bar must not be null");. Bunun için teşekkürler.
Kawu

1
Hangisi ilkti ve neden "dil" halkı bazı kütüphanelerin yazarlarını izlesin ki ?! Neden guava Java dili davranışını izlemiyor ?!
GhostCat

1
this.bar = Objects.requireNonNull(bar, "bar must not be null");İki ya da daha fazla değişken aynı yöntemle ayarlanır ise kurucular ok, ancak diğer yöntemler de, potansiyel olarak tehlikelidir. Örnek: talkwards.com/2018/11/03/…
Erk

100

Fail-hızlı

Kodun mümkün olan en kısa sürede çökmesi gerekir. İşin yarısını yapmamalı ve sonra null ve çökme işleminden vazgeçmelidir, ancak bazı işlerin yarısını bırakarak sistemin geçersiz durumda olmasına neden olun.

Buna genellikle "erken başarısız" veya "hızlı başarısız " denir .


40

Ancak null bir nesnenin kaydı kaldırılırsa NullPointerException yine de atılır. Peki, neden bu ekstra null kontrol yapmak ve NullPointerException atmak gerekir?

Bu, sorunu anında ve güvenilir bir şekilde tespit ettiğiniz anlamına gelir .

Düşünmek:

  • Kodunuzda bazı yan etkiler gerçekleştirildikten sonra başvuru, yöntemin ilerisine kadar kullanılamaz
  • Referans bu yöntemde hiç kayıttan çıkarılmamış olabilir
    • Tamamen farklı bir koda geçirilebilir (yani kod alanında neden ve hata birbirinden uzaktır)
    • Daha sonra kullanılabilir (yani neden ve hata zaman içinde çok farklıdır)
  • Bu bir null başvuru olduğunu Kullanılan yerde olabilir olduğunu geçerlidir, ancak istenmeyen bir etkiye sahiptir

.NET NullReferenceException("bir boş değer dereferenced") ayırarak (" ArgumentNullExceptionbir bağımsız değişken olarak null içinde geçirmemeliydim - ve bu parametre için) ayırarak bunu daha iyi hale getirir . Java aynı şeyi yapsaydı keşke sadece bir NullPointerException, hata saptanabildiği en erken noktaya atılırsa kodu düzeltmek çok daha kolaydır.


12

requireNonNull()Bir yöntemde ilk ifadeler olarak kullanmak , şu anda tanımlanmasına / istisnanın nedenini hızlı bir şekilde belirlemenize izin verir.
Stacktrace açıkça, yöntemin girişinde istisnanın atıldığını gösterir, çünkü arayan gereksinimlere / sözleşmeye saygı göstermedi. Bir Geçme nullbaşka yönteme nesneyi olabilir gerçekten bir bir seferde istisna ama sorunun nedeni daha istisna üzerinde belirli çağırma atılır gibi anlamak için karmaşık olabilir provoke nullçok daha olabilir nesne.


İşte genel olarak hızlı Object.requireNonNull()bir şekilde başarısız olmanın ve daha özel olarak veya olmamak üzere tasarlanan parametreler üzerinde boş bir kontrol yapmanın nedenini tercih etmemizi gösteren somut ve gerçek bir örnek null.

Bir düşünelim Dictionarybir oluşturan sınıf LookupServiceve Listiçinde Stringbulunan kelimeleri temsil eden. Bu alanlar değil olacak şekilde tasarlanmıştır nullve bunlardan biri de geçirilir Dictionary yapıcısı.

Şimdi yöntem girdisinde çek Dictionaryolmadan "kötü" bir uygulama olduğunu varsayalım null(burada kurucu):

public class Dictionary {

    private final List<String> words;
    private final LookupService lookupService;

    public Dictionary(List<String> words) {
        this.words = this.words;
        this.lookupService = new LookupService(words);
    }

    public boolean isFirstElement(String userData) {
        return lookupService.isFirstElement(userData);
    }        
}


public class LookupService {

    List<String> words;

    public LookupService(List<String> words) {
        this.words = words;
    }

    public boolean isFirstElement(String userData) {
        return words.get(0).contains(userData);
    }
}

Şimdi yapıcıyı parametre için Dictionarybir nullreferansla wordsçağıralım:

Dictionary dictionary = new Dictionary(null); 

// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");

JVM, NPE'yi şu ifadeye atıyor:

return words.get(0).contains(userData); 
"Main" iş parçacığında özel durum java.lang.NullPointerException
    LookupService.isFirstElement adresinde (LookupService.java:5)
    at Dictionary.isFirstElement (Dictionary.java:15)
    at Dictionary.main (Dictionary.java:22)

İstisna LookupServicesınıfta tetiklenirken , kökeninin kaynağı daha erken olur ( Dictionaryyapıcı). Genel sayı analizini daha az belirgin hale getirir.
Öyle words nullmi? Öyle words.get(0) nullmi? Her ikisi de ? Neden biri, diğeri ya da belki ikisi de null? Dictionary(Yapıcı? Çağrılan yöntem?) 'De bir kodlama hatası mı? Bu bir kodlama hatası LookupServicemı? (kurucu? çağrılan yöntem?)?
Son olarak, hata kaynağını bulmak için daha fazla kod incelememiz gerekecek ve daha karmaşık bir sınıfta, ne olduğunu daha kolay anlamak için bir hata ayıklayıcı bile kullanabiliriz.
Ama neden basit bir şey (boş kontrol eksikliği) karmaşık bir sorun haline geliyor?
Çünkü alt bileşenlerde belirli bir bileşen sızıntısında tanımlanabilir ilk hata / eksikliğe izin verdik.
Bunun LookupServiceyerel bir hizmet değil, uzaktaki bir hizmet ya da az sayıda hata ayıklama bilgisi içeren bir üçüncü taraf kütüphanesi olduğunu düşünün ya da daha önce nulltespit edilecek 2 kat değil, 4 veya 5 kat nesne çağırma olduğunu hayal edin. Sorunun analizi hala daha karmaşık olacaktır.

Bu yüzden tercih etme yolu:

public Dictionary(List<String> words) {
    this.words = Objects.requireNonNull(words);
    this.lookupService = new LookupService(words);
}

Bu şekilde, baş ağrısı yok: bu alınır alınmaz istisna atılır:

// exception thrown early : in the constructor 
Dictionary dictionary = new Dictionary(null);

// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
"Main" iş parçacığında özel durum java.lang.NullPointerException
    java.util.Objects.requireNonNull'da (Objects.java:203)
    com.ictionary. (Dictionary.java:15)
    com.ictionary.main adresinde (Dictionary.java:24)

Burada bir yapıcı ile sorunu resmediyor, ancak yöntem çağırma aynı null olmayan kontrol kısıtlaması olabilir unutmayın.


Vaov! Harika bir açıklama.
Jonathas Nascimento

2

Bir yan not olarak, bu Object#requireNotNulldaha önce hızlı başarısızlık, bazı jre sınıflarının içinde java-9'dan önce biraz farklı uygulandı. Varsayalım:

 Consumer<String> consumer = System.out::println;

Java-8'de bu derleme (sadece ilgili parçalar)

getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass

Temel olarak aşağıdaki gibi bir işlem: yourReference.getClass- eğer Referansınız olursa başarısız olur null.

Aynı kodun derlendiği jdk-9'da işler değişti

getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull

Veya temel olarak Objects.requireNotNull (yourReference)


1

Temel kullanım NullPointerExceptionhemen kontrol etmek ve fırlatmaktır .

Aynı gereksinimi karşılamak için daha iyi bir alternatif (kısayol), lombok tarafından @NonNull ek açıklamasıdır.


1
Ek açıklama, Nesneler yönteminin yerini almaz, birlikte çalışırlar. @ NonNull x = mightBeNull diyemezsiniz, @ NonNull x = Objects.requireNonNull (mightBeNull, "akıl almaz!");
Bill K

@BillK üzgünüm, u dont u
Supun Wijerathne

Ben sadece Nonnull ek açıklamasının requirNonNull ile çalıştığını söylüyordum, bu bir alternatif değil, ama birlikte gayet iyi çalışıyorlar.
Bill K

başarısız hızlı doğayı uygulamak için, bu alternatif bir hak mı? Evet, bunun görev için bir alternatif olmadığını kabul ediyorum.
Supun Wijerathne

İhtiyacım @Nullable @ @ NonNull için bir "dönüşüm" çağırmak sanırım. Ek açıklamaları kullanmazsanız, yöntem gerçekten çok ilginç değildir (çünkü tüm yaptığı, koruduğu kod gibi bir NPE atmaktır) - niyetinizi açıkça açıkça göstermesine rağmen.
Bill K

1

nullDaha sonraki bir noktada bulunan bir nesnenin üyesine eriştiğinizde boş işaretçi istisnası atılır . Objects.requireNonNull()hemen değeri kontrol eder ve ileri gitmeden anında istisna atar.


0

Ben kopya yapıcılar ve giriş parametresi bir nesne olan DI gibi bazı diğer durumlarda kullanılması gerektiğini düşünüyorum, parametrenin null olup olmadığını kontrol etmelisiniz. Bu gibi durumlarda, bu statik yöntemi rahatlıkla kullanabilirsiniz.


0

Nullability kontrolünü uygulayan derleyici uzantıları bağlamında (örn: uber / NullAway ), Objects.requireNonNullkodunuzun belirli bir noktasında boş olmayan bir boş alanınız olduğu durumlar için az miktarda kullanılmalıdır.

Bu şekilde iki ana kullanım vardır:

  • onaylama

    • burada zaten diğer yanıtların kapsamında
    • ek yük ve potansiyel NPE ile çalışma zamanı kontrolü
  • Nullability marking (@Nullable'dan @Nonnull olarak değiştiriliyor)

    • derleme zamanı kontrolü lehine çalışma zamanı kontrolünün asgari kullanımı
    • yalnızca ek açıklamalar doğru olduğunda çalışır (derleyici tarafından uygulanır)

Nullability işaretlemesinin örnek kullanımı:

@Nullable
Foo getFoo(boolean getNull) { return getNull ? null : new Foo(); }

// Changes contract from Nullable to Nonnull without compiler error
@Nonnull Foo myFoo = Objects.requireNonNull(getFoo(false));

0

Tüm doğru cevaplara ek olarak:

Reaktif akışlarda kullanıyoruz. Genellikle ortaya çıkanNullpointerException s, akıştaki oluşumlarına bağlı olarak diğer İstisnalara sarılır. Bu nedenle, daha sonra hatanın nasıl ele alınacağına kolayca karar verebiliriz.

Sadece bir örnek:

<T> T parseAndValidate(String payload) throws ParsingException { ... };
<T> T save(T t) throws DBAccessException { ... };

burada parseAndValidatesardı NullPointerExceptiongelen requireNonNulla ParsingException.

Şimdi karar verebilirsiniz, örneğin ne zaman yeniden deneme yapıp yapmayacağınıza karar verebilirsiniz:

...
.map(this::parseAndValidate)
.map(this::save)
.retry(Retry.<T>allBut(ParsingException.class))

Denetim olmadan, saveyöntemde NullPointerException oluşacak ve bu da sonsuz yeniden denemelere neden olacaktır . Buna değer olsa da, uzun süren bir abonelik hayal edin,

.onErrorContinue(
    throwable -> throwable.getClass().equals(ParsingException.class),
    parsingExceptionConsumer()
)

Şimdi RetryExhaustExceptionaboneliğinizi öldürecek.

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.