NullPointerException은 Java 프로그램의 가장 일반적인 실패 원인
중 하나입니다.
가장 단순한 경우에 컴파일러는 다음과 같은 코드가 보이면 사용자에게 직접 경고할 수 있습니다.
Object o = null;
String s = o.toString();
프로그램의 전체 또는 일부 경로에서 참조 취소된 변수에 널 값/널이 아닌 값이 지정되는지 확인하려면 예외 처리와 분기/루프와 함께 정교한 플로우 분석이 필요합니다.
내재된 복잡도 때문에 플로우 분석은 작은 단위로 수행하는 것이 가장 좋습니다. 한 번에 하나의 메소드를 분석하는 것이 바람직한 도구 성능을 유지할 수 있습니다. 반면, 전체 시스템 분석은 Eclipse Java 컴파일러 범위를 벗어납니다. 장점은, 분석이 빠르고 컴파일러에서 입력할 때 바로 경고할 수 있도록 증분식으로 처리된다는 점입니다. 단점은, 분석에서 매개변수 및 리턴값으로 메소드 간 전달되는 값(널 또는 널이 아님)을 "확인"할 수 없다는 점입니다.
다음은 널 어노테이션을 재현한 것입니다.
메소드 매개변수를 @NonNull로
지정하면 이 위치에서 널 값을 원하지
않음을 컴파일러에 지시할 수 있습니다.
String capitalize(@NonNull String in) {
return in.toUpperCase(); // no null check required
}
void caller(String s) {
if (s != null)
System.out.println(capitalize(s)); // preceding null check is required
}
규약에 의한 디자인에서 이는 두 가지 측면을 지닙니다.
capitalize의 구현자는
인수 in이 널이 아니며, 따라서 널 확인 없이 참조 취소해도
문제가 없다는 점을 보장합니다.
메소드 리턴값에서 상황은 대칭입니다.
@NonNull String getString(String maybeString) {
if (maybeString != null)
return maybeString; // the above null check is required
else
return "<n/a>";
}
void caller(String s) {
System.out.println(getString(s).toUpperCase()); // no null check required
}
Eclipse Java 컴파일러는 강화된 널 분석(기본적으로 사용되지 않음)을 위해 세 개의 서로 다른 어노테이션 유형을 사용하도록 구성될 수 있습니다.
@NonNull: 널이 올바른 값이 아님@Nullable: 널 값이 허용되며 예상되어야 함@NonNullByDefault: 널 어노테이션이 없는
메소드 서명의 유형은 널이 아닌 항목으로 간주됩니다.어노테이션 @NonNull 및
@Nullable은 다음 위치에서 지원됩니다.
@NonNullByDefault는 다음에 제공됨
package-info.java 파일을
통해) - 패키지에서 모든 유형에 영향을 줌
이 어노테이션의 실제 규정된 이름은
구성
가능하지만 기본적으로 위에 지정된 항목이 사용됩니다(패키지
org.eclipse.jdt.annotation에서).
써드파티 널 어노테이션을 사용하는 경우, 하나 이상의 @Target 메타 어노테이션을 사용하여 적절히 정의되도록 하십시오. 그렇지 않으면 컴파일러가 선언 어노테이션(Java 5)과 유형 어노테이션(Java 8)을 구별할 수 없습니다.
기본 널 어노테이션이 있는 JAR 파일은 Eclipse와 함께 제공되며 eclipse/plugins/org.eclipse.jdt.annotation_*.jar에 있습니다. 이 JAR 파일은 컴파일 시간에는 필드 경로에 있어야 하지만 런타임에서는 그렇지 않아도 되므로 이 JAR 파일을 컴파일된 코드의 사용자에게 제공하지 않아도 됩니다.
Eclipse Luna부터는 이 jar가 두 개의 버전으로 존재합니다. 하나는 Java 7 이하(버전 1.1.x)에 사용할 선언 어노테이션이 있는 버전이고 다른 하나는 Java 8(버전 2.0.x)에 사용할 널 유형 어노테이션이 있는 버전입니다.
일반 Java 프로젝트의 경우, @NonNull, @Nullable 또는 @NonNullByDefault에 대한 결정되지 않은 참조에 대해 적절한 JAR 버전을 빌드 경로에 추가하는 빠른 수정사항도 있습니다.

