수색…


비고

null 는, 형태가 참조 형인 필드의 초기화되어 있지 않은 값의 디폴트 값입니다.

NullPointerException (또는 NPE)은 null 개체 참조에 대해 부적절한 작업을 수행하려고 할 때 throw되는 예외입니다. 이러한 작업에는 다음이 포함됩니다.

  • null 타겟 오브젝트로 인스턴스 메소드를 호출하면 (자)
  • null 타겟 오브젝트의 필드에 액세스하는 것,
  • null 배열 객체의 인덱스를 null 거나 그 길이에 액세스하려고 시도하면,
  • synchronized 블록에서 null 객체 참조를 뮤텍스로 사용하면,
  • null 객체 참조를 형 변환한다.
  • null 객체 참조의 언 박싱, 및
  • null 오브젝트 참조를 슬로우합니다.

NPE의 가장 일반적인 근본 원인은 다음과 같습니다.

  • 참조 유형이있는 필드를 초기화하는 것을 잊어 버리는 경우,
  • 참조 유형의 배열 요소를 초기화하는 것을 잊거나
  • 특정 상황에서 null 을 리턴하는 것으로 지정된 특정 API 메소드의 결과를 테스트하지 않습니다.

null 을 리턴하는 일반적으로 사용되는 메소드의 예는 다음과 같습니다.

  • Map API의 get(key) 메소드는 매핑이없는 키를 사용하여 호출하면 null 을 반환합니다.
  • ClassLoaderClass API의 getResource(path)getResourceAsStream(path) 메소드는 자원을 찾을 수없는 경우 null 을 리턴합니다.
  • 가비지 컬렉터가 참조를 클리어 한 경우, Reference API 내의 get() 메소드는 null 를 돌려 null .
  • 존재하지 않는 요청 매개 변수, 세션 또는 세션 속성을 가져 오는 경우 Java EE 서블릿 API의 다양한 getXxxx 메소드가 null 을 리턴합니다.

null 을 명시 적으로 테스트하거나 "Yoda Notation"을 사용하는 것과 같은 원치 않는 NPE를 피하는 전략이 있지만 이러한 전략에는 코드에서 실제로 수정해야하는 문제를 숨기는 바람직하지 않은 결과가 종종 있습니다.

Pitfall - Primitive Wrappers를 불필요하게 사용하면 NullPointerExceptions가 발생할 수 있습니다.

때로는 새로운 Java를 사용하는 프로그래머는 원시 유형과 래퍼를 서로 교환하여 사용합니다. 이로 인해 문제가 발생할 수 있습니다. 다음 예제를 고려하십시오.

public class MyRecord {
    public int a, b;
    public Integer c, d;
}

...
MyRecord record = new MyRecord();
record.a = 1;               // OK
record.b = record.b + 1;    // OK
record.c = 1;               // OK
record.d = record.d + 1;    // throws a NullPointerException

MyRecord 클래스 1 은 기본 초기화를 사용하여 필드의 값을 초기화합니다. 따라서 레코드를 new 만들 때 ab 필드는 0으로 설정되고 cd 필드는 null 로 설정됩니다.

기본 초기화 된 필드를 사용하려고하면 int 필드가 항상 작동하지만 Integer 필드는 일부 경우에만 작동하고 다른 필드에서는 작동하지 않습니다. 특히, ( d 와 함께) 실패 할 경우, 오른쪽에있는 표현식이 null 참조의 언 박싱을 시도하고 이것이 NullPointerException 을 발생시키는 원인입니다.

이것을 살펴볼 수있는 몇 가지 방법이 있습니다.

  • 필드 cd 가 프리미티브 래퍼 일 필요가 있다면, 우리는 디폴트 초기화에 의존해서는 안되며, 아니면 null 테스트해야한다. 이전의 경우 null 상태의 필드에 대한 확실한 의미가 없으면 올바른 접근 방법입니다.

  • 필드가 프리미티브 래퍼 일 필요가없는 경우 원시 래퍼로 만드는 것은 실수입니다. 이 문제 외에도 기본 래퍼는 기본 유형에 비해 추가 오버 헤드가 있습니다.

