S-JIS[2008-02-08/2009-02-25] 変更履歴
共変ってあんまり聞かない言葉だけど。
メソッドをオーバーライドした際、戻り値の型が(JDK1.4までは一致している必要があったが、JDK1.5から)サブクラスに出来るようになった。
|
ちなみに、共変の反対は「反変(contravariant)」と言うらしい…が、それはJavaとは無関係^^;
interface Interface {
public Number getValue();
}
class Implement implements Interface {
public Integer getValue() { //戻り値の型にサブクラスを指定
return 123; //自動ボクシング
}
}
この例では、インターフェースでは戻り値をNumberで宣言しているが、その実装ではNumberのサブクラスであるIntegerで定義している。
Interface obj = new Implement();
Number n = obj.getValue();
Implement obj = new Implement();
Integer n = obj.getValue();
→インターフェースとクラス間だけでなく、複数のインターフェース間とクラスでも可 [2008-05-02]
クローンの定義が便利になる。
つまり、今まで自分の複製を返すにも関わらずObjectで定義するしかなかったので、clone()を呼んだ側でキャストする必要があった。これからはちゃんと自分のクラスを指定できる。
配列は共変なので、配列は共変戻り値に出来る(ただし、たぶん推奨されない)。[2009-02-25]
総称型(List等)は共変ではないので、共変戻り値に出来ない。
class CovariantSuper { public Number method() { return Byte.valueOf((byte) 0); } public Number[] methodArray() { return new Number[10]; } public List<Number> methodList() { return new ArrayList<Number>(); } }
class CovariantInteger extends CovariantSuper {
@Override
public Integer method() {
return Integer.valueOf(123);
}
@Override
public Integer[] methodArray() {
return new Integer[10];
}
@Override
public List<Integer> methodList() { ←コンパイルエラー
return null;
}
}
共変戻り値が指定できるのはあくまでサブクラス(派生クラス)なので、プリミティブ型は対象外。
class Super { public int getValue() { return 0; } }
class Sub extends Super {
public int getValue() {
//× public Integer getValue() { //intのラッパーであるIntegerもダメ
//× public long getValue() { //intよりサイズの大きいlongもダメ
return 0;
}
}
戻り値型を変えたクラスをjavapで見てみると、(JDK1.4まで通りの)まったく同一の戻り値型を持つメソッドと、戻り値型を変えたメソッドの2つのメソッドが定義されているのが分かる。
>javap CloneSample Compiled from "CloneSample.java" public class jp.hishidama.sample.covariant.CloneSample extends java.lang.Object implements java.lang.Cloneable{ public jp.hishidama.sample.covariant.CloneSample(); public jp.hishidama.sample.covariant.CloneSample clone() throws java.lang.CloneNotSupportedException; public java.lang.Object clone() throws java.lang.CloneNotSupportedException; }
より詳細(バイトコード)を見てみると、同一の戻り値型のメソッドが、戻り値型を変えた方のメソッドを呼ぶようになっていた。
たぶん、このメソッドのことを橋渡しメソッド(ブリッジメソッド)と呼ぶのだと思う。
>javap -v CloneSample
public jp.hishidama.sample.covariant.CloneSample clone() throws java.lang.CloneNotSupportedException; Exceptions: throws java.lang.CloneNotSupportedException Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokespecial #21; //Method java/lang/Object.clone:()Ljava/lang/Object; ←Object#clone()の呼び出し 4: checkcast #1; //class jp/hishidama/sample/covariant/CloneSample 7: areturn
public java.lang.Object clone() throws java.lang.CloneNotSupportedException; Exceptions: throws java.lang.CloneNotSupportedException Code: Stack=1, Locals=1, Args_size=1 0: aload_0 1: invokevirtual #27; //Method clone:()Ljp/hishidama/sample/covariant/CloneSample; ←CloneSampleを返すclone()の呼び出し 4: areturn
ついでに、このCloneSampleをさらに派生して新しいクラスを作り、clone()もオーバーライドして別クラスを返すようにしたら3つ目のメソッドが出来るかと思って試してみた。
class Clone2 extends CloneSample { @Override public Clone2 clone() throws CloneNotSupportedException { return (Clone2) super.clone(); } }
>javap Clone2 Compiled from "CloneSample.java" class jp.hishidama.sample.covariant.Clone2 extends jp.hishidama.sample.covariant.CloneSample{ jp.hishidama.sample.covariant.Clone2(); public jp.hishidama.sample.covariant.Clone2 clone() throws java.lang.CloneNotSupportedException; public jp.hishidama.sample.covariant.CloneSample clone() throws java.lang.CloneNotSupportedException; }
どうやら、すぐ上のクラス(スーパークラス)のメソッドのオーバーライド分だけが作られるようだ。
では、リフレクションはどうなるか。
(リフレクションでメソッドを取得するには メソッド名と引数の型だけを指定し、戻り値の型は指定しない為)
Class<CloneSample> cls = CloneSample.class; System.out.println("●getMethod()"); Method m = cls.getMethod("clone", new Class[] {}); System.out.println(m.getReturnType()); //戻り値の型を表示 System.out.println("●getDeclaredMethod()"); Method d = cls.getDeclaredMethod("clone", new Class[] {}); System.out.println(d.getReturnType()); //戻り値の型を表示 System.out.println("●getMethods()"); Method[] ms = cls.getMethods(); for (Method i : ms) { if (i.getName().equals("clone")) { System.out.println(i); System.out.println("橋 …" + i.isBridge()); System.out.println("合成…" + i.isSynthetic()); } }
実行結果:
●getMethod() class jp.hishidama.sample.covariant.CloneSample ●getDeclaredMethod() class jp.hishidama.sample.covariant.CloneSample ●getMethods() public jp.hishidama.sample.covariant.CloneSample jp.hishidama.sample.covariant.CloneSample.clone() throws java.lang.CloneNotSupportedException 橋 …false 合成…false public java.lang.Object jp.hishidama.sample.covariant.CloneSample.clone() throws java.lang.CloneNotSupportedException 橋 …true 合成…true
単独のメソッドを取得するgetMethod()やgetDeclaredMethod()では、自分で定義した(戻り値の型が異なっている)メソッドが取得されている。
全メソッドを取得するgetMethods()ではどちらも取得できる。
ただ、橋渡しメソッド(ブリッジメソッド)かどうかを判断するisBridge()や、合成メソッドかどうかを判断するisSynthetic()は値が異なっている。
定義したメソッドを呼ぶ側(戻り値の型が親クラスと一致しているメソッド)は橋渡しメソッドになっているようだ。
(合成メソッドってのは よく分からないけど、コンパイラが生成するものらしく、ソースには書けないらしい)