[java] 자연스럽게 NullPointerException을 발생시키지 않고 명시 적으로 던지는 이유는 무엇입니까?

JDK 소스 코드를 읽을 때 작성자가 매개 변수가 null 인 경우 매개 변수를 확인한 다음 새 NullPointerException ()을 수동으로 throw하는 것이 일반적입니다. 왜 그렇게합니까? 메소드를 호출 할 때 새로운 NullPointerException ()이 발생하기 때문에 그렇게 할 필요가 없다고 생각합니다. (예를 들어 HashMap의 소스 코드는 다음과 같습니다.)

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}



답변

기억해야 할 여러 가지 이유가 있습니다.

빠른 실패 : 실패 할 경우 나중에보다는 빨리 실패하는 것이 가장 좋습니다. 이를 통해 문제를 소스에 더 가깝게 파악하여 쉽게 식별하고 복구 할 수 있습니다. 또한 실패 할 수있는 코드에서 CPU주기 낭비를 방지합니다.

의도 : 예외를 명시 적으로 throw하면 오류가 의도적으로 있으며 작성자가 결과를 알고 있음을 유지 관리자에게 명확하게 알 수 있습니다.

일관성 : 오류가 자연스럽게 발생하도록 허용 된 경우 모든 시나리오에서 발생하지 않을 수 있습니다. 예를 들어 매핑이 없으면 remappingFunction사용되지 않으며 예외가 발생하지 않습니다. 입력을 미리 검증하면보다 결정적인 동작과보다 명확한 문서화가 가능 합니다.

안정성 : 코드는 시간이 지남에 따라 발전합니다. 예외가 발생하는 코드는 자연스럽게 약간의 리팩토링 후에는 그렇게하지 않거나 다른 상황에서 그렇게 할 수 있습니다. 명시 적으로 던지면 행동이 실수로 변경 될 가능성이 줄어 듭니다.


답변

명확성, 일관성 및 불필요한 불필요한 작업이 수행되는 것을 방지하기위한 것입니다.

메소드 맨 위에 가드 절이 없으면 어떻게 될지 고려하십시오. 그것은 NPE가 던져지기 전에 항상 전화를 걸었 hash(key)getNode(hash, key)때조차도 null지나갔습니다 remappingFunction.

더 나쁜 것은 if조건이 조건을 가지면 브랜치를 false취하는데 else전혀 사용 remappingFunction하지 않기 때문에 a null가 전달 될 때 메소드가 항상 NPE를 던지는 것은 아닙니다 . 지도의 상태에 따라 다릅니다.

두 시나리오 모두 나쁘다. 이 메서드에 null유효한 값이 아닌 경우 remappingFunction호출시 객체의 내부 상태에 관계없이 일관되게 예외가 발생해야하며, 그냥 던질 것이라는 점에서 의미없는 불필요한 작업을 수행하지 않고 수행해야합니다. 마지막으로, 소스 코드를 검토하는 사람이라면 누구나 그렇게 할 수 있다는 것을 쉽게 알 수 있도록 가드를 미리 확보하는 것이 깨끗하고 명확한 코드의 원칙입니다.

현재 모든 코드 분기에서 예외가 발생하더라도 향후 코드 개정으로 변경 될 수 있습니다. 처음에 검사를 수행하면 확실히 수행됩니다.


답변

@shmosel의 탁월한 답변에 나열된 이유 외에도 …

성능 : 일부 JVM의 경우 JVM이 아닌 NPE를 명시 적으로 처리하면 성능상의 이점이있을 수 있습니다.

Java 인터프리터 및 JIT 컴파일러가 널 포인터의 역 참조를 감지하는 전략에 따라 다릅니다. 한 가지 전략은 null을 테스트하지 않고 대신 명령이 주소 0에 액세스하려고 할 때 발생하는 SIGSEGV를 트랩하는 것입니다. 이는 참조가 항상 유효한 경우 가장 빠른 방법이지만 NPE의 경우 비용많이 듭니다 .

null코드에서 명시 적으로 테스트 하면 NPE가 자주 발생하는 시나리오에서 SIGSEGV 성능이 저하되는 것을 피할 수 있습니다.

(이것이 현대 JVM에서 가치있는 마이크로 최적화가 될지는 의문이지만 과거에는 가능했을 것입니다.)


호환성 : 예외에 메시지가없는 이유는 JVM 자체에서 발생하는 NPE와의 호환성 때문입니다. 호환되는 Java 구현에서 JVM에 의해 발생 된 NPE에는 null메시지가 있습니다. (Android Java는 다릅니다.)


답변

