2012-09-11 14 views
6

Ho il seguente codice, con un'interfaccia generica ITest estesa da un'interfaccia non generica ITestDouble. Il metodo op viene sostituito da ITestDouble.Riflessione sui metodi sovrascritti dall'interfaccia

Quando provo a elencare tutti i metodi di ITestDouble, ottengo il numero op due volte. Come posso verificare che siano effettivamente lo stesso metodo?

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

uscita:

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 So che questa è la stessa domanda di Java Class.getMethods() behavior on overridden methods, ma questa domanda non ho ricevuto risposta vera: la chiamata isBridge() restituisce sempre false.

MODIFICA: Sto anche bene con qualsiasi libreria che farebbe il lavoro sporco di filtrare il metodo "duplicato" op per me.

+1

Si vedono solo ponti su classi e non interfacce poiché il bridge è un pezzo di codice di stub in cui un metodo ne chiama un altro. –

+0

Infatti, come faccio a capire che in realtà esiste un solo metodo 'op'? – Flavio

+0

Sì, ma non conosco il modo semplice per dire che sono uguali solo dalle interfacce. Puoi guardare le informazioni generiche per ITest e dedurre che i metodi sono gli stessi ma è molto lavoro. –

risposta

7

Purtroppo si può avere queste informazioni, perché per quanto riguarda la JVM è interessato, ITestDouble ha un metodo legittimo op(Number) che può essere totalmente indipendente op(Double). In realtà è il tuo compilatore Java che assicura che i metodi coincidano sempre.

Ciò implica che è possibile creare implementazioni patologiche di ITestDouble con totalmente differenti implementazioni per op(Number) e op(Double) utilizzando un compilatore pre-JDK5, o un proxy dinamica:

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: appena appreso di Java ClassMate. È una libreria in grado di risolvere correttamente tutte le variabili di tipo in una dichiarazione. E 'molto facile da usare:

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

Ora, se eseguire iterazioni su methods vedrete il seguente:

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

Ora è facile per filtrare i duplicati:

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 (Numero) non è completamente indipendente da op (Double) come dimostrato dalla risposta di johncarl in basso. È lì perché altrimenti la firma TestDouble sarebbe incompatibile con il Test firma –

+0

Infatti nell'esempio di johncarl non sono indipendenti. Questo perché sta usando Java semplice e il suo compilatore da creare. Ma se usi un proxy, un vecchio compilatore o un assemblatore JVM puoi creare esempi indipendenti (e patologici) come il mio sopra. – Saintali

+0

Esempio molto informativo con 'Proxy'. Non sto davvero risolvendo la mia domanda, comunque. – Flavio

1

Aggiornamento:

Per la vostra soluzione di provare questo metodo (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())); 
     } 
    } 

} 

output è:

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] 

Generics sono cancellati durante la compilazione. Quindi hai davvero:

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

Classe ITest non so quante implementazioni hai. Quindi ha un solo metodo op con parametro Number. È possibile definire un'implementazione infinita con T estendi il numero. (nel tuo T = doppio).

+0

Non sono d'accordo. Se implementi 'ITestDouble', vedi che puoi/devi solo definire' public int op (Double value) '(come nella implementazione @johncarl). E so che posso definire 'ITestInteger',' ITestLong' e così via, ma quanto è rilevante per la mia domanda? – Flavio

0

So che questo potrebbe non rispondere alla tua domanda o risolvere il tuo problema al 100%, ma puoi usare il metodo isBridge() per determinare quali metodi sono implementati da una classe concreta vs quali metodi sono genericamente "colmati" in questo modo:

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

uscite:

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) 

particolare:

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

Grazie ... ma queste interfacce sono implementate da MyBatis tramite i proxy, non ho una classe concreta da verificare, sfortunatamente. – Flavio

1

È possibile utilizzare il metodo getDeclaredMethods() anziché getMethods() per ottenere solo i metodi dichiarati nella classe che si sta riflettendo (non super classi). Risolve il problema di avere metodi duplicati.

+0

Ok, ma in questo modo non vedo alcun metodo dell'interfaccia di base che non sia sovrascritta. – Flavio

+0

È possibile risolvere un problema alla volta. Prima di tutto, usa 'getDeclaredMethods()'. Successivamente, usa 'getMethods()' per ottenere i metodi che non sono sovrascritti. Quindi fai la differenza tra entrambi i gruppi. –

+0

Non credo di aver capito il tuo suggerimento. Io uso 'getDeclaredMethods()' e ottengo 'op (java.lang.Double)' e 'other()'. Quindi con 'getMethods()' ottengo 'op (java.lang.Double)', 'op (java.lang.Number)' e 'other()'. La differenza è 'op (java.lang.Number)'. Come mi dice che 'op (java.lang.Number)' sovrascrive 'op (java.lang.Double)'? – Flavio

2

Questo potrebbe funzionare per quello che ti serve. Modificalo per esigenze specifiche o eventuali casi di errore. In breve, controlla se il metodo corrisponde esattamente al tipo "generico" dichiarato per la classe da cui viene dichiarato. Se stai usando i tuoi generici, questo potrebbe cadere. Suggerirei di combinare le chiamate a getDeclaringClass() con la logica dei propri generici.

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