여기에서의 교훈은 꼭 필요한 경우가 아니면 원시 래퍼 유형을 사용하지 않는 것입니다.


1 -이 클래스는 좋은 코딩 방법의 예가 아닙니다. 예를 들어, 잘 설계된 클래스는 공개 필드를 갖지 않습니다. 그러나 이것이이 예제의 요점은 아닙니다.

Pitfall - null를 사용하여 빈 배열이나 컬렉션을 나타냅니다.

일부 프로그래머는 빈 배열이나 컬렉션을 나타내는 데 null 을 사용하여 공간을 절약하는 것이 좋습니다. 소량의 공간을 절약 할 수는 있지만 사실은 코드가 복잡해지고 부서지기 쉽다는 것입니다. 배열을 요약하는 두 가지 버전의 메소드를 비교하십시오.

첫 번째 버전은 일반적으로 메서드를 코딩하는 방법입니다.

/**
 * Sum the values in an array of integers.
 * @arg values the array to be summed
 * @return the sum
 **/
public int sum(int[] values) {
    int sum = 0;
    for (int value : values) {
        sum += value;
    }
    return sum;
}

두 번째 버전은 빈 배열을 나타 내기 위해 null 을 사용하는 습관이있는 경우 메서드를 코딩하는 방법입니다.

/**
 * Sum the values in an array of integers.
 * @arg values the array to be summed, or null.
 * @return the sum, or zero if the array is null.
 **/
public int sum(int[] values) {
    int sum = 0;
    if (values != null) {
        for (int value : values) {
            sum += value;
        }
    }
    return sum;
}

보시다시피 코드는 좀 더 복잡합니다. 이것은 null 방식으로 사용하기로 결정한 직접적인 원인이됩니다.

이제 null ) 일 수있는이 배열이 많은 장소에서 사용되는지 고려하십시오. 당신이 그것을 사용하는 각 장소에서 당신은 null 을 테스트 할 필요가 있는지 고려할 필요가 있습니다. null 테스트가 필요하다면 NullPointerException 이 발생할 수 있습니다. 따라서이 방법으로 null 을 사용하는 전략은 응용 프로그램을 더욱 허약하게 만듭니다. 즉 프로그래머 오류의 결과에 더 취약합니다.


여기서 교훈은 비어있는 배열과 비어있는 목록을 사용하여 그 뜻을 나타낼 때 사용하는 것입니다.

int[] values = new int[0];                     // always empty
List<Integer> list = new ArrayList();          // initially empty
List<Integer> list = Collections.emptyList();  // always empty

공간 오버 헤드는 작으며, 이것이 가치있는 일이면 최소화하는 다른 방법이 있습니다.

핏방울 - 예기치 않은 "좋음"

StackOverflow에서 Answers에 다음과 같은 코드가 자주 표시됩니다.

public String joinStrings(String a, String b) {
    if (a == null) {
        a = "";
    }
    if (b == null) {
        b = "";
    }
    return a + ": " + b;
}

종종 NullPointerException 을 피하기 위해 이와 같이 null 을 테스트하는 "우수 사례"라는 주장이 수반됩니다.

그것은 최선의 관행인가? 간단히 말해서 : 아니오.

우리의 joinStrings 에서 이것을 수행하는 것이 좋은지를 말하기 전에 몇 가지 기본 가정을 질문해야합니다.

"a"또는 "b"가 null이라는 것은 무엇을 의미합니까?

String 값은 0 개 이상의 문자 일 수 있으므로 이미 빈 문자열을 나타내는 방법이 있습니다. null"" 과 다른 것을 의미합니까? 없으면 빈 문자열을 나타내는 두 가지 방법이있는 것이 문제입니다.

null은 초기화되지 않은 변수에서 왔습니까?

