Etkili Java NullPointerException İşleme

NullPointerException'ın ne hakkında olduğunu ilk elden öğrenmek için fazla Java geliştirme deneyimi gerekmez. Aslında, bir kişi bununla uğraşmanın Java geliştiricilerinin yaptığı bir numaralı hata olduğunu vurguladı. İstenmeyen NullPointerExceptions'ı azaltmak için daha önce String.value (Object) kullanımı üzerine blog yazmıştım. JDK 1.0'dan beri bizimle birlikte olan bu yaygın RuntimeException tipinin oluşumlarını azaltmak veya ortadan kaldırmak için kullanılabilecek birkaç başka basit teknik vardır. Bu blog yazısı, bu tekniklerin en popülerlerinden bazılarını toplar ve özetler.

Kullanmadan Önce Her Nesnenin Boş Olduğunu Kontrol Edin

Bir NullPointerException'dan kaçınmanın en kesin yolu, nesnenin alanlarından veya yöntemlerinden birine erişmeden önce tüm nesne referanslarının boş olmadıklarından emin olmak için kontrol etmektir. Aşağıdaki örneğin gösterdiği gibi, bu çok basit bir tekniktir.

final String causeStr = "adding String to Deque that is set to null."; final String elementStr = "Fudd"; Deque deque = null; try { deque.push(elementStr); log("Successful at " + causeStr, System.out); } catch (NullPointerException nullPointer) { log(causeStr, nullPointer, System.out); } try { if (deque == null) { deque = new LinkedList(); } deque.push(elementStr); log( "Successful at " + causeStr + " (by checking first for null and instantiating Deque implementation)", System.out); } catch (NullPointerException nullPointer) { log(causeStr, nullPointer, System.out); } 

Yukarıdaki kodda, kullanılan Deque, örneği kolaylaştırmak için kasıtlı olarak null olarak başlatılmıştır. İlk trybloktaki kod, bir Deque yöntemine erişmeye çalışmadan önce null olup olmadığını kontrol etmez. İkinci trybloktaki kod null olup olmadığını kontrol eder ve boş ise Deque(LinkedList) ' in bir uygulamasını başlatır . Her iki örnekten elde edilen çıktı şuna benzer:

ERROR: NullPointerException encountered while trying to adding String to Deque that is set to null. java.lang.NullPointerException INFO: Successful at adding String to Deque that is set to null. (by checking first for null and instantiating Deque implementation) 

Yukarıdaki çıktıdaki ERROR'dan sonra gelen mesaj NullPointerException, null üzerinde bir yöntem çağrısı denendiğinde a'nın atıldığını gösterir Deque. Yukarıdaki çıktıda INFO'dan sonra gelen mesaj, Dequeönce boş değeri kontrol ederek ve ardından boş olduğunda bunun için yeni bir uygulama başlatarak istisnadan tamamen kaçınıldığını gösterir.

Bu yaklaşım sıklıkla kullanılır ve yukarıda gösterildiği gibi istenmeyen (beklenmeyen) NullPointerExceptiondurumlardan kaçınmada çok yararlı olabilir . Ancak, maliyeti de vardır. Her nesneyi kullanmadan önce boşluğun kontrol edilmesi kodu şişirebilir, yazmak yorucu olabilir ve ek kodun geliştirilmesi ve bakımı ile ilgili sorunlara daha fazla yer açar. Bu nedenle, yerleşik boş algılama için Java dil desteğinin sunulması, bu kontrollerin ilk kodlamadan sonra boş için otomatik olarak eklenmesi, boş güvenli türler, boş denetim eklemek için Boyut Yönelimli Programlama (AOP) kullanımı hakkında konuşulmaktadır. bayt koduna ve diğer boş algılama araçlarına.

Groovy, potansiyel olarak boş olan nesne referanslarıyla başa çıkmak için zaten uygun bir mekanizma sağlıyor. Groovy'nin güvenli gezinme operatörü ( ?.), boş NullPointerExceptionbir nesne referansına erişildiğinde a atmak yerine null döndürür .

Her nesne başvurusu için boş değeri kontrol etmek sıkıcı olabileceğinden ve kodu şişirdiğinden, birçok geliştirici hangi nesnelerin boş olup olmadığını kontrol edeceğini mantıklı bir şekilde seçmeyi seçer. Bu genellikle, kaynağı bilinmeyen potansiyel olarak tüm nesnelerde boş değerin kontrol edilmesine yol açar. Buradaki fikir, nesnelerin açık arabirimlerde kontrol edilebilmesi ve ardından ilk kontrolden sonra güvenli olduğunun varsayılabilmesidir.

Bu, üçlü operatörün özellikle yararlı olabileceği bir durumdur. Onun yerine

// retrieved a BigDecimal called someObject String returnString; if (someObject != null) { returnString = someObject.toEngineeringString(); } else { returnString = ""; } 

üçlü operatör bu daha kısa sözdizimini destekler

// retrieved a BigDecimal called someObject final String returnString = (someObject != null) ? someObject.toEngineeringString() : ""; } 

Null için Yöntem Bağımsız Değişkenlerini Kontrol Edin

