왜 Objects.requireNonNull ()을 사용해야합니까?


214

Oracle JDK의 많은 Java 8 메소드 가 주어진 객체 (인수)가 인 경우 Objects.requireNonNull()내부적으로 throw되는 많은 Java 8 메소드를 언급했습니다 .NullPointerExceptionnull

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

그러나 객체가 역 참조 NullPointerException되면 어쨌든 발생합니다 null. 그렇다면 왜이 여분의 null 검사를 수행하고 던져야 NullPointerException합니까?

명백한 대답 (또는 이점)은 코드를 더 읽기 쉽게 만들고 동의한다는 것입니다. Objects.requireNonNull()방법의 시작 부분에 사용하는 다른 이유를 알고 싶습니다 .




1
인수 검사에 대한 이러한 접근 방식을 사용하면 단위 테스트를 작성할 때 속임수를 쓸 수 있습니다. Spring에도 이와 같은 유틸리티가 있습니다 ( docs.spring.io/spring/docs/current/javadoc-api/org/… 참조 ). "if"가 있고 단위 테스트 적용 범위를 높이려면 조건이 충족 될 때와 조건이 충족되지 않을 때 두 가지를 모두 포함해야합니다. 을 사용 Objects.requireNonNull하면 코드에 분기가 없으므로 단위 테스트의 단일 패스로 100 % 적용 범위를 얻을 수 있습니다 :-)
xorcus

답변:


214

그렇게하면 명시 적으로 만들 수 있기 때문입니다. 처럼:

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인지 내일 알 수 없습니다 .

다시 말해,이 방법을 명시 적으로 사용하여 들어오는 참조 를 확인 하면 예외가 발생하는 시점을 제어 할 수 있습니다 . 그리고 대부분의 경우 가능한 빨리 실패 하고 싶습니다 !

주요 장점은 다음과 같습니다.

  • 말했듯이 통제 된 행동
  • 더 쉬운 디버깅-객체 생성 컨텍스트에서 포기하기 때문입니다. 당신이 당신의 로그 / 추적이 잘못 된 것을 말해 줄 수있는 특정 시점에!
  • 그리고 위에서 보여 지듯이 :이 아이디어의 진정한 힘은 최종 필드 와 함께 전개 됩니다. 이제 클래스의 다른 코드barnull이 아니라고 가정 할 수 있으므로 if (bar == null)다른 곳에서 검사 할 필요가 없습니다 !

23
당신은 서면으로 코드 컴팩트를 만들 수 있습니다this.bar = Objects.requireNonNull(bar, "bar must not be null");
키릴 Rakhman에게

3
@KirillRakhman 교육 GhostCat 내가 몰랐던 멋진 일-> GhostCat 공짜 추첨에서 티켓을 획득하는 것.
GhostCat

1
여기에 의견 # 1이의 실제 이점이라고 생각 Objects.requireNonNull(bar, "bar must not be null");합니다. 이것에 감사합니다.
Kawu

1
어느 것이 처음이었고, 왜 "언어"사람들이 어떤 도서관 의 저자들을 따라야합니까 ?! 구아바가 Java lang 동작을 따르지 않는 이유는 무엇입니까?!
GhostCat

1
this.bar = Objects.requireNonNull(bar, "bar must not be null");둘 개 이상의 변수가 동일하게 설정되는 경우, 생성자에 확인되지만, 다른 방법에 잠재적으로 위험하다. 예 : talkwards.com/2018/11/03/…
Erk

100

빠른 실패

가능한 빨리 코드가 충돌해야합니다. 작업의 절반을 수행하지 말고 널과 충돌을 역 참조하면 일부 작업의 절반 만 남겨두고 시스템이 유효하지 않은 상태가됩니다.

이것을 일반적으로 "fail early"또는 "fail-fast"라고 합니다.


40

그러나 null 객체가 역 참조되면 NullPointerException이 발생합니다. 그렇다면 왜이 여분의 null 검사를 수행하고 NullPointerException을 발생시켜야합니까?

그것은 당신이 문제를 감지 의미 즉시신뢰성 .

치다:

  • 코드가 이미 몇 가지 부작용을 수행 한 후에는 메소드의 뒷부분에서 참조를 사용할 수 없습니다
  • 이 방법에서는 참조가 전혀 역 참조되지 않을 수 있습니다
    • 완전히 다른 코드로 전달 될 수 있습니다 (즉, 코드 공간에서 원인과 오류가 멀리 떨어져 있음)
    • 훨씬 나중에 사용할 수 있습니다 (예 : 원인과 오류는 시간이 많이 남음)
  • null 참조 유효하지만 의도하지 않은 효과가있는 곳에서 사용될 수 있습니다.

