2011-10-28 12 views
6

私の現在のプロジェクトでは、次のようにモデル化されたクラスがあります。ある時点で、ABにはgetReturnTypeForGetId()のようなメソッドが呼び出されます。 Aでメソッドを呼び出すと、Integerが返されますが、BSerializableを返します。ジェネリック継承と呼び出しGetMethod()。getReturnType()

私はここで何が欠けていますか?私はいくつかの凶悪な消去事件に噛まれてしまったのでしょうか、あるいは私は何らかの一般的な文脈の矛盾を見逃していますか?

EDIT:B修正問題にオーバーだらけgetId()方法を追加し、私はまだ、私はに実行しているかを理解したいと思います。

import java.io.Serializable; 

public class WeirdTester { 
    static interface Identifiable<T extends Serializable> { 
     T getId(); 
     void setId(final T id); 
    } 

    static abstract class BaseEntity<T extends Serializable> implements Identifiable<T> { 
     private T id; 
     public T getId() { return id; } 
     public void setId(final T id) { this.id = id; } 
    } 

    static class A implements Identifiable<Integer> { 
     private Integer id; 
     public Integer getId() { return id; } 
     public void setId(final Integer id) { this.id = id; } 
    } 

    static class B extends BaseEntity<Integer> {} 

    @SuppressWarnings("unchecked") 
    private static <T extends Serializable, Q extends Identifiable<T>> Class<T> getReturnTypeForGetId(
      final Class<Q> clazz) throws Exception { 
     return (Class<T>) clazz.getMethod("getId", (Class[])null).getReturnType(); 
    } 

    public static void main(final String[] args) throws Exception { 
     System.out.println(getReturnTypeForGetId(A.class)); 
     // CONSOLE: "class java.lang.Integer" 
     System.out.println(getReturnTypeForGetId(B.class)); 
     // CONSOLE: "interface java.io.Serializable" 
    } 
} 
+0

スーパータイプのトークンを使って何をしようとしているのかを簡単に達成できないでしょうか? http://gafter.blogspot.com/2006/12/super-type-tokens.html – millimoose

答えて

2

コンパイルされたAクラスには、複数のgetIdメソッドがあります。共変な戻り型(仮想マシンには反映されていない言語の "フィクション")のためのブリッジメソッドを取得します。 Class.getMethodの仕様では、最も特定の戻り値の型(存在すると仮定)を持つメソッドを返します。 Aの場合はこれを行いますが、Bの場合はメソッドがオーバーライドされないため、javacは不要なブリッジメソッドの合成を回避します。

実際、この例では、すべての情報がクラスファイルに残ります。 (以前は私はそれがerasedではないと言ったが、それは本当ではないが、消去はそこにないということを意味するわけではない!)しかし、一般的な情報は抽出するのが少し難解である(Identifiable.class.getGenericReturnType(),Identifiable.class.getTypeParameters(),BaseEntity.class.getGenericInterfacesBaseEntity.class.getTypeParameters()B.getGenericSuperclass(私は思う!))。

javapを使用すると、クラスファイルの内容を正確に確認できます。

+0

私はあなたの答えを理解するかどうか分からない。 BaseIntityにsetId2(final Serializable id)を追加すると、idフィールドにはIntegerだけでなくすべてのSerializableが含まれている可能性があるため、getId()がBでオーバーライドされない場合(Integerを返すように強制されている場合)それから出てくる。メソッドがIDフィールドをInteger以外に設定することができないことを確認することは、コンパイラ/ JVMのジョブではありません。 – MarianP

+0

実際に動作するように 'setId2'を実装するには、' this.id =(T)id; 'は警告を与えます(注意を払うべきです!!)。 'B'のインスタンス上の' getId'は呼び出し元から 'ClassCastException'を投げます(私は思う)。 –

+0

(私は...間違っていると思います)。早い段階で修正されたバグは、javacが正しいオーバーロードにキャストされることでした。だから 'T 'が' 'char []'ならばCCEになります。 –

2

クラスAでは、getIdをオーバーライドしてIntegerを返します。

クラスBでは、getIdをオーバーライドしないため、BのgetIdメソッドはBaseEntityのメソッドです。消去のために、それはSerializableを返します。

+0