null 는, 초기화되어 있지 않은 필드 또는 초기화되어 있지 않은 배열 요소로부터 취득 할 수 있습니다. 이 값은 의도적으로 또는 실수로 초기화되지 않을 수 있습니다. 우연히 그랬다면 이것은 버그입니다.

null은 "모른다"또는 "누락 된 값"을 나타 냅니까?

때로는 null 이 진정한 의미를 가질 수 있습니다. 예를 들어 변수의 실제 값은 알 수 없거나 사용할 수 없거나 "선택 사항"입니다. Java 8에서는 Optional 클래스가 더 나은 표현 방법을 제공합니다.

이것이 버그 (또는 설계상의 오류)라면 우리는 "좋게"해야합니까?

코드의 해석 중 하나는 빈 문자열을 대신 사용하여 예기치 않은 null 을 "잘 작성하고"있다는 것입니다. 올바른 전략인가? NullPointerException 이 발생하도록하는 것이 더 낫겠습니까? 그리고 스택에서 예외를 잡아서 버그로 기록하십시오.

"잘하는"문제는 문제를 숨기거나 진단하기가 더 쉽다는 것입니다.

이것이 코드 품질에 효과적입니까?

일관성있게 "make good"접근 방식을 사용하면 코드에 많은 "방어적인"null 테스트가 포함됩니다. 이것은 읽는 것을 더 길고 더 어렵게 만들 것입니다. 또한이 모든 테스트와 "잘하는 것"은 응용 프로그램의 성능에 영향을 미칠 수 있습니다.

요약하자면

null 이 의미있는 값이면, null 경우를 테스트하는 것이 올바른 방법입니다. 추론는 경우이다 null 값이 의미가,이 명확하게 동의 어떤 방법의의 javadoc에 문서화되어야한다 null 값을하거나 반환합니다.

그렇지 않으면 예기치 않은 null 을 프로그래밍 오류로 처리하고 개발자가 코드에 문제가 있음을 알 수 있도록 NullPointerException 발생시키는 것이 더 좋습니다.

Pitfall - 예외를 throw하는 대신 null을 반환합니다.

일부 자바 프로그래머는 예외를 던지거나 전파하는 일반적인 혐오감을 가지고 있습니다. 이로 인해 다음과 같은 코드가 생성됩니다.

public Reader getReader(String pathname) {
    try {
        return new BufferedReader(FileReader(pathname));
    } catch (IOException ex) {
        System.out.println("Open failed: " + ex.getMessage());
        return null;
    }

}

그래서 그 문제는 무엇입니까?

문제는 getReaderReader 열 수 없음을 나타 내기 위해 null 을 특수 값으로 반환한다는 것입니다. 이제 반환 값은 그것이 사용되기 전에 null 인지를 검사해야합니다. 테스트가 생략되면 결과는 NullPointerException 됩니다.

여기에는 실제로 세 가지 문제가 있습니다.

  1. IOException 이 너무 빨리 잡혔습니다.
  2. 이 코드의 구조는 리소스를 유출 할 위험이 있음을 의미합니다.
  3. 「실제의」 Reader 를 돌려 줄 수 없었기 때문에, null 가 사용되었습니다.

사실, 예외가 이처럼 일찌감치 잡힐 필요가 있다고 가정하면 null 을 반환하는 몇 가지 대안이 있습니다.

  1. NullReader 클래스를 구현할 수 있습니다. 예를 들어 독자가 이미 "파일 끝"위치에있는 것처럼 API의 작업이 작동하는 경우입니다.
  2. Java 8에서는 getReaderOptional<Reader> 를 반환하는 것으로 선언 할 수 있습니다.

Pitfall - I / O 스트림을 닫을 때 초기화되지 않았는지 확인하지 않음

메모리 누수를 방지하려면 작업이 완료된 입력 스트림이나 출력 스트림을 닫는 것을 잊지 말아야합니다. 이것은 대개 catch 부분이없는 try - catch - finally 문으로 수행됩니다.

