一般的な継承とGetMethod()。getReturnType()の呼び出し

Generic Inheritance and Calling GetMethod().getReturnType()


質問 written by Ryan Ransford @2011-10-28 15:54:25Z

: 6 : 5 : 1

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

ここに何が欠けていますか? 私はいくつかの凶悪な消去の事に噛まれるのでしょうか、それともある種の一般的なコンテキスト破壊を逃しているのでしょうか?

編集: getId()メソッドをB追加すると、問題は修正されますが、私は何を実行しているのかを引き続き理解したいと思います。

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"
    }
}
コメント 1

スーパータイプトークンを使用して、目的のことを達成するのは簡単ではありませんか?gafter.blogspot.com/2006/12/super-type-tokens.html

written by millimoose @2011-10-28 16:01:07Z

回答 1 written by Tom Hawtin - tackline @2011-10-28 16:31:24Z
2

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

実際、この例では、すべての情報がクラスファイルに残っています。 (以前は消去されていないと述べましたが、それは真実ではありませんが、消去は存在しないという意味ではありません!)しかし、一般的な情報を抽出するのは少し難しいです( Identifiable.class.getGenericReturnType()Identifiable.class.getTypeParameters()BaseEntity.class.getGenericInterfacesBaseEntity.class.getTypeParameters()およびB.getGenericSuperclass (と思います!))。

javapを使用して、クラスファイルの内容を正確に確認します。

コメント 1

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

written by MarianP @2011-10-28 17:04:40Z

コメント 2

実際に動作するようにsetId2を実装するには、 this.id = (T)id;、警告が表示されます(注意が必要です!!)。BのインスタンスのgetIdは、呼び出し元からClassCastExceptionをスローしClassCastException (私は思う)。

written by トムホーティン-タックライン @2011-10-28 17:23:27Z

コメント 3

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

written by トムホーティン-タックライン @2011-10-28 17:29:33Z

コメント 4

SOの[履歴の表示]ボタンのようなものがありません。質問と結果の回答は、人々が急いで回答する間、リアルタイムで行われるすべての編集と少し混乱するかもしれません。

written by MarianP @2011-10-28 18:59:20Z

コメント 5

、そこにある、私の悪い

written by さて @2011-10-28 20:22:05Z

回答 2 written by Laurence Gonsalves @2011-11-03 00:34:04Z
2

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

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

コメント 1

違う。これは消去とは関係ありません。BaseEntity.idには、Serializableを含めることができます。(どのメソッドも他のSerializableに設定しないという推論は、コンパイラが行うことをはるかに上回っています)。BがgetId()をオーバーライドせず、getId()がIntegerのみを返すことが強制される場合、SerializableはgetId()から出てくる可能性があります-MarianP

written by @2011-10-28 16:54:38Z

コメント 2

@MarianP:「タイプ消去」が意味することについて再読したいと思うかもしれません、それ言及された場合に起こるのです。

written by -JimmyB @2011-10-29 12:27:27Z

コメント 3

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

written by ローレンスゴンサルベス @2011-10-30 03:43:21Z

コメント 4

@LaurenceGonsalves基本的なJavaのルールに従うだけなので、私が書いたものが推測であることがわかりません。コンパイルされたコードで遊ぶことが正しい推論であるかどうかは判断しませんが、そのままにしておきましょう。あなたの答えを少し考えると、あなたがうまく推論していることがわかります、それは簡潔です。「消去」を見た後、私はそれをダウン投票しました。別の明らかに間違った答えがあります。私は最初に見て、ここで消去を扱いました。この場合、重要なものは何も消去されず、Serializableを使用するのは消去ではないため、「消去」という単語は使用しませんが、ダウン票が急いでいたことを認めて取り戻します。

written by MarianP @2011-10-30 08:49:56Z

コメント 5

@LaurenceGonsalvesはあなたの答えの中のいくつかの文字を編集して、私のダウン投票を取り消すことができますか?

written by MarianP @2011-10-30 08:50:50Z

回答 3 written by Guillaume @2011-10-28 15:59:00Z
1

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

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

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

コメント 1

違う。BaseEntity.idにはSerializableを含めることができます。(どのメソッドも他のSerializableに設定しないという推論は、コンパイラが行うことをはるかに上回っています)。BがgetId()をオーバーライドせず、getId()がIntegerのみを返すことが強制される場合、SerializableはgetId()から出てくる可能性があります-MarianP

written by @2011-10-28 16:53:34Z

回答 4 written by MarianP @2011-10-28 17:05:16Z
1

BaseEntityのidプライベートで 、 ' Serializable or extends Serializable 'です。

クラスB(BaseEntityを拡張)は、このフィールドについて何も知りません。 独自のIDを定義し、getId()/ setId(...)をオーバーライドしなかった場合、これら2つのメソッドはBaseEntity.idを引き続き使用します。

このメソッドをBaseEntityに追加する場合:

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

BaseEntity.idを任意のSerializableに設定できます。

次のテストでは、idフィールドをたとえばFloat値に設定すると、すべてがコンパイルされ、変更されていないgetId()が快適にFloat値を返します。

In following test you may then set the id field to e.g. a Float value and everything compiles and non-changed getId() comfortably returns the Float value.

したがって、あなたが何をして「B.getId()メソッドの戻り値の型は何ですか」と尋ねた場合、BクラスのgetId()メソッドをオーバーライドしない限り(整数関数型を使用して整数を返すように強制します) BaseEntity.idはBには見えないことに注意してください!)リフレクションの答えはIntegerではなく、一般的なSerializableです。 SerializableはgetId()メソッドから実際に出てくる可能性があるためです。

回答 5 written by JimmyB @2011-10-30 14:45:32Z
1

Javaでは、戻り値の型のいわゆる「ナローイング」が許可されています。 それがあなたの例がまったく機能する理由です:

Serializable getId()

次のような、シリアル化可能な戻り値の型でオーバーライドできます。

Integer getId()は、 IntegerSerializable実装するため、この場合はInteger getId()ローイングが許可されます。

BgetId()オーバーライドしないため、 getId()BaseEntityから継承されたものと同じBaseEntity 宣言

class B extends BaseEntity<Integer>

コンパイル時に「タイプ消去」されます

is "type-erased" at compile time to

そして、ほら、観測結果を受け取ります。

コメント 1

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

written by MarianP @2011-10-28 16:56:26Z

コメント 2

申し訳ありませんが、ポイントを取得できませんでした:上記のジェネリッククラス(およびB )の場合、 T getId()Serializable getId()にコンパイルされます。これが、リフレクションによって戻り値型Serializableが返される理由です。クラスA ではInteger直列化可能であるため、 Integer getId()は継承されたSerializable getId()をオーバーライドします。そのため、コンパイルすると、クラスAのメソッドはIntegerを返します。これは、OPが観察したように、実行時にリフレクションが通知するものです。

written by JimmyB @2011-10-29 12:18:08Z

コメント 3

これは本当です。あなたは本当にあなたの答えでこの長さをすべて推論しませんでした。回答の文字を編集して、ダウン票をキャンセルできるようにしてください。

written by MarianP @2011-10-30 08:53:57Z