が間違っています。これは消去とは関係ありません。 BaseEntity.idには、任意のシリアライズ可能なものを含めることができます。 (推論は、他のシリアライズ可能なメソッドには設定しません)。 BがgetId()をオーバーライドしない場合、getId()のみがIntegerを返す場合、SerializableはgetId()から出る可能性があります。 – MarianP

+0

@MarianP: "タイプ消去"上記の場合に発生する*ということを意味します。 – JimmyB

+0

@MarianPあなたは「間違っている」と言っていますが、残りのコメントは推測です。実際にjavacの動作を確認するためにコードを逆コンパイルしてみましたか?私が持っています。ジェネリックスの適用は、静的プルーフ(チェックされていないキャストがない場合にのみ有効)とランタイムキャストの組み合わせによって行われます。 –

1

答えは本当にタイプ消去です。ジェネリックは、コンパイルされていないJavaコードのヒントに過ぎないことに注意してください。コンパイラは、バイトコードを生成するためにそれらと関係のあるすべてのものを削除します。したがって、getIdメソッドでリフレクションを使用すると、生の型だけが取得されます。

http://download.oracle.com/javase/tutorial/java/generics/erasure.html

しかし、あなたは、このメソッド(B.getId)によって返された実際のオブジェクトのクラスを求めるならば、それが構築されています方法が原因リフレクションを使用せずに、あなたは整数を取得します。

+0

が間違っています。 BaseEntity.idにはシリアライズ可能なものが含まれています。 (推論は、他のシリアライズ可能なメソッドには設定しません)。 BがgetId()をオーバーライドしない場合、getId()のみがIntegerを返すように強制される場合、SerializableはgetId()から出る可能性があります。 – MarianP

-1

Javaでは、戻り値型のいわゆる「絞り込み」が可能です。あなたの例がすべてで働く理由です:IntegerSerializableを実装し、その狭小化が、この場合には許可されているよう

Serializable getId()

は、

Integer getId()のように、任意のシリアル化可能な戻り値の型を上書きすることができます。

Bは、そのgetId()BaseEntityから継承されたものと同じであるないオーバーライドをgetId()ないため。宣言

class B extends BaseEntity<Integer> 

class B extends BaseEntity 

へのコンパイル時に「型消去」と、ほらあり、我々は観察結果を受け取ります。 BaseEntityで

+0

あなたは共変リターンを意味しますか?オーバーライドされたメソッドでは、戻り型のサブタイプを使用することができます。この質問とは関係がありません。 – MarianP

+0

申し訳ありませんが、上記の点を理解できませんでした。上記のジェネリッククラス(* B *)の場合、* T getId()*は* Serializable getId()*にコンパイルされます。そのため、リフレクションによって戻り値の型* Serializable *が返されます。 クラス* A * * Integer getId()*は継承された* Serializable getId()*をオーバーライドします。したがって、コンパイルすると、クラス* A *のメソッドは整数が返されます。これは、OPが観察したように、リフレクションが実行時に指示するものです。 – JimmyB

+0

これは本当です。あなたは本当にあなたの答えにこの長さを理由付けしなかった。あなたの答えでいくつかの文字を編集してください私は私のdownvoteをキャンセルすることができますようにしてください。 – MarianP

1

IDプライベートと 'SerializableのまたはSerializableのを拡張する' です。

クラスB(BaseEntityを拡張)は、このフィールドについて何も知らない。それは自身のIDを定義し、あなたがBaseEntityでこのメソッドを追加した場合、これらの2つの方法がBaseEntity.id

を使用し続けることになるのgetId()/ SETID(...)をオーバーライドしなかった場合:

public void setId2(final Serializable id) { 
     this.id = (T) id; 
} 

BaseEntity.idを任意のシリアライズ可能に設定できます。

次のテストでは、idフィールドをたとえばに設定することができます。 a Float値はすべてコンパイルされ、変更されません。getId()は浮動小数点値を快適に返します。あなたは何を行うと尋ねる場合

B b = new B(); 
b.setId2(2.1F); 
System.out.println(b.getId()); //prints out 2.1 

したがって、「B.getId()メソッドの戻り値の型がどのようなものです」、そしてあなたがのgetId()Bクラスのメソッドをオーバーライドしない限り、(それを強制ことになる使用しますInteger関数の型と返すInteger。BaseEntity.idはBでも表示されないことに注意してください!)反射の答えは整数ではなく、汎用Serializableです。任意のSerializableが実際にgetId()メソッドから出てくる可能性があるからです。

関連する問題