M12i.

学術書・マンガ・アニメ・映画の消費活動とプログラミングについて

Java言語ではFunc<T>とFunc<T,U>は区別できない

この1年くらいの間Javaを離れてC#ばかりをコーディングしていました。戻ってきて愕然としたのは例えばFunc<T>Func<T,U>という型を仮定した時、Java言語においてはそれらが区別のつかない型、型名の競合を引き起こす型として捉えられてしまうということです。

これは、次の2点を考えればなんのことはない、当然の事態ではあります:

  • Java言語のジェネリクスのベースにイレージャのメカニズムが存在すること、つまりコンパイル過程でリフレクションのための型のメタ情報はともかくとして型そのものからは型パラメータが消去されてしまうこと。
  • C#言語におけるような型パラメータに即した型名の自動修飾──Func<T>Func`1となり、Func<T,U>Func`2となる──の仕組みはJava言語には存在しないこと。

1点目のイレージャ型のジェネリクスを採用したことの背景にある事情を考えれば、2点目もこのようにならざるをえないわけです。既存APIを維持しつつ拡張しようとすると、イレージャを採用する一方で型名の自動修飾のメカニズム(もしくはルール)は不採用とする必要があるのです。

Java SE 8の関数型インターフェースについて勉強する人はFunctionBiFunctionSupplierなどなどのいくつものインターフェース名を記憶させられることになります。種々の型名で区別されるインターフェースが存在することは、それらのインターフェースの目的・役割を示す点でも有意義なものではありますが、実情としては型名の競合を防ぐためにはこうするしかなかったということです。
言い方を変えると「ソースコード上、型パラメータで示される情報は同時に型名でも示さないといけない」という二重修飾の拘束がはたらいているのです。

「だからなんだ、C#だって裏側では型名の自動修飾で競合を回避したりしているではないか」と意見もありそうですが、それにしてもそうした内情をソースコードのレベルで露わにしてしまい、標準APIの「概念的重み」(≒学習コスト)も増大させてしまうのはやはり望ましいことではないでしょう。イレージャ型ジェネリクスには、型パラメータの実パラメータが異なる型を引数とするオーバーロード・メソッドが定義できないという、これも地味ですが何かしらのAPI設計をしていると結構痛い(結果として引数の型名やメソッド名にバリエーションをつくる必要が生じる)問題もあります。

いずれも「言っても仕方のないこと」ではあるのですが、C#からの帰還後にはどうにも気になってしょうがないことでもあるのです。。