Az önce tartışılan teknik tüm nesnelerde kullanılabilir. Bu tekniğin açıklamasında belirtildiği gibi, birçok geliştirici, nesneleri yalnızca "güvenilmeyen" kaynaklardan geldiklerinde boş değer olarak kontrol etmeyi seçer. Bu genellikle, harici çağırıcılara maruz kalan yöntemlerde ilk olarak boş değeri test etmek anlamına gelir. Örneğin, belirli bir sınıfta geliştirici, publicyöntemlere iletilen tüm nesnelerde null olup olmadığını kontrol etmeyi seçebilir , ancak privateyöntemlerde null olup olmadığını kontrol edemez .

Aşağıdaki kod, bu yöntem girişinde null denetimi gösterir. Gösterge yöntemi olarak, iki yöntemi çağıran ve her yönteme tek bir boş bağımsız değişken geçiren tek bir yöntem içerir. Boş bağımsız değişken alan yöntemlerden biri, önce boş için bağımsız değişkeni kontrol eder, ancak diğeri yalnızca geçirilen parametrenin boş olmadığını varsayar.

 /** * Append predefined text String to the provided StringBuilder. * * @param builder The StringBuilder that will have text appended to it; should * be non-null. * @throws IllegalArgumentException Thrown if the provided StringBuilder is * null. */ private void appendPredefinedTextToProvidedBuilderCheckForNull( final StringBuilder builder) { if (builder == null) { throw new IllegalArgumentException( "The provided StringBuilder was null; non-null value must be provided."); } builder.append("Thanks for supplying a StringBuilder."); } /** * Append predefined text String to the provided StringBuilder. * * @param builder The StringBuilder that will have text appended to it; should * be non-null. */ private void appendPredefinedTextToProvidedBuilderNoCheckForNull( final StringBuilder builder) { builder.append("Thanks for supplying a StringBuilder."); } /** * Demonstrate effect of checking parameters for null before trying to use * passed-in parameters that are potentially null. */ public void demonstrateCheckingArgumentsForNull() { final String causeStr = "provide null to method as argument."; logHeader("DEMONSTRATING CHECKING METHOD PARAMETERS FOR NULL", System.out); try { appendPredefinedTextToProvidedBuilderNoCheckForNull(null); } catch (NullPointerException nullPointer) { log(causeStr, nullPointer, System.out); } try { appendPredefinedTextToProvidedBuilderCheckForNull(null); } catch (IllegalArgumentException illegalArgument) { log(causeStr, illegalArgument, System.out); } } 

Yukarıdaki kod yürütüldüğünde, çıktı aşağıda gösterildiği gibi görünür.

ERROR: NullPointerException encountered while trying to provide null to method as argument. java.lang.NullPointerException ERROR: IllegalArgumentException encountered while trying to provide null to method as argument. java.lang.IllegalArgumentException: The provided StringBuilder was null; non-null value must be provided. 

