GN⁺: 널 제한 및 널 허용 타입
(bugs.openjdk.org)Null-Restricted and Nullable Types (Preview)
요약
Java 타입에 null을 허용하거나 거부하는 nullness 마커를 지원하는 미리보기 언어 기능.
목표
- Java의 참조 타입을 개선하여 프로그래머가 null 참조를 기대하는지 여부를 표현할 수 있게 함
- 다른 nullness 속성을 가진 타입 간의 변환을 지원하며, 잘못 처리된 null 값에 대한 경고 제공
- 기존 Java 코드와 호환되며, 새로운 기능을 점진적으로 도입할 수 있도록 지원
- null을 거부하는 타입의 변수를 처음 읽기 전에 초기화하도록 보장
- 별도로 컴파일된 클래스에서도 null을 거부하는 타입을 런타임에 강제
- 런타임 최적화를 위해 필요한 메타데이터와 무결성 보장 제공
비목표
- 기존 코드를 자동으로 재해석하지 않음
- 모든 null 값을 명시적으로 처리하도록 요구하지 않음
- 기본 타입에 대한 변경 사항을 포함하지 않음
- 표준 라이브러리에 언어 개선 사항을 적용하지 않음
동기
- Java 프로그램에서 String 타입 변수는 String 객체 참조나 null 값을 가질 수 있음
- 변수에 null을 허용할지 여부를 명확히 표현할 수 없어 혼란과 버그 발생
- 개발자가 null 값을 지원하지 않거나 기대하는지를 타입의 일부로 명시할 수 있는 도구 제공 필요
설명
Nullness 속성과 마커
- 참조 타입은 nullness를 선택적으로 표현할 수 있음
-
Foo!
는 null을 포함하지 않는 null-restricted 타입 -
Foo?
는 null을 포함하는 nullable 타입 - 기본적으로
Foo
의 nullness는 지정되지 않음
필드와 배열 초기화
- null-restricted 필드나 배열은 사용 전에 반드시 초기화되어야 함
- 초기화되지 않은 null-restricted 필드를 읽으려 하면 예외 발생
표현식 nullness와 변환
- Java 컴파일러는 모든 표현식의 nullness를 결정
- nullness 변환을 통해 다른 nullness를 가진 표현식을 다룰 수 있음
- narrowing nullness 변환은 런타임에 NullPointerException을 발생시킬 수 있음
런타임 null 검사
- narrowing nullness 변환이 발생하면 NullPointerException 발생
타입 변수의 nullness
- 타입 변수도 nullness를 표현할 수 있음
- null-restricted와 nullable 타입 변수는 제네릭 코드 내에서 특정 nullness를 주장
타입 인수와 경계
- 타입 인수는 nullness를 표현할 수 있으며, 이는 API의 nullness에 영향을 미침
- nullness가 일치하지 않는 타입 인수는 경고를 발생시킬 수 있음
메서드 오버라이딩과 타입 인수 추론
- nullness는 메서드 시그니처가 동일한지 여부를 결정할 때 무시됨
- 오버라이딩 메서드의 반환 타입은 nullness 변환을 통해 변환 가능
컴파일러 경고
- null-restricted 타입을 만들면 새로운 컴파일 타임 오류가 발생할 수 있음
- narrowing nullness 변환, null-hostile 연산에서 ? 타입 사용 등은 경고를 발생시킬 수 있음
컴파일 및 클래스 파일 표현
- 대부분의 null 마커는 클래스 파일에서 제거됨
- 새로운 NullRestricted 속성은 필드가 null 값을 허용하지 않음을 나타냄
코어 리플렉션
-
Foo!.class
나Foo?.class
리터럴은 존재하지 않음 - 새로운 RuntimeType API는 런타임에 null-restricted 변형을 설명
보충 변경 사항
- 전통적인 직렬화는 null-restricted 필드 및 배열과 호환되지 않음
- javadoc은 nullness 마커를 포함
- java.lang.reflect.Type 및 javax.lang.model API는 nullness를 인코딩
대안
- Java 생태계의 다양한 개발 도구는 자체적으로 null 추적을 구현
- 다른 프로그래밍 언어는 nullness를 타입 시스템에서 추적
- 런타임 nullness 강제는 명시적 검사나 Objects.requireNonNull 호출로 구현 가능
종속성
- Flexible Constructor Bodies (Second Preview) 필요
- Null-Restricted Value Class Types (Preview)와 JEP 402: Enhanced Primitive Boxing (Preview)와 같은 향후 작업
GN⁺의 정리
- 이 JEP는 Java에서 null 값을 명확히 처리할 수 있는 도구를 제공하여 코드의 안정성과 가독성을 높임
- null-restricted와 nullable 타입을 도입하여 null 참조로 인한 버그를 줄일 수 있음
- 기존 코드와 호환되며 점진적으로 도입할 수 있어 개발자에게 유연성을 제공
- 다른 언어와의 비교에서 Java의 null 처리 능력을 강화할 수 있음
- 비슷한 기능을 제공하는 도구로는 Kotlin의 null-safety 기능이 있음
Hacker News 의견
-
C#와 Kotlin의 null 처리 방식 비교
- C#은 프로젝트에서 nullability를 활성화하면 모든 변수가 기본적으로 non-null로 선언됨
- Kotlin도 비슷하지만, backwards compatibility가 필요하지 않음
- Kotlin은
lateinit var
선언을 통해 초기화되지 않은 non-nullable 변수를 나중에 초기화할 수 있는 기능을 제공함
-
새로운 제안에 대한 의견
- 기존 변수들이 nullable, explicitly nullable, explicitly non-nullable로 구분됨
- C#의 접근 방식이 더 나아 보임, 특히 레거시 코드베이스에서 nullability 문제를 해결할 필요가 없음
- 그러나 C# 접근 방식은 코드베이스에서 nullability 문제를 즉시 강조함
-
nullness-narrowing 자동 변환에 대한 우려
- 자동 변환이 잘못된 느낌을 줌
- 컴파일러 오류를 발생시켜야 할 경우가 있음
- 명시적인 변환이 더 안전함
-
모든 변수를 기본적으로 non-null로 표시하는 방법 필요성
- 패키지나 파일 수준에서 모든 변수를 non-null로 표시할 수 있는 방법이 필요함
- 그렇지 않으면 T! 구문을 거의 모든 변수에 사용하게 되어 코드가 복잡해짐
-
Java에서 언어 수준의 명시적 optionality 기능 필요성
- Kotlin과 Typescript에서의 경험을 바탕으로 언어 수준의 지원이 더 나음
- Java의 NullAway 같은 도구는 번거로움
-
표준 라이브러리에 언어 개선 사항을 적용하지 않는 결정에 대한 비판
- PHP 사용 경험에서 표준 라이브러리와의 상호작용이 번거로움
- 표준 라이브러리에 이러한 표현력을 추가하는 것이 필요함
-
컴파일 타임 경고를 오류로 승격하는 쉬운 방법 필요성
- Java는 주로 정적 타입 언어이므로 동적 동작을 도입하는 것은 좋지 않음
-
기본적으로 non-nullable, immutable, 좁은 범위로 설정해야 하는 필요성
- 새로운 디자인 결정이 즉각적인 편의성을 위해 안전한 경로를 포기하는 경우가 많음
- 많은 언어와 기술에서 이러한 문제로 인해 많은 오류가 발생함
-
Hack 언어에서의 null 처리 경험
- Hack의 타입 시스템에서 nullness가 중요한 부분임
- nullable 배열에 대한 질문
- Java에서는 모든 레거시 코드가 nullability를 가정하므로 문제가 됨
-
Java SDK에 이 기능을 적용할 수 있는지에 대한 질문
- 레거시 코드에 대해 어떻게 적용할 수 있을지에 대한 의문
-
관련 링크 제공