DeprecatedがJDK9で変わるかもしれない件(JEP 277: Enhanced Deprecation)

この記事はJava Advent Calendar 2015の11日目の記事です.
昨日はkokuzawaさんの「JavaFXのUIをJUnit形式でテストする | KATSUMI KOKUZAWA'S BLOG」でした.
明日は@cero_tさんです.

JDK9でDeprecatedが変わるかもしれません.
この変更はJEP 277: Enhanced Deprecationで行われます.
手前味噌で,雑な翻訳ですが,翻訳版もあります.

まだ確定したわけではないので,常に元のJEPを確認するようにしてください.
今後変更があると思います.

概略

JDK9では以下の様にDeprecatedまわりが変更されます.

  • Deprecatedを用いてより一層詳細なAPIの状態などを提供できるようにする
  • アプリケーションが使用している非推奨APIを実行時にログとして出力する
  • アプリケーションで使用している非推奨APIの情報を静的に生成するツールを提供する
  • 機能のライフサイクルに関するJava SEとJDKのポリシーを明確化する

動機

なぜ@Deprecatedを変更しないといけないのでしょう?
現在,@Deprecatedはかなり矛盾した状態になっています.

@Deprecatedは1.5で導入されました.
@DeprecatedはJavadocでは以下のように説明されています.

注釈@Deprecatedの付いたプログラム要素は、一般に危険であったり,より適切なほかのプログラム要素で代用できることもあり,プログラマには使用を薦められないプログラム要素です.
推奨されていないプログラム要素が使用されている場合,あるいは推奨されているコードにおいてオーバーライドされている場合,コンパイラが警告を発します.

Deprecatedは,別のAPIに移行することを推進し,非推奨APIに新たに使うことが無いように,そのAPIがライフサイクルの最後を迎えるという事を開発者に伝えることを意図していました.
上記のJavadocでは述べられていないのに,様々な文章で,いつか@DeperecatedなAPIは削除されるだろうと述べられています.

@Deprecatedは結果的に幾つかの異なった目的で使われてしまっています.
何も削除されるべきではないと考える人たちによって,非推奨APIは実際にはほとんど削除されていません.
一方で,非推奨になったら,いつか削除されるだろうと思っている人たちもおり,混乱が生じています.
どちらも意図したものではありませんでした.
このことは開発者に伝わった@Deprecatedの意味と,そしてなにより,開発者が非推奨APIの使用に出会った時に何をするべきかが不明瞭だったために起きました.
現在,非推奨が実際のところ何を意味するのかということで皆が混乱しており,更には誰も非推奨を深刻に捉えていないと,Stuart Marksは指摘しています.
更には,このことはJava SEから何かを削除することを一層困難にしているとも指摘しています.

別の問題として,非推奨APIについての警告がコンパイル時にしか無いということがあります.
APIが将来のJDKで非推奨になっても,既存のバイナリは警告無しにその非推奨APIを使用し続けてしまいます.
もしもその非推奨APIが削除されたら,古いアプリケーションバイナリは警告が出ることもなく,突然リンクエラーで起動できなくなってしまうでしょう.
更に酷いことに,既存のバイナリが非推奨なAPIに依存しているかどうかを開発者が確認する方法がありません.

Deprecatedを用いてより一層詳細なAPIの状態などを提供できるようにする

APIの非推奨の状態についてのきめ細かい情報をツールとランタイムに提供するために@Deprecatedアノテーションを強化します.
以下メソッドと列挙型Reasonがjava.lang.Deprecatedアノテーションに追加するように提案されています.

デフォルト値 説明
value() Reason[] UNSPECIFIED この値は列挙型の一つ以上の定数の組み合わせになります.すべての値の組み合わせは意味をなしません. 空の配列を指定する事は構文的には可能で, この場合はUNSPECIFIEDが与えられたものとして扱うべきです.
replacement() String[]   このメソッドで返される文字列は非推奨APIを置き換えるAPIへのリンク. それぞれの文字列はJavadocタグの@linkと同じフォーマットにするべきです.
since() String   この文字列はAPIが非推奨になった時のリリースナンバーです.構文は自由ですが,リリースナンバリングはJavadocタグの@sinceのスキームと同じにするべきです. この値がJavadocの@sinceタグと冗長ではないことに注意してください. なぜなら,@sinceタグはそのAPIが導入されたリリースが記録されているのに対し,@Deprecatedアノテーションのsince()メソッドが返す値はそのAPIが非推奨になったリリースを記録しているからです.

列挙型Reasonは以下の要素を持ちます.

要素名 説明
UNSPECIFIED このAPIは他の理由で非推奨になりました. これはデフォルト値です. 今日,暗に非推奨とされているすべてのAPIの非推奨理由はUNSPECIFIEDです.
CONDEMNED このAPIは将来のJDKリリースで削除される予定です.
DANGEROUS このAPIを使用すると,データロスやデッドロック,セキュリティ上の脆弱性,不正確な結果,JVMの正確性の欠如といった結果を招く可能性があります.
OBSOLETE このAPIはもはや必要ではなく,このAPIの使用は削除されるべきです. 置き換え可能なAPIは存在しません. OBSOLETEなAPIはCONDEMNEDとしてマークされるかもしれませんし,されないかもしれません.
SUPERSEDED このAPIは新しいAPIで置き換えられており,このAPIの使用はこのAPIから新しいAPIに変更されるべきです. OBSOLETEなAPIはCONDEMNEDとしてマークされるかもしれません.
UNIMPLEMENTED このAPIの呼び出しは意味がないか常に例外が投げられます.
EXPERIMENTAL このAPIは仕様で策定されておらず,いつか互換性の無い形で変更されるか,削除されるかもしれません.

JDK内にある既存の@Deprecatedの使用もアップデートされます.
例えば,Thread#destroyとThread#stop(Throwable)の@Deprecatedについて,reasonが{UNIMPLEMENTED, CONDEMNED}となるように修正することが提案されています.
また,HashTableなどに新たに@Deprecated注釈が行われる事も提案されています.

アプリケーションが使用している非推奨APIを実行時にログとして出力する

非推奨APIの使用についての警告を発する方法が新たに提供されます.
例えば,非推奨クラスが読み込まれた際に警告を発するための"-verbose:deprecation"といったコマンドラインオプションです.
実行時にライブラリのコードから,この設定を行えるようにすることも提案されています.

警告文はgrepなどのツールで処理しやすい様な文章になる予定ですが,実際にどのような文章になるかはまだ決まっていません.

理由(列挙型Reason)ごとに,警告が発せられるかどうかを設定できる様になるらしいです.

アプリケーションで使用している非推奨APIの情報を静的に生成するツールを提供する

開発者がJARファイル(やクラスファイルの他のコンテナ)内で非推奨なクラスやメンバーの使用があるかを調査できる静的解析ツールが提供されるらしいです.
ツールとしてはjdepsに機能が似ているため,それを拡張したものになるようです.

機能のライフサイクルに関するJava SEとJDKのポリシーを明確化する

これについてはまだあまり決まっていないようです.
結構この辺りで揉めそう・・・

その他

Javadoc内で非推奨なAPIがわかりやすく表示される様にするという変更も考えられています.