6

私は次のコードを持っています。汎用インターフェイスITestは非汎用インターフェイスによって拡張されていITestDoubleます。メソッドはopによってオーバーライドされITestDoubleます。

のすべてのメソッドをリストしようとするとITestDoubleop2 回表示されます。それらが実際に同じ方法であることをどのように確認できますか?

public class Test {

    public static void main(String[] args) throws NoSuchMethodException {
        for (Method m : ITestDouble.class.getMethods()) {
            System.out.println(m.getDeclaringClass() + ": " + m + "(bridge: " + m.isBridge() + ")");
        }
    }

    public interface ITestDouble extends ITest<Double> {
        @Override
        public int op(Double value);

        @Override
        public void other();
    }

    public interface ITest<T extends Number> {
        public int op(T value);

        public void other();
    }
}

出力:

interface Test$ITestDouble: public abstract int Test$ITestDouble.op(java.lang.Double)(bridge: false)
interface Test$ITestDouble: public abstract void Test$ITestDouble.other()(bridge: false)
interface Test$ITest: public abstract int Test$ITest.op(java.lang.Number)(bridge: false)

PS私はこれがオーバーライドされたメソッドに対するJava Class.getMethods()の動作と同じ質問であることを知っていますが、その質問には本当の答えがありませんでした.isBridge()呼び出しは常にfalse.

op編集:「重複」メソッドを除外するという汚い仕事をするライブラリでも問題ありません。

4

6 に答える 6

7

残念ながら、 JVMに関する限り、完全に独立しITestDoubleた正当な方法があるため、その情報を取得することはできません。メソッドが常に一致していることを確認するのは、実際には Javaコンパイラです。op(Number)op(Double)

これは、JDK5 より前のコンパイラまたは動的プロキシを使用して、ITestDouble完全に異なる実装をop(Number)使用して の異常な実装を作成できることを意味します。op(Double)

public static void main(String[] args) throws NoSuchMethodException {

    final Method opNumber = ITest.class.getMethod("op", Number.class);
    final Method opDouble = ITestDouble.class.getMethod("op", Double.class);
    final Method other = ITestDouble.class.getMethod("other");

    ITestDouble dynamic = (ITestDouble) Proxy.newProxyInstance(
            ITestDouble.class.getClassLoader(),
            new Class<?>[]{ITestDouble.class},
            new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {
                    if (opDouble.equals(m)) return 1;
                    if (opNumber.equals(m)) return 2;
                    // etc....

                    return null;
                }
            });

    System.out.println("op(Double): " + dynamic.op(null);            // prints 1.
    System.out.println("op(Number): " + ((ITest) dynamic).op(null);  // prints 2. Compiler gives warning for raw types
}

編集: Java ClassMate について 学びました。宣言内のすべての型変数を正しく解決できるライブラリです。使い方はとても簡単です:

    TypeResolver typeResolver = new TypeResolver();
    MemberResolver memberResolver = new MemberResolver(typeResolver);

    ResolvedType type = typeResolver.resolve(ITestDouble.class);
    ResolvedTypeWithMembers members = memberResolver.resolve(type, null, null);
    ResolvedMethod[] methods = members.getMemberMethods();

これを繰り返すとmethods、次のようになります。

void other();
int op(java.lang.Double);
int op(java.lang.Double);

重複を簡単にフィルタリングできるようになりました。

public boolean canOverride(ResolvedMethod m1, ResolvedMethod m2) {
    if (!m1.getName().equals(m2.getName())) return false;

    int count = m1.getArgumentCount();
    if (count != m2.getArgumentCount()) return false;

    for (int i = 0; i < count; i++) {
        if (!m1.getArgumentType(i).equals(m2.getArgumentType(i))) return false;
    }

    return true;
}
于 2012-09-18T08:29:33.117 に答える
4

これまでのところ、すべての回答者が積極的に貢献していますが、さまざまなアイデアを 1 つの回答にまとめようと思います。何が起こっているのかを理解するための鍵は、Java コンパイラと JVM がどのようにジェネリックを実装するかです。これにより、ブリッジが説明され、インターフェイスでそれが false である理由が説明されます。

ただし、要約すると、次のようになります。

  1. int op(Number)シグネチャの互換性と実装のブリッジ メソッドの構築には、より一般的なメソッドが必要です。

  2. メソッドは、isBridge()インターフェイスではなく、具体的なクラスに対してのみ true にすることができます

  3. おそらく、2 つのメソッドのどちらを選択しても問題ありません。ランタイムでは同じように機能します。

わかりました、ここに長い答えがあります:

Java の Generics の実装

ジェネリック クラスまたはインターフェイスがある場合、コンパイラはクラス ファイル内にメソッドを構築し、各ジェネリック型を適切な具象型に置き換えます。たとえば、ITest次のメソッドがあります。

int op(T value)

ここで、クラスは として定義TされてT extends Numberいるため、クラス ファイルには次のメソッドがあります。

int op(Number);    

を使用するITestと、コンパイラは、ジェネリックが解決される型ごとに追加のクラスを作成します。たとえば、次のコード行があるとします。

ITest<Double> t = new ...

コンパイラは、次のメソッドを持つクラスを生成します。

int op(Number);
int op(Double);

しかし、JVM が必要とするのはint op(Double)バージョンだけでしょうか? コンパイラは、ITest<Double>が呼び出しのみを受信することを確認しませんop(Double)か?

int op(Number)Java がメソッドを必要とする理由は 2 つあります。

  1. オブジェクト指向では、クラスをどこで使用しても、(少なくともタイプ セーフから)いつでもサブクラスに置き換えることができる必要があります。存在しない場合int op(Number)、クラスはスーパークラス (またはスーパーインターフェース) の署名の完全な実装を提供しません。

  2. Java はキャストとリフレクションの両方を備えた動的言語であるため、間違った型でメソッドを呼び出すことができます。この時点で、Java はクラス キャスト例外が発生することを保証します。

実際、上記の 2. の実装は、「ブリッジ メソッド」を生成するコンパイラによって実現されます。

ブリッジメソッドは何をしますか?

ITest<Double>から作成されるITest<T extends Number>と、コンパイラはint op(Number)メソッドを作成し、その実装は次のとおりです。

public int op(Number n) {
    return this.op((Double) n);
} 

この実装には 2 つのプロパティがあります。

  1. n が である場合Double、呼び出しを に委譲しint op(Double)

  2. n が でない場合、 Doubleが発生しClassCastExceptionます。

このメソッドは、ジェネリック型から具象型への「ブリッジ」です。その性質上、具体的なメソッドのみがブリッジになることができるためint op(Double)、サブインターフェイスの はシグネチャにすぎません。

OPはどうですか?

問題の例では、コンパイラによって作成されたサブインターフェイス クラス ファイルITestDoubleに両方のメソッドがあります。

int op(Number);
int op(Double);

int op(Number)実装がITestDoubleブリッジ メソッドを持つことができるようにするために が必要ですが、このメソッド自体はブリッジではありません。間違いなく、Sun/Oracle はここでトリックを見逃しており、バグを報告する価値があるかもしれません。

正しい方法を見つけるには?

まず、関係ありますか?のすべての実装にITestDoubleは、コンパイラによって自動的に挿入されるブリッジ メソッドがあり、ブリッジ メソッドがメソッドを呼び出しますint op(Double)。つまり、どのメソッドが呼び出されるかは問題ではなく、1 つを選択するだけです。

次に、実行時にインターフェイスではなくインスタンスが渡される可能性が高くなります。を実行するgetMethods()と、ブリッジ メソッドと実際の実装を区別できるようになります。これはジョンカールが言ったことです。

第 3 に、インターフェイスに問い合わせてこれを解決する必要がある場合は、「最も低い」サブタイプの引数をテストすることをお勧めします。たとえば、メタ レベルでは次のようになります。

  1. 同じ名前の 2 つのメソッドをすべて収集する

  2. 引数の型を収集します。Method.getParameterTypes()[0]

  3. を使用しClass.isAssignableFrom(Class)ます。引数が同じか、メソッドが呼び出されたクラスのサブクラスである場合、メソッドは true を返します。

  4. 引数が他のメソッドの引数のサブクラスであるメソッドを使用します。

于 2012-09-21T08:39:55.897 に答える
2

これは、必要なものに役立つ場合があります。特定のニーズや失敗した場合に合わせて微調整します。つまり、宣言元のクラスに対して宣言された正確な「ジェネリック」型にメソッドが一致するかどうかをチェックします。独自のジェネリックを使用している場合、これは失敗する可能性があります。getDeclaringClass() の呼び出しと独自のジェネリックのロジックを組み合わせることをお勧めします。

public static boolean matchesGenericSignature(Method m) {
    Type[] parameters = m.getGenericParameterTypes();
    if (parameters.length == 0)
        return false;
    Class<?> declaring = m.getDeclaringClass();
    TypeVariable<?>[] types = declaring.getTypeParameters();
    for (TypeVariable<?> typeVariable : types) {
        for (Type parameter : parameters) {
            if (typeVariable.equals(parameter)) {
                return true;
            }
        }
    }
    return false;
}
于 2012-09-20T08:08:10.150 に答える
1

getMethods()の代わりにメソッドgetDeclaredMethods()を使用して、反映しているクラス(スーパークラスではない)で宣言されたメソッドのみを取得できます。メソッドが重複しているという問題を解決します。

于 2012-09-14T18:15:21.470 に答える
1

アップデート:

解決策として、このメソッド (Method.getGenericParameterTypes()) を試してください。

public static void main(String[] args) {

    for (Method m : ITestDouble.class.getMethods()) {
        Type [] types = m.getGenericParameterTypes();
        System.out.println(m.getDeclaringClass() + ": " + m + "(genericParameterTypes: "
                + Arrays.toString(types) + ")"+" "+(types.length>0?types[0].getClass():""));

        Type t = types.length>0?types[0]:null;
        if(t instanceof TypeVariable){
            TypeVariable<?> v = (TypeVariable)t;
            System.out.println(v.getName()+": "+Arrays.toString(v.getBounds()));
        }
    }

}

出力は次のとおりです。

interface FakeTest$ITestDouble: public abstract int FakeTest$ITestDouble.op(java.lang.Double)(genericParameterTypes: [class java.lang.Double]) class java.lang.Class
interface FakeTest$ITestDouble: public abstract void FakeTest$ITestDouble.other()(genericParameterTypes: []) 
interface FakeTest$ITest: public abstract int FakeTest$ITest.op(java.lang.Number)(genericParameterTypes: [T]) class sun.reflect.generics.reflectiveObjects.TypeVariableImpl
T: [class java.lang.Number]

ジェネリックはコンパイル中に消去されます。だからあなたは本当に持っています:

   public interface ITestDouble extends ITest {

        public int op(Double value);

        @Override
        public void other();
    }

    public interface ITest {
        public int op(Number value);

        public void other();
    }

クラス ITest は、実装の数を知りません。したがって、パラメーター Number を持つメソッド op は 1 つだけです。T extends Numberで無限実装を定義できます。(T = Doubleで)。

于 2012-09-11T16:17:54.457 に答える
0

これであなたの質問に答えたり、問題を 100% 解決したりできないことはわかっていますが、 isBridge() メソッドを使用して、どのメソッドが具象クラスによって実装されているのか、どのメソッドが一般的に「ブリッジ」されているのかを判断できます。

public class Test {

    public static void main(String[] args) throws NoSuchMethodException {
        for (Method m : TestDouble.class.getMethods()) {
            System.out.println(m.getDeclaringClass() + ": " + m + "(bridge: " + m.isBridge() + ")");
        }
    }

    public class TestDouble extends ITestDouble{
        public int op(Double value) {
            return 0;
        }

        public void other() {
        }
    }

    public interface ITestDouble extends ITest<Double> {

        public int op(Double value);

        public void other();
    }

    public interface ITest<T extends Number> {
        public int op(T value);

        public void other();
    }
}

出力:

class test.Test$TestDouble: public int test.Test$TestDouble.op(java.lang.Double)(bridge: false)
class test.Test$TestDouble: public int test.Test$TestDouble.op(java.lang.Number)(bridge: true)
class test.Test$TestDouble: public void test.Test$TestDouble.other()(bridge: false)
class java.lang.Object: public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException(bridge: false)
class java.lang.Object: public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException(bridge: false)
class java.lang.Object: public final void java.lang.Object.wait() throws java.lang.InterruptedException(bridge: false)
class java.lang.Object: public boolean java.lang.Object.equals(java.lang.Object)(bridge: false)
class java.lang.Object: public java.lang.String java.lang.Object.toString()(bridge: false)
class java.lang.Object: public native int java.lang.Object.hashCode()(bridge: false)
class java.lang.Object: public final native java.lang.Class java.lang.Object.getClass()(bridge: false)
class java.lang.Object: public final native void java.lang.Object.notify()(bridge: false)
class java.lang.Object: public final native void java.lang.Object.notifyAll()(bridge: false)

特に:

Test$TestDouble.op(java.lang.Number)(bridge: true)
于 2012-09-11T16:58:03.283 に答える