Oracle JDK의 많은 Java 8 메소드 가 주어진 객체 (인수)가 인 경우 Objects.requireNonNull()
내부적으로 throw되는 많은 Java 8 메소드를 언급했습니다 .NullPointerException
null
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
그러나 객체가 역 참조 NullPointerException
되면 어쨌든 발생합니다 null
. 그렇다면 왜이 여분의 null 검사를 수행하고 던져야
NullPointerException
합니까?
명백한 대답 (또는 이점)은 코드를 더 읽기 쉽게 만들고 동의한다는 것입니다. Objects.requireNonNull()
방법의 시작 부분에 사용하는 다른 이유를 알고 싶습니다
.
답변
그렇게하면 명시 적으로 만들 수 있기 때문입니다. 처럼:
public class Foo {
private final Bar bar;
public Foo(Bar bar) {
Objects.requireNonNull(bar, "bar must not be null");
this.bar = bar;
}
또는 더 짧게 :
this.bar = Objects.requireNonNull(bar, "bar must not be null");
이제 당신 은 알고있다 :
- Foo 객체를 사용하여 성공적으로 생성 한 시점
new()
- 다음 의 바 필드가됩니다 보장 null 이외.
오늘 Foo 객체를 만들고 내일은 해당 필드를 사용하고 던지는 메소드를 호출합니다. 어제 생성자에게 전달 된 참조가 왜 null인지 내일 알 수 없습니다 .
다시 말해,이 방법을 명시 적으로 사용하여 들어오는 참조 를 확인 하면 예외가 발생하는 시점을 제어 할 수 있습니다 . 그리고 대부분의 경우 가능한 빨리 실패 하고 싶습니다 !
주요 장점은 다음과 같습니다.
- 말했듯이 통제 된 행동
- 더 쉬운 디버깅-객체 생성 컨텍스트에서 포기하기 때문입니다. 당신이 당신의 로그 / 추적이 잘못 된 것을 말해 줄 수있는 특정 시점에!
- 그리고 위에서 보여 지듯이 :이 아이디어의 진정한 힘은 최종 필드 와 함께 전개 됩니다. 이제 클래스의 다른 코드 는
bar
null이 아니라고 가정 할 수 있으므로if (bar == null)
다른 곳에서 검사 할 필요가 없습니다 !
답변
빠른 실패
가능한 빨리 코드가 충돌해야합니다. 작업의 절반을 수행하지 말고 널과 충돌을 역 참조하면 일부 작업의 절반 만 남겨두고 시스템이 유효하지 않은 상태가됩니다.
이것을 일반적으로 “fail early”또는 “fail-fast”라고 합니다.
답변
그러나 null 객체가 역 참조되면 NullPointerException이 발생합니다. 그렇다면 왜이 여분의 null 검사를 수행하고 NullPointerException을 발생시켜야합니까?
그것은 당신이 문제를 감지 의미 즉시 및 신뢰성 .
치다:
- 코드가 이미 몇 가지 부작용을 수행 한 후에는 메소드의 뒷부분에서 참조를 사용할 수 없습니다
- 이 방법에서는 참조가 전혀 역 참조되지 않을 수 있습니다
- 완전히 다른 코드로 전달 될 수 있습니다 (즉, 코드 공간에서 원인과 오류가 멀리 떨어져 있음)
- 훨씬 나중에 사용할 수 있습니다 (예 : 원인과 오류는 시간이 많이 남음)
- null 참조 가 유효하지만 의도하지 않은 효과가있는 곳에서 사용될 수 있습니다.
.NET은 분리하여 더 나은 만드는 NullReferenceException
에서 ( “당신이 널 (null) 값을 역 참조”) ArgumentNullException
( “당신은 인수로 null을 통과하지 않아야 – 그것은 위해이었다 이 매개 변수) 나 자바 같은 일을했다 좋겠지 만, 심지어와. 단지는 NullPointerException
, 그것은 여전히 많은 오류가 그것을 검출 할 수있는 가장 빠른 시점에서 발생 된 경우 수정 코드에 쉽게.
답변
requireNonNull()
메소드에서 첫 번째 명령문으로 사용하면 지금 예외의 원인을 식별 / 빠르게 식별 할 수 있습니다.
스택 트레이스는 호출자가 요구 사항 / 계약을 존중하지 않기 때문에 메소드가 입력되는 즉시 예외가 발생했음을 명확하게 나타냅니다 .
지나가는 null
다른 방법으로 목적하는 것은 할 수 참으로 한 번에 예외하지만 문제의 원인이 더 많은 예외가의 특정 호출에 던져 질 것이다로 이해하는 복잡 할 수있다 도발 null
훨씬 더 할 수있다 개체를.
여기에 우리가 일반적으로 실패를 선호해야하는 이유를 보여주는 구체적이고 실제적인 예가 Object.requireNonNull()
있습니다 null
.
에 포함 된 단어 를 나타내는 Dictionary
a LookupService
와 a 를 구성 하는 클래스를 가정하십시오 . 이러한 필드는 설계되지 않았 으며 이들 중 하나는 List
String
null
Dictionary
생성자에 .
이제 메소드 엔트리 (여기서 생성자) Dictionary
를 null
점검 하지 않고 “나쁜”구현을 가정 해 보자 .
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);
}
}
이제 매개 변수에 Dictionary
대한 null
참조를 사용하여 생성자를 호출 해 보겠습니다 words
.
Dictionary dictionary = new Dictionary(null);
// exception thrown lately : only in the next statement
boolean isFirstElement = dictionary.isFirstElement("anyThing");
JVM은 다음 명령문에서 NPE를 발생시킵니다.
return words.get(0).contains(userData);
스레드 "main"의 예외 java.lang.NullPointerException LookupService.isFirstElement (LookupService.java:5)에서 Dictionary.isFirstElement (Dictionary.java:15)에서 Dictionary.main (Dictionary.java:22)에서
예외는 LookupService
클래스 에서 발생 하며 그 기원은 훨씬 빠릅니다 ( Dictionary
생성자). 전체 이슈 분석이 훨씬 덜 명확 해집니다.
입니까 words
null
? 입니까 words.get(0) null
? 둘 다? 왜 하나, 다른 하나 또는 둘 다 null
인가? Dictionary
(생성자? 호출 된 메소드?) 의 코딩 오류 입니까? 의 코딩 오류 LookupService
입니까? (생성자? 호출 된 메소드?)?
마지막으로, 오류 원점을 찾기 위해 더 많은 코드를 검사해야하며, 더 복잡한 클래스에서는 디버거를 사용하여 발생한 상황을 더 쉽게 이해할 수도 있습니다.
그러나 왜 간단한 것 (널 체크가 없음)이 복잡한 문제가됩니까?
하위 구성 요소의 특정 구성 요소 누출에서 식별 가능한 초기 버그 / 부족을 허용했기 때문입니다.
상상 해봐LookupService
로컬 서비스가 아니라 원격 서비스 또는 디버깅 정보가 거의없는 타사 라이브러리 였거나 null
감지 되기 전에 2 층이 아니라 4 또는 5 층의 객체 호출이 있다고 상상해보십시오 . 문제는 여전히 분석하기가 더 복잡합니다.
따라서 선호하는 방법은 다음과 같습니다.
public Dictionary(List<String> words) {
this.words = Objects.requireNonNull(words);
this.lookupService = new LookupService(words);
}
이런 식으로 두통이 발생하지 않습니다.이를 수신하자마자 예외가 발생합니다.
// exception thrown early : in the constructor
Dictionary dictionary = new Dictionary(null);
// we never arrive here
boolean isFirstElement = dictionary.isFirstElement("anyThing");
스레드 "main"의 예외 java.lang.NullPointerException java.util.Objects.requireNonNull (Objects.java:203)에서 com.Dictionary. (Dictionary.java:15)에서 com.Dictionary.main (Dictionary.java:24)에서
여기서는 생성자와 관련된 문제를 설명했지만 메서드 호출은 null이 아닌 동일한 검사 제약 조건을 가질 수 있습니다.
답변
참고로, 이것은 Object#requireNotNull
일부 jre 클래스 자체에서 java-9 이전에 약간 다르게 구현 되기 전에 빠르게 실패 합니다. 사례를 가정하십시오.
Consumer<String> consumer = System.out::println;
Java-8에서는 다음과 같이 컴파일됩니다 (관련 부분 만)
getstatic Field java/lang/System.out
invokevirtual java/lang/Object.getClass
기본적으로 다음과 같은 작업은 yourReference.getClass
-Refercence가 실패하면 실패합니다 null
.
동일한 코드가 다음과 같이 컴파일되는 jdk-9에서 상황이 변경되었습니다.
getstatic Field java/lang/System.out
invokestatic java/util/Objects.requireNonNull
아니면 기본적으로 Objects.requireNotNull (yourReference)
답변
기본적인 사용법은 NullPointerException
바로 확인하고 던지는 것 입니다.
동일한 요구 사항을 충족시키는 더 좋은 대안 (바로 가기)은 lombok의 @NonNull 주석입니다.
답변
null
나중에 있는 개체의 멤버에 액세스하면 Null 포인터 예외가 발생 합니다. Objects.requireNonNull()
즉시 값을 확인하고 진행하지 않고 즉시 예외를 발생시킵니다.