2012-09-11 30 views
6

我有下面的代碼,用一個通用的接口ITest由不通用ITestDouble接口擴展。該op方法由ITestDouble覆蓋。反思界面重寫方法

當我嘗試列出ITestDouble的所有方法時,我得到了兩次op。我如何確認它們實際上是相同的方法?

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() behavior on overridden methods同樣的問題,但這個問題沒有得到真正的答案:isBridge()調用總是返回false

編輯: 我也很好,任何圖書館,會做我的篩選出「重複」op方法的骯髒工作。

+1

您只能看到類不是接口的網橋,因爲網橋是一個方法調用另一個方法的存根的一段代碼。 –

+0

事實上,我如何理解實際上只有一個「op」方法? – Flavio

+0

是的,但我不知道簡單的告訴他們從接口單獨是相同的方式。您可以查看ITest的通用信息,並推斷出這些方法是相同的,但這是很多工作。 –

回答

7

不幸的是你不能有這些信息,因爲只要JVM而言,ITestDouble具有合法的方法op(Number)它可以是完全獨立的op(Double)。它實際上是你的Java 編譯器,它確保方法總是一致的。

這意味着,您可以通過使用預JDK5編譯或動態代理創建爲op(Number)完全不同的實現和op(Double)ITestDouble病理實現:

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; 
} 
+0

op(Number)並不完全獨立於op(Double),正如johncarl的答案所示。它在那裏,因爲否則TestDouble簽名將與Test 簽名不兼容 –

+0

確實在johncarl的示例中它們不是獨立的。這是因爲他使用普通的Java及其編譯器來創建。但是,如果您使用代理,舊編譯器或JVM彙編器,則可以創建獨立(和病態)示例,如上所述。 – Saintali

+0

具有'Proxy'的非常豐富的例子。雖然沒有真正解決我的問題。 – Flavio

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的方法。您可以定義無限的執行與牛逼延伸數。 (在你的T = Double中)。

+0

我不同意。如果你實現了'ITestDouble',你會發現你只能定義'public int op(Double value)'(就像在@johncarl實現中一樣)。我知道我可以定義「ITestInteger」,「ITestLong」等,但這與我的問題有什麼關係? – Flavio

0

我知道這並不能回答你的問題或解決您的問題100%,但您可以使用isBridge()方法來確定哪些方法是用vs什麼方法都統稱「橋接」這樣一個具體的類實現:

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) 
+0

謝謝...但這些接口是由MyBatis通過代理實現的,我不幸有具體的類來驗證。 – Flavio

1

您可以使用方法getDeclaredMethods()而不是getMethods()來僅獲取正在反射的類(而非超類)上聲明的方法。它解決了重複方法的問題。

+0

好吧,但這種方式我沒有看到沒有覆蓋的基礎接口的任何方法。 – Flavio

+0

您可以一次解決一個問題。首先,使用'getDeclaredMethods()'。之後,使用'getMethods()'獲取未覆蓋的方法。然後在兩組之間做出區別。 –

+0

我不認爲我理解你的建議。我使用'getDeclaredMethods()'並且獲得'op(java.lang.Double)'和'other()'。然後用getMethods()獲得op(java.lang.Double),op(java.lang.Number)和other()。不同的是'op(java.lang.Number)'。這是如何告訴我,op(java.lang.Number)'覆蓋'op(java.lang.Double)'? – Flavio

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; 
} 
3

每個受訪者迄今已作出了積極貢獻,但我會盡力向上包住不同的想法成一個單一的答案。瞭解正在發生的事情的關鍵是Java編譯器和JVM如何實現泛型 - 這解釋了橋接,以及爲什麼它在接口中是錯誤的。

綜上所述,雖然:

  1. 比較通用的方法int op(Number)需要簽名的兼容性和構建實施的電橋法

  2. isBridge()方法只能針對具體的類是真實的,而不是接口

  3. 這兩種方法中的哪一種可能無關緊要 - 它們將在運行時保持一致。

好吧,這裏是長答案:

Java的實現泛型的

當你有一個泛型類或接口,編譯器構建的方法在類文件中,每個泛型類型換成適當的混凝土類型。例如,ITest有一個方法:

int op(T value) 

在類定義TT extends Number,所以類文件有一個方法:

int op(Number);  

當使用ITest,編譯器創建用於每個附加的類鍵入通用解析爲。例如,如果有一行代碼:

ITest<Double> t = new ... 

編譯器生成一類具有下列方法:

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

但可以肯定的JVM只需要int op(Double)版本?編譯器不確定ITest<Double>只接收op(Double)的呼叫嗎?

有兩個原因Java需要的int op(Number)方法:

  1. 面向對象要求,無論你使用一個類你可以總是由子類(從類型安全至少)替換它。如果int op(Number)不存在,則該類將不提供超類(或超級接口)的簽名的完整實現。 Java是一種具有鑄造和反射的動態語言,所以它可以用來調用該方法的類型不正確。在這一點上,Java保證你得到一個類拋出異常。

實際上,上述2的實現是通過編譯器生成'橋接方法'來實現的。

橋接方法的作用是什麼?

當從ITest<T extends Number>創建ITest<Double>,編譯器創建的int op(Number)方法,它的實現是:

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

此實現具有兩個屬性:

  1. 其中N是Double,它將電話委託給int op(Double)

  2. 其中n是而不是 a Double,它會導致ClassCastException

該方法是從泛型類型到具體類型的「橋樑」。從其本質來看,只有具體的方法才能成爲橋樑,所以子接口上的int op(Double)只是一個簽名。

OP的情況如何?

在的問題的例子中,子接口的類文件ITestDouble由編譯器生成具有這兩種方法:

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

int op(Number)需要使得ITestDouble實現可以有自己的電橋法 - 但這種方法本身並不是一個橋樑,因爲它只是一個簽名,而不是一個實現。可以說孫/甲骨文在這裏錯過了一個竅門,可能值得引起他們的一個錯誤。

如何找到正確的方法?

首先,重要嗎?ITestDouble的所有實現都將由編譯器自動插入橋接方法,橋接方法調用int op(Double)方法。換句話說,調用哪個方法並不重要,只需選擇一個即可。

其次,在運行時,你很可能會被傳遞實例,沒有接口。當您執行getMethods()時,您將能夠區分網橋方法和實際實施。這就是johncarl所說的。第三,如果你確實需要通過詢問接口來解決這個問題,你可能想測試'最低'子類型的參數。例如,在元級:

  1. 收集所有的兩種方法具有相同的名稱

  2. 收集參數類型:Method.getParameterTypes()[0]

  3. 使用Class.isAssignableFrom(Class)。如果參數是相同的或調用該方法的類的子類,則該方法返回true。

  4. 使用方法的參數是其他方法參數的子類

+0

你提出的最後一部分顯然不工作:如果它是一個普通的舊非非泛型接口,如'interface ITestDouble {int op(Double); int op(Number); }'? 不錯嘗試雖然:) – Saintali

+0

問題的作品問。此外,它可以擴展爲兩個或更多參數。將會有兩個同名的方法,一個是完全通用的,另一個是具體的。所以再一次,您選擇的方法的參數是另一種方法中相同參數的子類。至少有一個參數會有這個屬性。 –

+0

不錯的總結,但草圖算法似乎有點天真...好吧,它適用於'op'方法,但通常情況似乎更復雜(例如更通用的參數)。 – Flavio