void writeNullBytesToAFile(int count, String filename) throws IOException {
    FileOutputStream out = null;
    try {
        out = new FileOutputStream(filename);
        for(; count > 0; count--)
            out.write(0);
    } finally {
        out.close();
    }
}

위의 코드는 무죄로 보일 수 있지만 디버깅을 불가능하게 만들 수있는 결함이 있습니다. 여기서 광고하면 out 초기화된다 ( out = new FileOutputStream(filename) )가 예외를 발생하고 out 것이다 nullout.close() 심한 결과 실행 NullPointerException !

이를 방지하려면 스트림을 닫으려고 시도하기 전에 스트림이 null 이 아닌지 확인하십시오.

void writeNullBytesToAFile(int count, String filename) throws IOException {
    FileOutputStream out = null;
    try {
        out = new FileOutputStream(filename);
        for(; count > 0; count--)
            out.write(0);
    } finally {
        if (out != null)
            out.close();
    }
}

더 나은 접근법은 리소스가 -with-resource를 try 하는 것입니다. 왜냐하면 finally 블록을 필요로하지 않고 NPE를 던질 확률이 0 인 스트림을 자동으로 닫을 것이기 때문입니다.

void writeNullBytesToAFile(int count, String filename) throws IOException {
    try (FileOutputStream out = new FileOutputStream(filename)) {
        for(; count > 0; count--)
            out.write(0);
    }
}

Pitfall - "Yoda 표기법"을 사용하여 NullPointerException을 피하십시오

StackOverflow에 게시 된 많은 예제 코드에는 다음과 같은 스 니펫이 포함되어 있습니다.

if ("A".equals(someString)) {
    // do something
}

someStringnull 경우 가능한 NullPointerException 을 "방지"또는 "회피"합니다. 또한,

    "A".equals(someString)

보다 낫다:

    someString != null && someString.equals("A")

(더 간결하고 상황에 따라 더 효율적일 수 있습니다. 그러나 우리가 아래에서 논증하는 것처럼, 간결함은 부정적 일 수 있습니다.)

그러나 실제 함정은 습관 문제로 NullPointerExceptions 을 피하기 위해 Yoda 테스트 사용하고 있습니다.

"A".equals(someString) 를 쓸 때 someStringnull 되는 경우를 실제로 "좋게 만든다". 그러나 또 다른 예 ( Pitfall - "좋지 않은"예상치 못한 null )가 설명하는 것처럼, "좋은" null 값을 만드는 것은 여러 가지 이유로 해를 끼칠 수 있습니다.

이것은 요다 조건이 "모범 사례"가 아님을 의미합니다 1 . null 이 예상되지 않는 한 NullPointerException 이 발생하도록하여 단위 테스트 실패 (또는 버그 보고서)를 얻을 수 있도록하는 것이 좋습니다. 이를 통해 예기치 않은 / 원치 않는 null 이 나타나는 버그를 찾아서 수정할 수 있습니다.

Yoda 조건은 테스트중인 오브젝트가 null 을 리턴하는 것으로 문서화 된 API에서 왔기 때문에 null예상되는 경우에만 사용해야합니다. 그리고 틀림없이, 테스트를 표현하는 덜 예쁜 방법 중 하나를 사용하는 것이 더 좋을 수 있습니다. 그 방법은 코드를 검토중인 사람에게 null 테스트를 강조하는 데 도움이되기 때문입니다.


1 - 위키 피 디아 (Wikipedia) 에 따르면 "최고의 코딩 사례는 소프트웨어 개발 커뮤니티가 시간이 지남에 따라 학습 한 비공식적 인 규칙으로, 소프트웨어의 품질을 향상시키는 데 도움이 될 수 있습니다." . Yoda 표기법을 사용하는 것은 이것을 달성하지 못합니다. 많은 상황에서 코드가 더 나 빠지게됩니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow