효과적인 Java NullPointerException 처리

NullPointerException이 무엇인지 직접 배우는 데 많은 Java 개발 경험이 필요하지 않습니다. 사실, 한 사람이이 문제를 Java 개발자가 저지르는 가장 큰 실수로 강조했습니다. 원치 않는 NullPointerExceptions를 줄이기 위해 String.value (Object) 사용에 대해 이전에 블로그를 작성했습니다. JDK 1.0 이후로 우리와 함께 있었던 이러한 일반적인 유형의 RuntimeException 발생을 줄이거 나 제거하기 위해 사용할 수있는 몇 가지 다른 간단한 기술이 있습니다. 이 블로그 게시물은 이러한 기술 중 가장 인기있는 몇 가지를 수집하고 요약합니다.

사용하기 전에 각 개체의 Null 확인

NullPointerException을 피하는 가장 확실한 방법은 모든 개체 참조를 검사하여 개체의 필드 또는 메서드 중 하나에 액세스하기 전에 null이 아닌지 확인하는 것입니다. 다음 예에서 알 수 있듯이 이것은 매우 간단한 기술입니다.

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); } 

위의 코드에서 사용 된 Deque는 예제를 용이하게하기 위해 의도적으로 null로 초기화됩니다. 첫 번째 try블록 의 코드 는 Deque 메서드에 액세스하기 전에 null을 확인하지 않습니다. 두 번째 try블록 의 코드는 null을 확인하고 null 인 경우 Deque(LinkedList) 구현을 인스턴스화합니다 . 두 예제의 출력은 다음과 같습니다.

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) 

위의 출력에서 ​​ERROR 다음에 나오는 메시지 NullPointerException는 null에 대한 메서드 호출이 시도 될 때 a가 throw 됨을 나타냅니다 Deque. 위의 출력에서 ​​INFO 다음에 나오는 메시지는 Deque먼저 null 을 확인한 다음 null 일 때 새로운 구현을 인스턴스화함으로써 예외가 완전히 방지 되었음을 나타냅니다 .

이 방법은 자주 사용되며 위에 표시된 것처럼 원치 않는 (예기치 않은) NullPointerException인스턴스 를 방지하는 데 매우 유용 할 수 있습니다 . 그러나 비용이없는 것은 아닙니다. 모든 개체를 사용하기 전에 null을 확인하면 코드가 부풀어지고 작성하는 데 지루할 수 있으며 추가 코드의 개발 및 유지 관리에 문제가 발생할 여지가 더 많이 열립니다. 이러한 이유로 내장 널 감지를위한 Java 언어 지원, 초기 코딩 후 널 검사 자동 추가, 널 안전 유형, AOP (Aspect-Oriented Programming)를 사용하여 널 검사를 추가하는 것에 대한 이야기가있었습니다. 바이트 코드 및 기타 널 감지 도구.

Groovy는 잠재적으로 null 인 개체 참조를 처리하기위한 편리한 메커니즘을 이미 제공합니다. Groovy의 안전한 탐색 연산자 ( ?.)는 NullPointerExceptionnull 객체 참조에 액세스 할 때를 던지는 대신 null을 반환합니다 .

모든 개체 참조에 대해 null을 확인하는 것은 지루할 수 있고 코드를 부풀리기 때문에 많은 개발자가 null을 확인할 개체를 신중하게 선택합니다. 이것은 일반적으로 잠재적으로 알려지지 않은 출처의 모든 개체에 대해 null 검사로 이어집니다. 여기서 아이디어는 노출 된 인터페이스에서 개체를 확인한 다음 초기 확인 후 안전하다고 가정 할 수 있다는 것입니다.

이것은 삼항 연산자가 특히 유용 할 수있는 상황입니다. 대신에

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

삼항 연산자는이보다 간결한 구문을 지원합니다.

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

Null에 대한 메서드 인수 확인

방금 설명한 기술은 모든 개체에 사용할 수 있습니다. 이 기술의 설명에서 언급했듯이 많은 개발자는 "신뢰할 수없는"소스에서 올 때만 개체가 null인지 확인하도록 선택합니다. 이것은 종종 외부 호출자에게 노출 된 메서드에서 null 우선 테스트를 의미합니다. 예를 들어, 특정 클래스에서 개발자는 public메서드에 전달 된 모든 개체에서 null을 확인하도록 선택할 수 있지만 메서드에서 null을 확인하지 않을 수 private있습니다.

