2012-09-11 8 views
6

Mam następujący kod z ogólnym interfejsem ITest rozszerzonym o nie ogólny interfejs ITestDouble. Metoda op jest zastępowana przez ITestDouble.Refleksje na temat metod nadpisywanych interfejsami

Kiedy próbuję wyświetlić listę wszystkich metod z ITestDouble, otrzymuję dwa razy . Jak mogę sprawdzić, czy faktycznie są to ta sama metoda?

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(); 
    } 
} 

wyjściowa:

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 Wiem, że to samo pytanie jak Java Class.getMethods() behavior on overridden methods, ale to pytanie nie dostał prawdziwą odpowiedź: wywołanie isBridge() zawsze zwraca false.

EDIT: Jestem też dobrze z każdej biblioteki, która zrobi brudną robotę odfiltrowanie „duplikat” op sposób dla mnie.

+1

Widoczne są tylko mosty na klasach bez interfejsów, ponieważ most jest fragmentem kodu, w którym jedna metoda wywołuje inną. –

+0

Rzeczywiście, więc jak rozumiem, że istnieje tylko jedna metoda "op"? – Flavio

+0

Tak, ale nie znam prostego sposobu mówienia, że ​​są one takie same z samych interfejsów. Możesz spojrzeć na ogólne informacje dla ITest i wywnioskować, że metody są takie same, ale to dużo pracy. –

Odpowiedz

7

Niestety nie można mieć tych informacji, ponieważ w miarę JVM obawia się, ITestDouble ma uzasadniony sposób op(Number) które mogą być całkowicie niezależne od op(Double). W rzeczywistości jest to Twój kompilator Java , który zapewnia, że ​​metody zawsze się pokrywają.

To oznacza, że ​​można tworzyć patologicznych implementacje ITestDouble z całkowicie różnych wdrożeń dla op(Number) i op(Double) za pomocą kompilatora wstępnie JDK5 lub dynamicznego proxy:

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 
} 

EDIT: właśnie dowiedziałem się od Java ClassMate. Jest to biblioteka, która może poprawnie rozwiązać wszystkie zmienne typu w deklaracji. Jest bardzo łatwy w użyciu:

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(); 

Teraz, jeśli iteracyjne nad methods zobaczysz co następuje:

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

Teraz jest łatwe filtrowanie duplikatów:

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) nie jest całkowicie niezależny od op (Double), co zostało zademonstrowane przez odpowiedź johncarla poniżej. Jest tam, ponieważ w przeciwnym razie podpis TestDouble byłby niekompatybilny z Testem signature –

+0

Rzeczywiście w przykładzie johncarla nie są one niezależne. To dlatego, że używa zwykłej Java i jej kompilatora do stworzenia. Ale jeśli używasz proxy, starego kompilatora lub asemblera JVM, możesz tworzyć niezależne (i patologiczne) przykłady, takie jak moje powyżej. – Saintali

+0

Bardzo pouczający przykład z 'Proxy'. Naprawdę jednak nie rozwiązuje mojego pytania. – Flavio

1

Aktualizacja:

Dla rozwiązania spróbować tej metody (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())); 
     } 
    } 

} 

wyjścia:

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] 

Leki generyczne są usuwane podczas kompilacji. Więc naprawdę masz:

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(); 
    } 

Klasa ITest nie wiem, ile masz wdrożenia. Więc ma tylko jedną metodę op z parametrem Number. Możesz zdefiniować nieskończoną implementację za pomocą T przedłuża Numer. (w twoim T = Double).

+0

Nie zgadzam się. Jeśli zaimplementujesz 'ITestDouble', zobaczysz, że możesz/musisz zdefiniować tylko' public int op (Double value) '(jak w implementacji @johncarl). I wiem, że mogę zdefiniować 'ITestInteger',' ITestLong' i tak dalej, ale jak to ma znaczenie dla mojego pytania? – Flavio

0

