オブジェクトマッピングを実現するロジックを書いていたりすると Class
クラスのインスタンスを扱うことがあります。
その Class
に何かしらの条件を設けたいということもよくあります。
折角Javaを使っているのだから、型検査時に不正な値を弾くことができるとステキですよね。
実現したいこと
// 重要なインターフェイス!
interface MetaSyntacticValiable {}
class Hoge implements MetaSyntacticValiable {}
class Fuga implements MetaSyntacticValiable {}
// MetaSyntacticValiable とは無関係なクラス
class UnrelatedToHogeOrSomething {}
(中略)
someMethodUsingClassClass(Hoge.class); // OK
someMethodUsingClassClass(Fuga.class); // OK
someMethodUsingClassClass(UnrelatedToHogeOrSomething.class); // NG! そしてNGであることを型検査で教えて欲しい
解決方法
public void someMethodUsingClassClass(Class<? extends MetaSyntacticValiable> klass) {
...
}
Class<? extends MetaSyntacticValiable>
がキモでした。
T.class
の型が Class<T>
なので、Class<?>
のワイルドカードに条件を加えてやることで、任意のインターフェイスを継承したクラスの Class
インスタンスのみを代入する事ができます。
インターフェイスを extends する…というのがどうにももどかしいですが、こちらの stackoverflow の回答によると、「ジェネリクスの文脈においては extends も implements も変わらない」のだそうで。
(ただしジェネリクスの制約で使えるのは extends
と super
のみ)
もっと型の条件を増やしたいときは?
残念ながら以下の書き方はできません。
条件に使えるのは一つの型だけのようです。
// コンパイル通らない
Class<? extends InterfaceOne, InterfaceTwo>
「それでも両方のインターフェイスを継承したクラスに限定したいんだ!」という場合は、一つのインターフェイスに統合する必要があります。
interface InterfaceOnePlusTwo extends InterfaceOne, InterfaceTwo {}
(中略)
// これはコンパイル通る
Class<? extends InterfaceOnePlusTwo>
ただし、 統合前のインターフェイスを個別に implements したクラスは受け付けなくなります。
void methodUsingClassClass(Class<? extends InterfaceOnePlusTwo> klass) { ... }
// Class<? extends InterfaceOnePlusTwo> ではない!
class ForArgumentOne implements InterfaceOne, InterfaceTwo {}
// よってNG.
methodUsingClassClass(ForArgumentOne.class);
// こちらは Class<? extends InterfaceOnePlusTwo>
class ForArgumentTwo implements InterfaceOnePlusTwo {}
// なのでOK.
methodUsingClassClass(ForArgumentTwo.class);
別にこれくらい受け付けてくれたって良いじゃないか…