Ogni intervistato finora ha contribuito positivamente, ma cercherò di avvolgere le diverse idee su in una sola risposta. La chiave per capire cosa sta succedendo è come il compilatore Java e la JVM implementano i generici - questo spiega il bridge, e perché è falso nell'interfaccia.

In sintesi, però:

  1. Il metodo più generico int op(Number) è richiesto per la compatibilità firma e costruire il metodo ponte di attuazione

  2. Procedimento isBridge() può essere vero solo per le classi di cemento, non interfacce

  3. Probabilmente non importa quale dei due metodi si raccolgono, funzioneranno in modo identico nel runtime.

Ok, ecco la risposta lunga:

implementazione di Java Generics

Quando si dispone di una classe o interfaccia generica, il compilatore costruisce un metodo nel file di classe con ogni tipo generico sostituito con un tipo concreto appropriato. Ad esempio, ITest ha un metodo:

int op(T value) 

cui la classe definisce T come T extends Number, in modo che il file di classe ha un metodo:

int op(Number);  

Quando si utilizza ITest, il compilatore crea classi aggiuntive per ogni digitare il generico è risolto a.Ad esempio, se c'è una riga di codice:

ITest<Double> t = new ... 

Il compilatore produce una classe con i seguenti metodi:

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

Ma sicuramente la JVM ha bisogno solo la versione int op(Double)? Il compilatore non si assicura che ITest<Double> riceva solo chiamate su op(Double)?

Ci sono due ragioni per cui Java ha bisogno il metodo int op(Number):

  1. orientamento oggetto richiede che ovunque si utilizza una classe si può sempre sostituirlo con una sottoclasse (almeno dal tipo di sicurezza). Se int op(Number) non esiste, la classe non fornirà un'implementazione completa della firma della superclasse (o super-interfaccia).

  2. Java è un linguaggio dinamico con casting e riflessione, pertanto è possibile chiamare il metodo con un tipo errato con . A questo punto, Java garantisce di ottenere un'eccezione di classe.

Infatti, l'attuazione di 2. viene raggiunto dal compilatore producendo un 'metodo bridge'.

Che cosa fa un metodo bridge?

Quando ITest<Double> viene creato dal ITest<T extends Number>, il compilatore crea il metodo int op(Number), e la sua implementazione è:

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

Questa implementazione ha due proprietà:

  1. dove N è un Double, delega la chiamata a int op(Double) e

  2. Dove n è non a Double, causa lo ClassCastException.

Questo metodo è un "ponte" dal tipo generico al tipo di calcestruzzo. Per la sua stessa natura, i metodi di calcestruzzo solo possono essere ponti, quindi int op(Double) sull'interfaccia secondaria è solo una firma.

E l'OP?

Nell'esempio in questione, il file di classe sub-interfaccia ITestDouble creato dal compilatore ha entrambi i metodi:

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

Il int op(Number) è necessario in modo che le implementazioni di ITestDouble possono avere il loro metodo di bridge - ma questo metodo non è di per sé un bridge perché è solo una firma, non un'implementazione.Probabilmente Sun/Oracle ha mancato un trucco qui, e potrebbe valere la pena sollevare un bug con loro.

Come trovare il metodo corretto?

In primo luogo, importa? Tutte le implementazioni di ITestDouble avranno il metodo bridge inserito automaticamente dal compilatore e il metodo bridge chiama il metodo int op(Double). In altre parole, non importa quale metodo venga chiamato, basta sceglierne uno.

In secondo luogo, in fase di esecuzione molto probabilmente passerai le istanze , non le interfacce. Quando esegui lo getMethods(), sarai in grado di distinguere tra il metodo bridge e l'effettiva implementazione. Questo è ciò che ha detto johncarl.

In terzo luogo, se è necessario risolvere questo problema interrogando l'interfaccia, è possibile testare gli argomenti per il sottotipo "più basso". Ad esempio, in un meta-livello:

  1. Raccogliere tutti i due metodi con lo stesso nome

  2. Raccogliere il tipo di argomento: Method.getParameterTypes()[0]

  3. Usa Class.isAssignableFrom(Class). Il metodo restituisce true se l'argomento è lo stesso o una sottoclasse della classe su cui viene chiamato il metodo.

  4. Utilizzare il metodo con l'argomento sottoclasse dell'argomento del metodo diverso.

+0

L'ultima parte che hai suggerito ovviamente non funziona: cosa succederebbe se fosse una semplice interfaccia non generica come 'interface ITestDouble {int op (Double); int op (numero); } '? Bel tentativo però :) – Saintali

+0

I lavori per la domanda posta. Inoltre può essere esteso per due o più parametri. Ci saranno due metodi con lo stesso nome e uno sarà il generico completo, l'altro sarà quello concreto. Quindi, di nuovo, scegli il metodo con l'argomento che è la sottoclasse dello stesso argomento nell'altro metodo. Almeno un argomento avrà questa proprietà. –

+0

Bel riepilogo, ma l'algoritmo di sketch sembra un po 'ingenuo ... ok funziona con i metodi 'op', ma in generale la situazione sembra essere più complessa (ad esempio parametri più generici). – Flavio

Problemi correlati