.NET은 분리하여 더 나은 만드는 NullReferenceException에서 ( "당신이 널 (null) 값을 역 참조") ArgumentNullException( "당신은 인수로 null을 통과하지 않아야 - 그것은 위해이었다 매개 변수) 나 자바 같은 일을했다 좋겠지 만, 심지어와. 단지는 NullPointerException, 그것은 여전히 많은 오류가 그것을 검출 할 수있는 가장 빠른 시점에서 발생 된 경우 수정 코드에 쉽게.


12

requireNonNull()메소드에서 첫 번째 명령문으로 사용하면 지금 예외의 원인을 식별 / 빠르게 식별 할 수 있습니다.
스택 트레이스는 호출자가 요구 사항 / 계약을 존중하지 않기 때문에 메소드가 입력되는 즉시 예외가 발생했음을 명확하게 나타냅니다 . 지나가는 null다른 방법으로 목적하는 것은 할 수 참으로 한 번에 예외하지만 문제의 원인이 더 많은 예외가의 특정 호출에 던져 질 것이다로 이해하는 복잡 할 수있다 도발 null훨씬 더 할 수있다 개체를.


여기에 우리가 일반적으로 실패를 선호해야하는 이유를 보여주는 구체적이고 실제적인 예가 Object.requireNonNull()있습니다 null.

에 포함 된 단어 를 나타내는 Dictionarya LookupService와 a 를 구성 하는 클래스를 가정하십시오 . 이러한 필드는 설계되지 않았 으며 이들 중 하나는 ListStringnullDictionary 생성자에 .

이제 메소드 엔트리 (여기서 생성자) Dictionarynull점검 하지 않고 "나쁜"구현을 가정 해 보자 .

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이 아닌 동일한 검사 제약 조건을 가질 수 있습니다.


와! 좋은 설명입니다.
Jonathas Nascimento

2

참고로, 이것은 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)


1

기본적인 사용법은 NullPointerException바로 확인하고 던지는 것 입니다.

동일한 요구 사항을 충족시키는 더 좋은 대안 (바로 가기)은 lombok의 @NonNull 주석입니다.


1
주석은 Objects 메서드를 대체하지 않으며 함께 작동합니다. @ NonNull x = mightBeNull이라고 말할 수 없습니다. @ NonNull x = Objects.requireNonNull (mightBeNull, "생각 불가!");
Bill K

@BillK 죄송합니다, 난 당신을 얻을 수 없습니다
Supun Wijerathne

Nonnull 주석은 requireNonNull과 함께 작동한다고 말하고 있지만 대안은 아니지만 매우 잘 작동합니다.
Bill K

실패의 빠른 본질을 구현하는 것, 대안의 권리? 나는 과제에 대한 대안이 아니라고 동의한다.
Supun Wijerathne 1

requireNonNull ()을 @ Nullable에서 @ NonNull로 "변환"이라고 부릅니다. 주석을 사용하지 않으면 그 방법은 그다지 흥미롭지 않습니다 (보호하는 코드와 마찬가지로 NPE를 던지기 때문에) 의도가 명확하게 표시되지만.
Bill K

1

null나중에 있는 개체의 멤버에 액세스하면 Null 포인터 예외가 발생 합니다. Objects.requireNonNull()즉시 값을 확인하고 진행하지 않고 즉시 예외를 발생시킵니다.


0

복사 생성자 및 입력 매개 변수가 객체 인 DI와 같은 다른 경우에 사용해야한다고 생각합니다. 매개 변수가 null인지 확인해야합니다. 이러한 상황에서는이 정적 방법을 편리하게 사용할 수 있습니다.


0

Nullability 체킹 (예 : uber / NullAway ) 을 구현하는 컴파일러 확장의 맥락 Objects.requireNonNull에서, 코드의 특정 지점에서 널 입력 가능 필드가 널 (null)이 아님을 알고있는 경우에는 약간만 사용해야합니다.

이런 식으로 두 가지 주요 사용법이 있습니다.

  • 확인

    • 이미 다른 답변으로 덮여 있습니다.
    • 오버 헤드 및 잠재적 NPE로 런타임 확인
  • Nullability 표시 (@Nullable에서 @Nonnull로 변경)

    • 컴파일 타임 검사에 유리한 런타임 검사 사용 최소화
    • 주석이 올바른 경우에만 작동합니다 (컴파일러에서 시행)

Nullability 마킹 사용 예 :

@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

모든 정답 외에도 :

반응성 스트림에서 사용합니다. 일반적으로 결과 NullpointerExceptions는 스트림에서 발생에 따라 다른 예외로 래핑됩니다. 따라서 나중에 오류 처리 방법을 쉽게 결정할 수 있습니다.

예를 들어 :

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

어디 parseAndValidate을 wrapps NullPointerException에서 requireNonNullA의 ParsingException.

이제 재시도시기를 결정할 수 있습니다.

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

확인하지 않으면 save메소드 에서 NullPointerException 이 발생하여 재 시도가 끝납니다. 가치가 있더라도 장기 구독을 상상해보십시오.

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

이제 RetryExhaustException구독이 중단됩니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.