新たなJEP候補は次のことを提案する。難解なJavaでの型変位の概念をよりうまく扱えるようにすることだ。新しい提案は、Java 10を対象にする可能性が高いが、それはジェネリックな型の定義において対象の型に対してデフォルトの変位を扱えるように、手段を導入するだろう。ジェネリックな型をインスタンス化する時にワイルドカードを通じてそれを示す現在のスタイルとはまったく異なる。この提案はワイルドカードを置き換えるのではなく、むしろその必要を減らす方法である。
型変位の話題はまだ多くの開発者にはよく知られておらず、Javaではこの問題はむしろ人気のないワイルドカードを通じて位置づけられているという事実は、それを明らかにする助けとなっていない。この理由で、そして読者にこのJEPの潜在的な影響力を理解してもらえるように、この記事で我々はまず型変位とは何か、そして現在どのようにJavaでは位置づけられているのかを説明したい。そして、新しい提案が達成することへの記述へと続ける。
変位と共変、反変
オンラインショッピングアプリケーションによくある以下のコードを考えてみよう。
public class Product {
/* ... */
}
public class FrozenProduct extends Product {
/* ... */
}
もしメソッドscan(Product product)
があるなら、それをFrozenProduct
オブジェクトを渡して呼び出す。呼び出しは問題なく動作する。これはポリモーフィズムの共通規則のもとでよく知られている。しかし引数がジェネリクスを含むとき、同じロジックは適用できない。以下のコードはコンパイルエラーになる。
private void addAllProducts(List<Product> products) {
/* Check product stock */
shoppingCart.addAll(products);
}
private void freezerSection() {
List<FrozenProduct> frozenProducts = /* select frozen products */;
addAllProducts(frozenProducts); // ERROR: List<Product> expected
// List<FrozenProduct> found
}
ジェネリックがJavaで使われるとき、対象の型の互換性、そのサブタイプもしくはスーパータイプかといったことに関する仮定はまったく作られない。言い換えると、ジェネリクスが使われるとき、対象の型はデフォルトで不変だと言える。正確な型だけが適合する。
しかし上記の例では、addAllProducts
メソッドは実際Product
のList
もしくはそのどんなサブタイプとともに動作するだろうとわかる。ジェネリックな引数がその対象の型とどんなサブタイプのどちらも受け入れるとき、型が共変であると言う。Javaではextends
を使って表現する。
private void addAllProducts(List<? extends Product> products) {
/* Check product stock */
shoppingCart.addAll(products);
}
private void freezerSection() {
List<FrozenProduct> frozenProducts = /* select frozen products */;
addAllProducts(frozenProducts); // works with no problem
}
この例では、受け入れられる対象の型の変位はサブタイプである。対象の型の変位がサブタイプではなく、スーパータイプであるようなほかのシナリオもある。次のような例を考えてみよう。
private boolean askQuestion(Predicate<String> p) {
return p.test("hello");
}
private void applyPredicate() {
Predicate<Object> evenLength = o -> o.toString().length() % 2 == 0;
askQuestion(evenLength); // ERROR: Predicate<String> expected
// Predicate<Object> found
}
この場合、文字列"hello"
をラムダo -> o.toString().length() % 2 == 0
に適用しても問題なく動作するとわかる。しかし、コンパイラは我々にそうさせてくれない。askQuestion
はString
の、もしくはそのあらゆるスーパータイプのPredicate
であればokであるべきだ。この場合の対象の型は反変であると言う。Javaではsuper
を使って表現する。
private boolean askQuestion(Predicate<? super String> p) {
return p.test("hello");
}
private void applyPredicate() {
Predicate<Object> evenLength = o -> o.toString().length() % 2 == 0;
askQuestion(evenLength); // works with no problem
}
ワイルドカードは型変位を確立するとても柔軟性がある方法である。それは同一の型に対して異なる場所では異なる変位を定義できるからだ。たとえば上記の例ではaddAllProducts
が共変の引数を持つべきであると決定した。しかしほかの場所ではそれを反変もしくは不変とすることもできる。そうすることが必要なのであれば、だ。しかし否定的な側面は次のようなことだ。変位を必要とされる場所すべてで明確に指定しなければならない。多くの冗長さと混乱がすぐに追加されることとなる。これが新しい提案がなされた理由である。
宣言箇所でデフォルトの変位を示す
ワイルドカードでの主な問題の1つは開発者がワイルドカードの動作に一般的に求めることよりもワイルドカードはさらに柔軟であることである。Predicate<String>
の例では、理論的にはPredicate<? extends String>
を要求するメソッドを作ることができるが、これが役に立つユースケースの数は非常に限られる(たとえいくつかあるとしても)。この場合たいていは意味がある型推論はたった1つだ。 このJEP 300はジェネリックな型を宣言する時点でデフォルトの変位を示す方法を提案している。たとえばこの提案では、インタフェースPredicate<T>
は提案されているキーワードcontravariant
を使ってPredicate<contravariant T>
として書くことができる。これは次のことを意味する。Predicate<String>
と開発者が書くとき、つねに暗黙的にPredicate<? super String>
として解釈されるということだ。
この新しい機能のシンタックスはまだ決まっていないが、いくつかのオプションが提案されている。新しい明示的なキーワード、Function<contravariant T, covariant R>
のようなものだが、もしくは先行する他言語に合わせて、Scalaでのシンボル(Function<-T, +R>
)やより短いC#でのキーワード(Function<in T, out R>
)のようなものだ。シンタックスの問題が取り組まれる前だが、重要な技術的側面にいくつか取り組む必要がある。すなわちデフォルトの変位とワイルドカードが何であるかということだ。デフォルトの変位が既存のコードにもたらす影響と、変位の型の互換性を確認する実際のメカニズムといったことだ。
最後に、JEP 300はデフォルトの変位という新しい機能の作成を扱うだけであるということを指摘しておこう。しかしこれはJavaで利用できるどんなクラスやインタフェースも変更しない。この種の作業はJEP 300で進めてほしいと期待されているが、他のJEPで実行されるだろう。
Rate this Article
- Editor Review
- Chief Editor Action