Her iki durumda da bir hata mesajı kaydedildi. Ancak, bir boş değerin kontrol edildiği durum, null ile ne zaman karşılaşıldığına dair ek bağlam bilgisi içeren, reklamı yapılan bir IllegalArgumentException attı. Alternatif olarak, bu boş parametre çeşitli şekillerde ele alınabilirdi. Boş parametrenin işlenmediği durumda, bunun nasıl ele alınacağına dair hiçbir seçenek yoktu. Çoğu kişi NullPolinterException, bir boş değer açıkça keşfedildiğinde ek bağlam bilgisiyle birlikte bir atmayı tercih eder ( bkz.Etkili Java'nın İkinci Sürümünde Öğe # 60 veya Birinci Sürümde Öğe # 42), ancak IllegalArgumentExceptionbunun açıkça bir null olan yöntem argümanı çünkü istisnanın bağlam ayrıntılarını eklediğini ve konuya "null" eklemek kolay olduğunu düşünüyorum.

Null için yöntem argümanlarını kontrol etme tekniği, tüm nesneleri null için kontrol etmenin daha genel tekniğinin bir alt kümesidir. Bununla birlikte, yukarıda özetlendiği gibi, herkese açık yöntemlere yönelik argümanlar, genellikle bir uygulamada en az güvenilenlerdir ve bu nedenle, bunları kontrol etmek, ortalama nesnenin boş olup olmadığını kontrol etmekten daha önemli olabilir.

Null için yöntem parametrelerinin kontrol edilmesi, aynı zamanda, Etkili Java'nın İkinci Sürümü Madde # 38'de ( Birinci Baskıda Madde 23) tartışıldığı gibi, genel geçerlilik için yöntem parametrelerini kontrol etmenin daha genel bir uygulamasının bir alt kümesidir .

Nesnelerden Çok Temel Öğeleri Düşünün

intBasitçe a olasılığından kaçınmak için karşılık gelen nesne referans türü (Tamsayı gibi ) üzerinden ilkel bir veri türü (örneğin ) seçmenin iyi bir fikir olduğunu düşünmüyorum NullPointerException, ancak aşağıdaki avantajlardan birinin ilkel türler, NullPointerExceptions'ye yol açmamasıdır . Ancak, ilkellerin geçerliliği hala kontrol edilmelidir (bir ay negatif bir tam sayı olamaz) ve bu nedenle bu fayda küçük olabilir. Öte yandan, ilkel öğeler Java Koleksiyonlarında kullanılamaz ve bir değerin null olarak ayarlanmasının istendiği zamanlar vardır.

En önemli şey, ilkellerin, referans türlerinin ve otomatik kutulamanın birleşimi konusunda çok dikkatli olmaktır. Etkili Java'da (İkinci Baskı, Madde # 49) NullPointerException, ilkel ve referans türlerin dikkatsizce karıştırılmasıyla ilgili fırlatma dahil tehlikelere ilişkin bir uyarı vardır.

Zincirleme Yöntem Çağrılarını Dikkatlice Düşünün

A'yı NullPointerExceptionbulmak çok kolay olabilir çünkü nerede oluştuğunu bir satır numarası belirtecektir. Örneğin, bir yığın izleme aşağıda gösterildiği gibi görünebilir:

java.lang.NullPointerException at dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace(AvoidingNullPointerExamples.java:222) at dustin.examples.AvoidingNullPointerExamples.main(AvoidingNullPointerExamples.java:247) 

Yığın izleme NullPointerException, satırının 222. satırında çalıştırılan kodun bir sonucu olarak atıldığını açıkça gösterir AvoidingNullPointerExamples.java. Sağlanan satır numarasıyla bile, aynı satırda erişilen yöntemlere veya alanlara sahip birden fazla nesne varsa, hangi nesnenin boş olduğunu daraltmak yine de zor olabilir.

Örneğin, benzer bir ifadenin aynı kod satırına atfedilen someObject.getObjectA().getObjectB().getObjectC().toString();dört olası çağrısı vardır NullPointerException. Bir hata ayıklayıcı kullanmak bu konuda yardımcı olabilir, ancak her aramanın ayrı bir hat üzerinde gerçekleştirilmesi için yukarıdaki kodu basitçe ayırmanın tercih edilebileceği durumlar olabilir. Bu, bir yığın izlemede bulunan satır numarasının sorunun tam olarak hangisi olduğunu kolayca belirtmesine olanak tanır. Dahası, her nesnenin null olup olmadığını açıkça kontrol etmeyi kolaylaştırır. Bununla birlikte, olumsuz tarafı, kodun kırılması, kod satırını artırır (bazılarına göre pozitiftir!) Ve her zaman arzu edilmeyebilir, özellikle de söz konusu yöntemlerin hiçbirinin boş olmayacağından eminseniz.

NullPointerExceptions'ı Daha Bilgilendirici Yapın

Yukarıdaki tavsiyede, uyarı esas olarak yöntem çağrısı zincirlemesinin dikkatlice kullanılmasının düşünülmesiydi çünkü yığın izlemede satır numarasına sahip olmanın, NullPointerExceptionaksi halde olabileceğinden daha az yararlı olmasını sağladı . Bununla birlikte, satır numarası yalnızca kod hata ayıklama bayrağı açıkken derlendiğinde yığın izlemede gösterilir. Hata ayıklama olmadan derlenmişse, yığın izleme aşağıda gösterilene benzer:

java.lang.NullPointerException at dustin.examples.AvoidingNullPointerExamples.demonstrateNullPointerExceptionStackTrace(Unknown Source) at dustin.examples.AvoidingNullPointerExamples.main(Unknown Source) 

Yukarıdaki çıktının gösterdiği gibi, bir yöntem adı vardır, ancak NullPointerException. Bu, kodda istisnaya neyin yol açtığını hemen tespit etmeyi daha zor hale getirir. Bunu ele almanın bir yolu, atılan herhangi bir durumda bağlam bilgisi sağlamaktır NullPointerException. Bu fikir, daha önce bir NullPointerExceptionyakalandığında ve ek bağlam bilgisiyle birlikte bir IllegalArgumentException. Bununla birlikte, istisna, NullPointerExceptionbağlam bilgisiyle başka bir istisna olarak yeniden atılsa bile, yine de faydalıdır. Bağlam bilgisi, kodda hata ayıklayan kişinin sorunun gerçek nedenini daha hızlı tanımlamasına yardımcı olur.

Aşağıdaki örnek bu prensibi göstermektedir.

final Calendar nullCalendar = null; try { final Date date = nullCalendar.getTime(); } catch (NullPointerException nullPointer) { log("NullPointerException with useful data", nullPointer, System.out); } try { if (nullCalendar == null) { throw new NullPointerException("Could not extract Date from provided Calendar"); } final Date date = nullCalendar.getTime(); } catch (NullPointerException nullPointer) { log("NullPointerException with useful data", nullPointer, System.out); } 

Yukarıdaki kodun çalıştırılmasından elde edilen çıktı aşağıdaki gibidir.

ERROR: NullPointerException encountered while trying to NullPointerException with useful data java.lang.NullPointerException ERROR: NullPointerException encountered while trying to NullPointerException with useful data java.lang.NullPointerException: Could not extract Date from provided Calendar