다음 코드는 메서드 항목에서 null을 확인하는 방법을 보여줍니다. 여기에는 각 메서드에 단일 null 인수를 전달하는 두 메서드를 돌려서 호출하는 데모 메서드로 단일 메서드가 포함됩니다. null 인수를받는 메서드 중 하나는 먼저 해당 인수에서 null을 확인하지만 다른 메서드는 전달 된 매개 변수가 null이 아니라고 가정합니다.

 /** * 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); } } 

위의 코드가 실행되면 다음과 같은 출력이 나타납니다.

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. 

두 경우 모두 오류 메시지가 기록되었습니다. 그러나 null이 확인 된 경우 null이 발생한 시점에 대한 추가 컨텍스트 정보를 포함하는 광고 된 IllegalArgumentException이 발생했습니다. 또는이 널 매개 변수는 다양한 방법으로 처리 될 수 있습니다. null 매개 변수가 처리되지 않은 경우 처리 방법에 대한 옵션이 없었습니다. 많은 사람들이 던져 선호 NullPolinterException널이 명시 적으로 발견 될 때 (항목의 두 번째 버전에서 # 60 참조 추가 컨텍스트 정보와 효과적인 자바 초판 또는 품목 # 42),하지만 난에 대한 약간의 선호가 IllegalArgumentException그것을 명시 적으로 때를 바로 예외가 컨텍스트 세부 정보를 추가하고 주제에 "null"을 포함하기 쉽기 때문에 null 인 메서드 인수입니다.

null에 대한 메서드 인수를 확인하는 기술은 실제로 모든 개체의 null을 확인하는보다 일반적인 기술의 하위 집합입니다. 그러나 위에서 설명한 것처럼 공개적으로 노출 된 메서드에 대한 인수는 응용 프로그램에서 가장 신뢰도가 낮은 경우가 많으므로 평균 개체에서 null을 확인하는 것보다 확인하는 것이 더 중요 할 수 있습니다.

메소드 매개 변수가 null인지 확인하는 것은 효과적인 Java 제 2 판 (초판의 항목 23) 의 항목 # 38에 설명 된대로 일반적인 유효성에 대한 메소드 매개 변수를 확인하는보다 일반적인 관행의 하위 집합이기도합니다 .

객체보다는 프리미티브 고려

int의 가능성을 피하기 위해 해당 객체 참조 유형 (예 : Integer)보다 원시 데이터 유형 (예 :)을 선택하는 것은 좋은 생각이 NullPointerException아니지만 원시 유형은 NullPointerExceptions로 이어지지 않는다는 것 입니다. 그러나 프리미티브는 여전히 유효성을 확인해야하므로 (한 달은 음의 정수가 될 수 없음)이 이점은 적을 수 있습니다. 반면에, Java Collections에서는 프리미티브를 사용할 수 없으며 값을 null로 설정하는 기능을 원할 때가 있습니다.

가장 중요한 것은 프리미티브, 참조 유형 및 오토 박싱의 조합에 대해 매우주의하는 것입니다. 효과적인 자바 (Second Edition, Item # 49) NullPointerException에는 원시 유형과 참조 유형의 부주의 한 혼합과 관련된 던지기를 포함한 위험에 대한 경고가 있습니다.

연결된 메서드 호출을 신중하게 고려

A NullPointerException는 행 번호가 발생한 위치를 나타 내기 때문에 쉽게 찾을 수 있습니다. 예를 들어 스택 추적은 다음과 같을 수 있습니다.

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

스택 추적을 통해의 NullPointerException222 행에서 실행 된 코드의 결과로이 (가) 발생 했음을 분명히 알 수 있습니다 AvoidingNullPointerExamples.java. 제공된 줄 번호를 사용하더라도 동일한 줄에 액세스 한 메서드 또는 필드가있는 여러 개체가있는 경우 어떤 개체가 null인지 좁히는 것이 여전히 어려울 수 있습니다.

For example, a statement like someObject.getObjectA().getObjectB().getObjectC().toString(); has four possible calls that might have thrown the NullPointerException attributed to the same line of code. Using a debugger can help with this, but there may be situations when it is preferable to simply break the above code up so that each call is performed on a separate line. This allows the line number contained in a stack trace to easily indicate which exact call was the problem. Furthermore, it facilitates explicit checking each object for null. However, on the downside, breaking up the code increases the line of code count (to some that's a positive!) and may not always be desirable, especially if one is certain none of the methods in question will ever be null.

Make NullPointerExceptions More Informative

In the above recommendation, the warning was to consider carefully use of method call chaining primarily because it made having the line number in the stack trace for a NullPointerException less helpful than it otherwise might be. However, the line number is only shown in a stack trace when the code was compiled with the debug flag turned on. If it was compiled without debug, the stack trace looks like that shown next:

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

위의 출력에서 ​​알 수 있듯이 메서드 이름은 있지만 NullPointerException. 이로 인해 코드에서 예외가 발생한 내용을 즉시 식별하기가 더 어려워집니다. 이를 해결하는 한 가지 방법은 throw 된 모든 컨텍스트 정보를 제공하는 것 NullPointerException입니다. 이 아이디어는 a NullPointerException가 포착되어 추가 컨텍스트 정보와 함께 IllegalArgumentException. 그러나 예외가 NullPointerException컨텍스트 정보가있는 다른 예외로 다시 발생하더라도 여전히 유용합니다. 컨텍스트 정보는 코드를 디버깅하는 사람이 문제의 실제 원인을 더 빨리 식별하는 데 도움이됩니다.

다음 예는이 원리를 보여줍니다.

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); } 

위 코드를 실행 한 결과는 다음과 같습니다.

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