OSGi 번들/플러그인의 경우, 다음 항목 중 하나를 MANIFEST.MF에 추가하십시오.
Require-Bundle: ..., org.eclipse.jdt.annotation;bundle-version="[1.1.0,2.0.0)";resolution:=optional
Require-Bundle: ..., org.eclipse.jdt.annotation;bundle-version="[2.0.0,3.0.0)";resolution:=optional
호환성에 대한 해당 섹션에서 토론도 참조하십시오.
이제 널 어노테이션이 Java 프로그램에 정보를 더 추가함을 명확히 해야 합니다. 그러면 컴파일러에서 이를 사용해 더 나은 경고를 제공할 수 있습니다. 하지만 이 어노테이션으로 정확히 무엇을 말하려는 것입니까? 프로그래밍 관점에서 널 어노테이션으로 표현할 수 있는 세 가지 레벨이 있습니다.
(1)에서 추가로 읽지 않고 널 어노테이션 사용을 바로 시작할 수 있지만 종종 약간의 힌트만 기대할 수 있습니다. 다른 레벨은 추가 설명이 필요합니다.
규약에 의한 디자인 방식으로 API 스펙에 대한 널 어노테이션을
사용하는 경우 모든 API 메소드의 서명이 완전히 어노테이션됨을
의미합니다. 즉, int와 같은 기본 유형을 제외하고
각 매개변수와 각 메소드 리턴 유형은 @NonNull 또는
@Nullable로 표시되어야 합니다.
이는 많은 널 어노테이션을 삽입함을 의미하므로,
잘 디자인된 코드(특히 API 메소드)에서
@NonNull은
@Nullable보다 더 자주 사용된다는 점을
알아두면 좋습니다. 따라서 어노테이션 수는
패키지 레벨에서 @NonNullByDefault 어노테이션을 사용해
@NonNull을 기본으로 선언하면 줄일 수 있습니다.
널 어노테이션 생략과 @Nullable 사이의 중요한 차이에
주의하십시오.
이 어노테이션은 명시적으로 널이 유효하며 예상된다는 점을 명시합니다.
반대로, 어노테이션이 없으면 의도를 알 수 없다는 의미입니다.
이는 때때로 호출자와 피호출자 모두 반복해서 널을 검사하고
둘 다 반대편이 검사를 수행했다고 잘못 가정하는 예전 상황을
가리킵니다.
이는 NullPointerExceptions이 발생하는 원인입니다.
어노테이션이 없으면 컴파일러는 특정 조언을 제공하지 않지만,
@Nullable 어노테이션이 있으면 확인되지 않은 모든 참조 해제가
플래그로 지정됩니다.
이러한 기본 정보를 통해 모든 매개변수 어노테이션을 사전 조건으로 맵핑하고 메소드의 사후 조건으로 리턴 어노테이션을 해석할 수 있습니다.
객체 지향 프로그래밍에서 규약에 의한 디자인이라는 개념은
한 가지 차원을 더 처리해야 합니다. 바로
하위 유형과 대체가 그것입니다. (다음에 "대체"라는 용어는
상위 유형의 다른 메소드를 대체
또는 구현하는 Java 6: 메소드에서
@Override 어노테이션의 관점에서 사용됩니다.) 메소드를 호출하는 클라이언트는 다음과 같습니다.
@NonNull String checkedString(@Nullable String in)
이 메소드의 모든 구현이 규약을 이행한다고 가정할 수 있어야 합니다.
따라서 메소드 선언이 인터페이스 I1에
있으면 I1을 구현하는 모든 클래스
Cn이 호환되지 않는 구현을 제공한다는 점을
제외해야 합니다. 특히 Cn에서 매개변수를
@NonNull로 선언한 구현으로 이 메소드를 대체하려는 경우
올바르지 않습니다.
이를 허용하려는 경우 I1에 대해 프로그래밍된
클라이언트 모듈은 널을 인수로 올바르게 전달할 수 있지만
구현에서는 널이 아닌 값을 가정합니다. 메소드 구현 내 확인되지 않은
참조 해제는 허용되지만 런타임에 손상될 수 있습니다.
따라서 @Nullable 매개변수 스펙은
모든 대체를 강요하여
예상되는 올바른 값으로 널을 승인합니다.
반대로 @NonNull 리턴 스펙은
모든 대체를 강요하여 널이 리턴되지 않도록 보장합니다.
따라서 컴파일러는 대체가 상위 유형에 없는
@NonNull 매개변수 어노테이션 또는
@Nullable 리턴 어노테이션을 추가하지 않는지 확인해야 합니다.
@Nullable 매개변수 어노테이션 또는
@NonNull 리턴 어노테이션 추가와 같은
역순 재정의는 올바릅니다. (이를 메소드의 "개선"사항으로 간주할 수도 있습니다. 이 경우 더 많은 값을 승인하고 보다 구체적인 리턴값을 생성합니다.)
하위 클래스가 대체 메소드에서 널 어노테이션을 반복하도록 강제 실행함으로써 상속 계층을 검색하지 않고도 각 메소드의 널 계약을 이해할 수 있습니다. 그러나 상속 계층이 여러 출처의 코드로 혼합된 경우 모든 클래스에 널 어노테이션을 한 번에 추가하지 못할 수 있습니다. 이러한 경우 컴파일러는 널 어노테이션이 누락된 메소드를 대체된 메소드의 어노테이션이 상속된 것처럼 취급하도록 지시될 수 있습니다. 이는 널 어노테이션 상속 컴파일러 옵션을 사용하여 사용되도록 설정됩니다. 한 메소드가 서로 다른 널 계약을 사용하는 두 개의 메소드를 대체할 수 있습니다. 또한 상속된 널 어노테이션과 충돌되는 메소드에서 널 조건 기본값을 적용할 수도 있습니다. 이러한 경우는 오류로 플래그 지정되므로 대체 메소드는 명시 널 어노테이션을 사용하여 충돌을 해결해야 합니다.
@NonNull 매개변수를 지정되지 않도록 완화?
널 어노테이션의 상속을
사용하도록 설정하지 않을 경우, 유형 이론 관점에서는 안전하지만 여전히 문제점을 나타낼 수 있는 특정 상황이
있습니다(매개변수를 @NonNull로 선언하는 상위 메소드와 해당 매개변수를 (명시 널 어노테이션이나
적용 가능한 @NonNullByDefault에 의해) 제한하지 않는 대체 메소드를 고려해 볼 때).
이는 상위 선언을 참조하는 클라이언트가 null을 사용하지 않도록 강제 실행되므로 안전한 반면, 대체 구현은
이 특정 메소드의 스펙 부족으로 인해 이 보증을 간단히 적용할 수 없습니다.
이는 상위 유형의 선언이 모든 대체에도 적용해야 한다고 의도될 수 있으므로 잘못 이해될 수 있습니다.
이러한 이유 때문에 컴파일러는 대체 메소드에서 '@NonNull' 매개변수가 어노테이션되지 않음 옵션을 제공합니다.
null이 허용해야 하는 경우 @Nullable 어노테이션을 추가하여 상위 메소드의
@NonNull을 대체하는 것이 좋습니다.
이전 고려사항에서는 어노테이션이 있는 코드가 "기존"
유형(즉, 어노테이션이 없음)(타사 라이브러리에서 유래할 수
있으며, 이 경우 변경 불가능)의 하위 유형으로 작성된 경우 난이도가 추가됩니다.
마지막 섹션을 자세히 검토하면 "기존" 메소드가 @NonNull
매개변수를 포함하는 메소드로 대체됨을 허용하지 않는다는 점을
확인할 수 있습니다. (상위 유형을 사용하는 클라이언트는 @NonNull
강요를 "확인"하지 않기 때문입니다.)
이 상황에서 널 어노테이션을 강제로 생략합니다. (향후 라이브러리에 어노테이션 추가를 지원하는 계획이 있지만, 해당 기능이 사용될 수 있어도 아직 정확한 계획은 없습니다.)
"기존" 유형의 하위 유형이 @NonNullByDefault가
지정된 패키지에 상주하는 경우 상황은 복잡해집니다. 이제
어노테이션이 없는 상위 유형을 포함하는 유형을 대체 메소드의 모든 매개변수를
@Nullable로 표시해야 합니다.
이는 이 위치에서 금지된 @NonNull 매개변수와 같이 해석되므로
매개변수 어노테이션의 생략은 허용되지 않습니다.
이 때문에 Eclipse Java 컴파일러는 널 조건의 기본값 취소를
지원하지 않습니다. @NonNullByDefault(false)를
통해 메소드 또는 유형에 어노테이션을 작성하면
해당되는 기본값이 이 요소에서 취소되며 어노테이션이 없는 매개변수가
다시 지정되지 않은 항목으로 해석됩니다. 이제 하위 유형은 원하지 않는
@Nullable 어노테이션을 추가하지 않고도 다시 올바릅니다.
class LegacyClass {
String enhance (String in) { // clients are not forced to pass nonnull.
return in.toUpperCase();
}
}
@NonNullByDefault
class MyClass extends LegacyClass {
// ... methods with @NonNull default ...
@Override
@NonNullByDefault(false)
String enhance(String in) { // would not be valid if @NonNullByDefault were effective here
return super.enhance(in);
}
}
널 어노테이션은 메소드 시그니처에 적용될 때 가장 잘 작동합니다(로컬 변수는 일반적으로 이를 필요로 하지 않지만 어노테이션이 있는 코드와 "레거시" 코드 사이를 연결하는 경우 널 어노테이션에 영향을 줄 수 있습니다). 이러한 사용법에서 널 어노테이션은 글로벌 데이터 플로우에 대한 명령문을 달성하기 위해 내부 프로시저 분석의 청크를 연결합니다. Eclipse Kepler부터 널 어노테이션을 필드에도 적용할 수 있지만 이 때의 상황은 다소 다릅니다.
@NonNull이 표시된 필드를 고려하십시오. 이는 분명히 필드에 대한 지정이 널이 아닌 값을 제공할 것을 요구합니다.
또한 컴파일러는 널이 아닌 필드는 결코 초기화되지 않은 상태(여전히 null 값이 있음)로 액세스될 수 없음을 확인할 수 있어야 합니다.
모든 생성자가 이 규칙(비슷하게 정적 필드가 초기자(initializer)를 가지고 있어야 함)을 준수하는 것이 확인되면 프로그램은 필드를
참조 해제할 때 NullPointerException이 발생하지 않는 안전한 상태가 됩니다.
@Nullable로 표시된 필드를 고려할 때 상황이 더욱 미묘해집니다.
이러한 필드는 항상 위험한 필드로 고려되어야 하므로 널 입력 가능 필드에 대해 작업할 경우
항상 로컬 변수에 값을 지정한 후 작업할 것을 권장합니다.
로컬 변수를 사용하는 경우 플로우 분석을 통해 참조 해제가 널 검사로 충분히 보호되는지 여부를 확인할 수 있습니다.
널 입력 가능 필드에 대한 작업 시 이 일반 규칙을 따르면 문제가 발생하지 않습니다.
코드가 널 입력 가능 필드의 값을 직접 참조 해제하는 경우 더 많은 사항이 관련될 수 있습니다. 문제는 코드가 참조 해제를 수행하기 전에 널 검사를 수행할 경우 다음 중 하나에 의해 널 검사가 쉽게 무효화될 수 있다는 점입니다.
스레드 동기화(컴파일러의 기능이 아님)를 분석하지 않으면 널 입력 가능 필드에 대한 널 검사가 후속 참조 해제에 대해 100% 안전을 부여하지 않는다는 것을 쉽게 알 수 있습니다. 따라서 널 입력 가능 필드에 동시 액세스가 가능한 경우, 필드 값을 절대 직접 참조 해제해서는 안되며 항상 로컬 변수를 대신 사용해야 합니다. 동시성이 포함되지 않은 경우에도 컴파일러의 일반적인 기능을 초과하는 전체 분석을 수행해야 하는 문제가 남아 있습니다.
컴파일러가 별명 지정, 부작용 및 동시성의 영향을 완전히 분석할 수 없는 경우 Eclipse 컴파일러는 필드에 대해 (초기화 관련 외에 다른) 플로우 분석을 수행하지 않습니다. 많은 개발자가 이 제한사항을 너무 제한적이라고 여기고 자신의 코드가 실제적으로 안전하다고 느끼는 로컬 변수를 사용하게 하므로 잠정적인 타협책으로 새 옵션이 소개되었습니다.
컴파일러는 일부 구문 분석을 수행하도록 구성될 수 있습니다. 이는 다음과 같이 가장 명백한 패턴을 발견합니다.
@Nullable Object f;
void printChecked() {
if (this.f != null)
System.out.println(this.f.toString());
}
지정된 옵션으로 위의 코드를 사용하는 경우 컴파일러는 이를 플래그 지정하지 않습니다. 어떤 방법으로든 이 구문 분석이 "똑똑"하지 않은지 확인하는 것이 중요합니다. 검사와 참조 해제 사이에 코드가 나타나는 경우, 컴파일러는 그 중간 코드가 일부 기준에 따라 무해할 수 있는지 확인조차 하지 않고 이전 널 검사 정보를 "잊어버립니다". 그러므로 널이 발생하지 않아야 하지만 컴파일러가 널 입력 가능 필드의 참조 해제를 안전하지 않음으로 플래그 지정할 때마다 위에 표시된 인식된 패턴에 따라 코드를 다시 작성십시오. 또는 더 좋은 방법으로 로컬 변수를 사용하여 구문 분석으로 불가능한 플로우 분석의 복잡한 기능을 모두 처리할 것을 권장합니다.
위에서 간략히 설명한 대로, 규약에 의한 디자인 스타일에서 널 어노테이션을 사용하면 여러 방식으로 Java 코드의 품질을 향상시키는 데 도움이 됩니다. 메소드의 인터페이스에서 명시적으로 되어 널값을 허용하는 매개변수/리턴과 허용하지 않는 매개변수/리턴이 구분됩니다. 이는 컴파일러에서 확인 가능한 방식으로 개발자와 관련성 깊은 디자인 의사결정을 포착합니다.
또한 이 인터페이스 스펙에 기반하여 프로시저 내 플로우 분석이 사용 가능한 정보를 선택하고 보다 정확한 오류/경고를 제공할 수 있습니다. 어노테이션이 없으면 메소드 내부 또는 외부로 전달되는 값에 알 수 없는 널 조건이 포함되므로 널 분석이 해당 사용을 인식하지 못합니다. API 레벨의 널 어노테이션에서 대부분 값의 널 조건은 실제로 알려져 있으며, 극소수의 NPE만 컴파일러에서 확인되지 않습니다. 그러나 지정되지 않은 값이 분석되는 일부의 허술한 부분이 존재하며, 이로 인해 NPE가 런타임에 발생할 수 있는지 완전한 설명이 불가능해집니다.
널 어노테이션에 대한 지원은 향후 확장과 호환 가능한 방식으로 디자인되었습니다. 이 확장은 Java 8에 도입된 유형 어노테이션(JSR 308)으로서 Java 언어의 일부가 되었습니다. JDT는 널 유형 어노테이션에 대한 새 개념을 활용하도록 지원합니다.
어노테이션 기반 널 분석의 시맨틱 세부사항은 컴파일러가 검사하는 규칙과 규칙 위반 시 발행하는 메시지를 설명하여 여기에 표시합니다.
해당하는 환경 설정 페이지에서 컴파일러가 검사하는 개별 규칙은 다음 표제 아래에 그룹화됩니다.
스펙 위반으로, 널 어노테이션이 실제 구현에서 위반된 클레임을 제기하는
상황을 처리합니다. 일반 상황은 값(로컬, 인수, 메소드 리턴)을
@NonNull로 지정하여 발생합니다. 반면, 실제로 구현은
널 입력 가능 값을 제공합니다. 다음은 정적으로 값을 널로 평가한다고
알려졌거나 @Nullable 어노테이션으로 선언된 경우
널 입력 가능으로 간주되는 표현식입니다.
두 번째로 이 그룹은 위에
언급한 대로 메소드 대체에 대한 규칙도 포함합니다.
다음은 상위 메소드에서 클레임(예: 널이 올바른 인수임)을 설정하는 반면,
대체는 널이 올바른 인수가 아니라고 가정하여 이 클레임을
피하고자 합니다.
언급한 대로 어노테이션이 없는 조건에서
@NonNull로 인수를 특화하는 것은 스펙 위반입니다.
클라이언트를 바인드해야 하는 규약을 도입하지만(널을 전달하지 않음)
상위 유형을 사용하는 클라이언트는 이 규약을 보지 못하므로
예상되는 조건도 알지 못하기 때문입니다.
스펙 위반과 관련된 상황의 전체 목록이
여기
제공됩니다.
이 그룹의 오류는 무시할 수 없다는
점을 명심해야 합니다.
그렇지 않으면 전체 널 분석이 잘못된 가정에 기반하여 수행되기
때문입니다.
특히 컴파일러가
@NonNull 어노테이션의 값을 확인할 때마다
런타임에 널이 발생하지 않는다고 간주합니다.
이는 이 이유가 합리적임을 보장하는 스펙 위반에 대한 규칙입니다.
따라서 이러한 종류의 문제는
오류로 구성하도록 하는 것이 좋습니다.
또한 이 규칙 그룹은 널 스펙에 대한 준수를 감시합니다.
그러나 여기에서는 @Nullable로
선언되지 않은 값(널값도 아님)을 처리하지만
프로시저 내 플로우 분석이 일부 실행 경로에서 널이 발생할 수 있음을
유추하는 값은 다루지 않습니다.
이 상황은 어노테이션이 없는 로컬 변수에서 플로우 분석을 통해 널이 가능한지 여부를 컴파일러가 유추한다는 사실로부터 발생합니다. 이 분석이 정확하다고 가정하고 문제점이 확인되면 이 문제점은 널 스펙의 직접적인 위반과 심각도가 동일합니다. 따라서 다시 이 문제점을 오류로 구성한 상태로 두고 이 메시지를 무시하지 않도록 하는 것이 좋습니다.
이 문제점에 대한 별도의 그룹을 작성하는 경우 두 가지 목적을 지원합니다. 하나는, 플로우 분석의 도움으로 지정된 문제점이 발생한 경우를 문서화하는 것이고, 다른 하나는, 구현의 버그로 인해 이 플로우 분석에 결함이 발생할 수 있다는 점을 설명하는 것입니다. 수신확인된 구현 버그의 경우 이러한 종류의 오류 메시지를 억제하도록 예외 상황에서 확인될 수 있습니다.
정적 분석 특성 때문에 플로우 분석이 실행 경로 및 값의 특정 조합이 불가능하다는 점을 확인하는 데 실패할 수 있습니다. 예제로 변수 상관을 고려합니다.
String flatten(String[] inputs1, String[] inputs2) {
StringBuffer sb1 = null, sb2 = null;
int len = Math.min(inputs1.length, inputs2.length);
for (int i = 0; i < len; i++) {
if (sb1 == null) {
sb1 = new StringBuffer();
sb2 = new StringBuffer();
}
sb1.append(inputs1[i]);
sb2.append(inputs2[i]); // warning here
}
if (sb1 != null) return sb1.append(sb2).toString();
return "";
}
컴파일러는 다음의 호출에서 잠재적 널 포인터 액세스를 보고합니다.
sb2.append(..).
리더가 사람인 경우 sb1 및
sb2는 실제로 두 변수 모두가 널이거나 둘 다 널이 아닌 방식으로
상관되므로 실제 위험이 없음을 확인할 수 있습니다.
문제가 되는 행에서 sb1은 널이 아니므로
sb2도 널이 아닙니다. 이러한 상관 분석이
Eclipse Java 컴파일러의 용량을 넘어서는 이유를 자세히
분석하지 않고, 이 분석이 전체 정리 증명을 활용하지 않으므로,
추가로 가능한 분석이 잘못된 알람으로 식별될 수 있는 일부
문제점을 보고할 수 있다는 점을 명심하십시오.
플로우 분석을 활용하려면 컴파일러에 약간의 도움을 제공하는 것이 좋습니다.
그러면 사용자의 결론을 "확인"할 수 있습니다. 이 도움은 각 로컬 변수에 하나씩
if (sb1 == null)을 별개의 if로 분리하는 것만큼 단순합니다.
이는 현재 컴파일러가 발생한 조건을 정확히 보고 이에 따라 코드를
확인한다는 이점에 비하면 매우 작은 희생입니다.
이 주제에 대한 추가 논의는
아래
계속됩니다.
이 문제점 그룹은 다음 유추에 기반합니다. Java 5 일반을 사용하는 문제점에서 Java-5 이전 라이브러리에 대한 호출은 원시 유형을 공개할 수 있습니다. 즉, 구체적 유형 인수를 지정하는 데 실패하는 일반 유형을 적용할 수 있습니다. 일반을 사용하여 이러한 값을 프로그램에 맞추려는 경우 컴파일러는 유형 인수가 코드의 클라이언트 부분에서 예상하는 방식으로 지정되었다고 가정하여 함축적 변환을 추가할 수 있습니다. 컴파일러는 이러한 변환 사용에 대한 경고를 발행하고 라이브러리가 "올바른 작업"을 수행한다는 가정 아래 유형 검사를 진행합니다. 정확히 동일한 방식으로 라이브러리 메소드의 어노테이션이 없는 리턴 유형은 "원시" 또는 "기존" 유형으로 간주될 수 있습니다. 다시 함축적 변환은 낙관적으로 예상 스펙을 가정할 수 있습니다. 다시 경고가 발행되고 분석은 라이브러리가 "올바른 작업"을 수행한다고 계속 가정합니다.
이론적으로 이러한 함축적 변환에 대한 요구는 스펙 위반을 표시합니다. 그러나 이 경우 코드가 예상하는 스펙을 위반하는 타사 코드일 수 있습니다. 또는 스스로 믿고 있는 대로, 타사 코드가 규약을 이행하지만 널 어노테이션을 사용하지 않으므로 이와 같이 선언하는 작업만 실패합니다. 이러한 상황에서 조직적 이유에 대한 문제점을 정확히 수정하지 못할 수 있습니다.
@SuppressWarnings("null")
@NonNull Foo foo = Library.getFoo(); // implicit conversion
foo.bar();
위 코드 스니펫에서는 Library.getFoo()가
널 어노테이션을 지정하지 않고
Foo를 리턴한다고 가정합니다. @NonNull
로컬 변수에 지정하여 어노테이션이 있는 프로그램에 리턴값을
통합할 수 있습니다. 확인되지 않은 변환과 관련된 경고를
트리거합니다.
대응하는 SuppressWarnings("null")을
이 선언에 추가하면 내재된 위험을 확인하고 원하는 대로,
라이브러리가 실제로 작동한다는 점을 확인할 책임을 허용합니다.
플로우 분석이 값이 실제로 널이 아님을 확인할 수 없으면
가장 단순한 전략은 항상
@NonNull로 어노테이션된 새로운 범위의 로컬 변수를 추가하는 것입니다.
그런 다음, 이 로컬에 지정된 값이 런타임에 널이 아님을
확신하면 다음과 같이 헬퍼 메소드를 사용할 수 있습니다.
static @NonNull <T> T assertNonNull(@Nullable T value, @Nullable String msg) {
if (value == null) throw new AssertionError(msg);
return value;
}
@NonNull MyType foo() {
if (isInitialized()) {
MyType couldBeNull = getObjectOrNull();
@NonNull MyType theValue = assertNonNull(couldBeNull,
"value should not be null because application " +
"is fully initialized at this point.");
return theValue;
}
return new MyTypeImpl();
}
위의 assertNonNull()
메소드를 사용하여 이 어설션이 항상 런타임에 보유하는 책임을 허용합니다.
이를 원하지 않는 경우 어노테이션이 있는 로컬 변수는 계속 분석에서 특정 위치로
전달되는 널의 가능성을 확인하게 되는 이유와 경우를 좁히는 데 도움을 줍니다.
JDT 버전 3.8.0 출시 시점에 널 어노테이션 채택에 관한 조언을 수집하는 작업도 계속 진행됩니다. 이러한 이유로 이 정보는 현재 Eclipse wiki에서 유지보수됩니다.