今週初め、JSpecifyプロジェクトのバージョン1.0.0のリリースについて報告した。このリリースは、静的な型の使用におけるnullabilityステータスを示すための型使用アノテーションを提供することに焦点を当てている。
このテーマに関連して、Draft JEP 8303099が最近公開された。このJEPは、Null制限型とNull許容型について議論し、Java言語にオプションのNullness-Markingを導入することを目的としている。
その意図するところは、型の用途にアノテーションだけでなくマーカーを追加して、その用途の許容値セットにNULL
が含まれるかどうかを指定することである。この提案はまだ開発の初期段階であり(公式のJEP番号がまだないなど)、構文が変更される可能性があることを念頭に置いておくことが重要である。とはいえ、今のところKotlinのようなマーカーが使われているので、型Foo
に対して、その型をどのように使うかには3つの可能性がある。
Foo!
はnull-restricted(nullを制限する) - この型の使用で許容される値はnull
を含まない。Foo?
はnullable(null 許容)である - この型の用途で許容される値には明示的にnull
が含まれる。Foo
はnull
が許容されるかどうかを指定しない。
既存のコードがコンパイルされたときに意味が変わってはいけないので、素のFoo
の使用はデフォルトのままである。
現在、提案ではすべての型の使用に注釈を付けることが求められている。つまり、(JSpecifyで可能なように)クラスやモジュール全体をNull制限としてマークする方法はないが、この機能は後に追加されるかもしれない。
この新機能は、(widening変換やunboxing変換に似た)nullness変換を導入している。例えば、次の種類の割り当てはすべて許可される。
Foo!
からFoo?
Foo!
からFoo
へFoo?
からFoo
へFoo
からFoo?
これらは制約の緩和を表すので、例えば、どんなNull制限値もNull許容使用で表現できる。
次のようなNull制限の縮小変換もある。
Foo?
からFoo!
Foo
からFoo!
これらは、例えばFoo?
からFoo!
にNull
をロードしようとすると、実行時エラーを引き起こす可能性がある。一般的な戦略としては、このようなケースをコンパイル時の警告(エラーではない)として扱い、Null制限に違反した場合にNullPointerException
をスローする実行時チェックを含めることである。
これらは基本的に簡単なケースであり、もっと複雑なケースもあり得ることに注意してほしい。例えば、ジェネリックスを扱う場合、コンパイラーは、NULLが宣言された境界と矛盾する型引数のような状況に遭遇する可能性がある。
Nullマーカーを導入することで、コンパイル時の安全性をさらに高めることができ、最初は型のNull性を定義し、次にコンパイル時の警告をなくすようにすることで、徐々にマーカーを採用できる。
InfoQは、このプロジェクトについてさらに詳しい情報を得るために、Kevin Bourrillion氏(Googleのコアライブラリチームの創設者で、現在はOracleのJava言語チームのメンバー)に話を聞いた。
InfoQ: JavaにおけるNullnessの取り組みについての背景を説明してもらえるか?
Bourrillion氏:私はJSpecifyグループを共同で設立し、主要な "デザイナー"("非常に複雑な設計上の決定を、どうにかしてコンセンサスを得るために推進する重責を担う人 "と定義される)の1人だ。現在はオラクルに移ったが、ほぼ同じ形で関わり続けている。
InfoQ:このJEPは、JSpecifyの仕事とどのように重なるのか?
Bourrillion氏:最終的には、NullnessマーカーをサポートしたJavaができるでしょう。JSpecifyは、アノテーションのセマンティックな意味を非常に正確に釘付けにするという大変な作業を行ってきました。つまり、プロジェクトがどのようなアップグレードのタイムテーブルを採用するにしても、JSpecifyから言語レベルのNullnessマーカーに移行するのに非常に有利な状態になるということです。実際、その移行は高度に自動化できるはずです。
InfoQ: Draft JEP 8303099と、はるか昔のJava 5でジェネリックスが追加された方法には、類似点があるように思える。それは正しいのか?これは主にコンパイル時のメカニズムで、バイトコードレベルではほとんど消去されるよね?
Bourrillion氏:そう、そこには有用な類似点があります。私たちは型消去と「ヒープ汚染」の恐怖を残念な譲歩だと考えていますが、それこそがこの機能をとても*採用しやすい*ものにしたのです。そのため、現在では未加工の型を見る必要はほとんどないです(といいのだが!)。私たちの場合、Null汚染は長い間現実の一部となるでしょうが、それでも構わないです!でも大丈夫です!現在はすべてNull汚染です。
そして、ジェネリック型情報のように、nullnessアノテーションはリフレクションによって実行時に利用できますが、実行時の型チェックには関与しないです。誰かが、我々のアノテーションに基づいてNullチェックを注入するバイトコード・インストゥルメンタを構築するかどうかには興味がある。本当に役立つかもしれない理由と、手間をかける価値がないかもしれない理由がわかります。見てみる必要があります。
InfoQ: Null-restrictionもProject Valhallaにとって重要なトピックだよね?このDraft JEPとその分野で進行中の作業との相互作用について何か共有できることはある?
Bourrillion氏:これは本当に同じ質問です。Valhalla氏は、あなたが引用したJEP draftから発展していくだけです。NULLにできないことを知ることは、VMがそのような間接的な要素を排除して最適化するのに役立つでしょう。
InfoQ: 中長期的には、JSpecifyはJavaにおける言語レベルのnullabilityサポートへのオンランプを提供できるはずだ。これは、すでにKotlinのnullabilityサポートとの整合性をとるために使用できるのと似ている。読者にJSpecifyを採用することをどう勧める?
Bourrillion氏:JSpecifyは1.0.0のJSpecify jarだけで、アノテーションの非常に正確な意味を規定する仕様はまだ(わずかで微妙な)変更の可能性があります。そのため、今日、コードベースにアノテーションを付けるために多くの労力を費やしたとしても、あなたのコードが突然正しくコンパイルできなくなることはないですが、ちょっとした仕様の修正の後に、ここにあるいくつかの
@Nullable
を削除やここにあるいくつかの@Nullable
を削除したり、そこにいくつか追加したりする必要があることに気づくかもしれないです。もし、今使っているツールチェーンにNullNull解析機能を導入するのが、既存の警告を一掃する必要があるため、時間がかかりすぎるのであれば、
@SuppressWarnings
を必要な分だけ散布するのは、実は完全に合理的なアプローチです!少し醜いですが、それは時間をかけて少しずつクリーンアップしていけばいいことです。たとえ時間がかかったとしても、重要なのは、*新しい*コードが今日からチェックされ始めるということであり、それはとにかくチェックすべきもっとも重要なコードです。
InfoQ: ありがとう!