wiem, że to nie może odpowiedzieć na pytanie czy w rozwiązaniu problemu w 100%, ale można użyć metody isBridge() w celu określenia, jakie metody są realizowane przez betonową klasy vs jakie metody są rodzajowo „zmostkowanym” jako takie:

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(); 
    } 
} 

wyjścia:

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) 

zwłaszcza:

Test$TestDouble.op(java.lang.Number)(bridge: true) 
+0

Dzięki ... ale te interfejsy są implementowane przez MyBatis przez proxy, niestety nie mam konkretnej klasy do zweryfikowania. – Flavio

1

Możesz użyć metody getDeclaredMethods() zamiast getMethods(), aby uzyskać tylko metody zadeklarowane dla klasy, którą odzwierciedlasz (nie super klasy). Rozwiązuje problem posiadania zduplikowanych metod.

+0

No dobrze, ale w ten sposób nie widzę żadnej metody interfejsu podstawowego, która nie zostałaby zastąpiona. – Flavio

+0

Możesz rozwiązać jeden problem naraz. Przede wszystkim użyj 'getDeclaredMethods()'. Następnie użyj 'getMethods()', aby uzyskać metody, które nie zostały zastąpione. Następnie dokonaj zmiany między obiema grupami. –

+0

Nie sądzę, że rozumiem twoją sugestię. Używam 'getDeclaredMethods()' i otrzymuję 'op (java.lang.Double)' i 'other()'. Następnie za pomocą 'getMethods()' otrzymuję 'op (java.lang.Double)', 'op (java.lang.Number)' i 'other()'. Różnica polega na 'op (java.lang.Number)'. W jaki sposób to mi powiedzieć, że 'op (java.lang.Number)' zastępuje 'op (java.lang.Double)'? – Flavio

2

To może zadziałać w razie potrzeby. Dostosuj go do konkretnych potrzeb lub w każdym przypadku niepowodzenia. W skrócie, sprawdza, czy metoda jest zgodna z typem "rodzajowym" zadeklarowanym dla klasy, z której została zadeklarowana. Jeśli używasz własnych generyków, może to spaść. Sugerowałbym łączenie wywołań getDeclaringClass() z logiką dla twoich własnych generycznych.

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

Każdy respondent dotąd pozytywnie przyczynił, ale postaram się owinąć się różne pomysły w jedną odpowiedź. Kluczem do zrozumienia, co się dzieje, jest to, w jaki sposób kompilator Java i JVM implementują generyczne - to wyjaśnia mostek i dlaczego jest fałszywe w interfejsie.

Podsumowując, choć:

  1. Metoda bardziej ogólny int op(Number) wymagana jest zgodność podpisu i skonstruować metodą most wdrażania za

  2. Sposób isBridge() może być tylko prawdziwe dla konkretnych klas, a nie interfaces

  3. Prawdopodobnie nie ma znaczenia, która z dwóch metod zostanie wybrana - będą działać identycznie w środowisku wykonawczym.

Ok, tutaj jest długa odpowiedź:

implementacja Javy z rodzajowych

Kiedy masz rodzajowe klasy lub interfejsu, kompilator tworzy metodę w pliku klasy z każdego typu rodzajowego zastąpiony odpowiednim rodzajem betonu. Na przykład ITest ma metodę:

int op(T value) 

gdzie klasa definiuje T jako T extends Number, więc plik klasa ma metodę:

int op(Number);  

Kiedy ITest służy, kompilator tworzy dodatkowe zajęcia dla każdego wpisz ogólną jest rozstrzygnięty na.Na przykład, jeśli istnieje linia kodu:

ITest<Double> t = new ... 

Kompilator tworzy klasę z następujących metod:

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

Ale pewnie tylko JVM musi wersję int op(Double)? Czy kompilator nie upewnia się, że ITest<Double> odbiera tylko połączenia pod numerem op(Double)?