다른 사람들이 지적한 것 외에도 여기에서 컨벤션의 역할에 주목할 가치가 있습니다. 예를 들어 C #에서는 이와 같은 경우 예외를 명시 적으로 발생시키는 것과 동일한 규칙이 있지만 구체적으로 ArgumentNullException는 좀 더 구체적입니다. (는 C # 규칙은 것입니다 NullReferenceException 항상 아주 간단하게, 그것은 안 – 어떤 종류의 버그를 나타냅니다 , 부여, 생산 코드에서 발생 ArgumentNullException일반적으로도 않지만, 그렇게하지 “의 라인을 따라 더 많은 버그가 수 라이브러리를 올바르게 사용하는 방법을 이해하십시오 ( “종류의 버그).

따라서 기본적으로 C # NullReferenceException에서는 프로그램이 실제로 사용하려고 시도했지만 ArgumentNullException값이 잘못되었음을 인식하여 사용하려고 시도하지 않아도됩니다. 실제 상황에 따라 의미가 다를 수 있습니다 (환경에 따라 다름). 문제 ArgumentNullException의 방법에 아직 부작용이 없었기 때문입니다 (방법의 전제 조건이 실패 했으므로).

덧붙여, 넌 모금 무엇인가를하는 경우 ArgumentNullExceptionIllegalArgumentException, 체크를하는 점의 부품이 있음을 : 당신은 당신이 “보통”했던 것보다 다른 예외를 원하는 얻을.

어느 쪽이든, 예외를 명시 적으로 제기하면 메소드의 사전 조건 및 예상 인수에 대해 명시 적으로 작성하는 것이 좋습니다. 따라서 코드를보다 쉽게 ​​읽고, 사용하고, 유지할 수 있습니다. 명시 적으로 확인 null하지 않은 경우 아무도 null인수를 전달하지 않을 것이라고 생각했기 때문에 어쨌든 모르겠습니다. 어쨌든 예외를 throw하기 위해 계산하고 있거나 확인하는 것을 잊어 버렸습니다.


답변

따라서 나중에지도를 사용할 때 오류가 발생하지 않고 오류가 발생하자마자 예외가 발생하며 그 이유를 이해할 수 없습니다.


답변

겉보기에 불규칙한 오류 조건이 명확한 계약 위반으로 바뀝니다.이 기능에는 올바르게 작동하기위한 몇 가지 전제 조건이 있으므로 미리 확인하여 충족시켜야합니다.

그 결과 computeIfPresent()예외가 발생했을 때 디버깅 할 필요가 없습니다 . 전제 조건 검사에서 예외가 발생하면 잘못된 인수로 함수를 호출 한 것입니다. 검사가없는 경우 computeIfPresent()자체에 예외가 발생하는 일부 버그가있을 가능성을 배제해야합니다 .

분명히, 제네릭을 던지는 것은 NullPointerException계약 위반 자체를 나타내지 않기 때문에 정말 나쁜 선택입니다. IllegalArgumentException더 나은 선택이 될 것입니다.


참고 사항 :
Java가 이것을 허용하는지 알지 못하지만 (의심 할 것입니다) C / C ++ 프로그래머 assert()는이 경우 디버깅을 사용하는 것이 훨씬 낫습니다. 조건이 거짓으로 평가됩니다. 그래서, 당신이 달렸다면

void MyClass_foo(MyClass* me, int (*someFunction)(int)) {
    assert(me);
    assert(someFunction);

    ...
}

디버거와 어떤 NULL인수에 전달 된 것이 있으면 프로그램은 인수가 무엇 인지 알려주는 줄에서 바로 멈추고 NULL여가 시간에 전체 호출 스택의 모든 지역 변수를 검사 할 수 있습니다.


답변

그것은 가능하기 때문입니다 하지 자연적으로 발생 할 수 있습니다. 다음과 같은 코드를 보자 :

bool isUserAMoron(User user) {
    Connection c = UnstableDatabase.getConnection();
    if (user.name == "Moron") {
      // In this case we don't need to connect to DB
      return true;
    } else {
      return c.makeMoronishCheck(user.id);
    }
}

(물론이 샘플에는 코드 품질에 대한 수많은 문제가 있습니다. 완벽한 샘플을 상상하기 위해 게으른 죄송합니다)

상황 c실제로 사용되지 않는 은 가능 NullPointerException하더라도 던져지지 않습니다 c == null.

더 복잡한 상황에서는 그러한 경우를 찾기가 쉽지 않습니다. 이것이 일반적인 점검 if (c == null) throw new NullPointerException()이 더 나은 이유 입니다.