Istnieją dwa powody, dlaczego Java potrzebuje metody int op(Number):

  1. orientacja obiektu wymaga, aby wszędzie tam, gdzie użycie klasy można zawsze zastąpić ją podklasy (przynajmniej z bezpieczeństwem typu). Jeśli nie istnieje int op(Number), klasa nie zapewni pełnej implementacji sygnatury superklasy (lub super-interfejsu).

  2. Java jest dynamicznym językiem z odlewem i odbiciem, dzięki czemu można wywołać metodę z nieprawidłowym typem. W tym momencie Java gwarantuje, że otrzymasz wyjątek od klasy.

W rzeczywistości implementacja powyższego 2. jest osiągnięta przez kompilator wytwarzający "metodę mostu".

Do czego służy metoda pomostowa?

Kiedy ITest<Double> tworzony jest z ITest<T extends Number>, kompilator tworzy metodę int op(Number), a jego realizacja jest:

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

Ta implementacja ma dwie właściwości:

  1. gdzie n jest Double, przekazuje połączenie pod numer int op(Double) i

  2. Gdzie n jest nie a Double, powoduje to, że ClassCastException.

Metoda ta jest "pomostem" od rodzaju ogólnego do typu betonu. Z samej swojej natury, tylko konkretne metody mogą być mostami, więc int op(Double) na pod-interfejsie jest tylko podpisem.

Co z OP?

Na przykład, w kwestii klasa plik sub interfejs ITestDouble utworzony przez kompilator zarówno metody:

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

int op(Number) jest potrzebny, aby implementacje ITestDouble może mieć swój sposób mostek - , ale ta metoda nie jest sama w sobie mostem, ponieważ jest tylko podpisem, a nie implementacją.Prawdopodobnie Sun/Oracle pominęli tutaj pewną sztuczkę i warto byłoby podnieść z nimi błąd.

Jak znaleźć właściwą metodę?

Po pierwsze, ma to znaczenie? Wszystkie implementacje ITestDouble będą miały automatycznie wstawioną metodę mostu przez kompilator, a metoda mostu wywoła metodę int op(Double). Innymi słowy, to naprawdę nie ma znaczenia, która metoda jest wywoływana, po prostu wybierz jedną.

Po drugie, w czasie wykonywania najprawdopodobniej zostanie przekazany instancje, nie interfejsów. Gdy wykonasz numer getMethods(), będziesz w stanie odróżnić metodę pomostową od rzeczywistej implementacji. Tak powiedział johncarl.

Po trzecie, jeśli musisz rozwiązać ten problem, przesłuchując interfejs, możesz przetestować argumenty dla "najniższego" podtypu. Na przykład, w meta poziomie:

  1. zebrać wszystkie te dwie metody o tej samej nazwie

  2. Collect typ argumentu: Method.getParameterTypes()[0]

  3. Korzystając Class.isAssignableFrom(Class). Metoda zwraca wartość true, jeśli argument jest taki sam lub podklasę klasy, w której wywoływana jest metoda.

  4. Użyj metody z argumentem będącym podklasą pod numerem argumentu innej metody.

+0

Ostatnia część, którą zasugerowałeś, oczywiście nie działa: co jeśli był to zwykły stary interfejs nietypowy, taki jak 'interface ITestDouble {int op (Double); int op (liczba); } '? Niezła próba :) – Saintali

+0

Prace na zadane pytanie. Ponadto można go rozszerzyć o dwa lub więcej parametrów. Będą dwie metody o tej samej nazwie, jedna będzie pełna, druga będzie konkretna. Więc znowu wybierasz metodę z argumentem, który jest podklasą tego samego argumentu w innej metodzie. Przynajmniej jeden argument będzie miał tę właściwość. –

+0

Ładne zawijanie, ale naszkicowany algorytm wydaje się nieco naiwny ... OK działa dla metod 'op', ale ogólnie sytuacja wydaje się bardziej złożona (np. Bardziej ogólne parametry). – Flavio

Powiązane problemy