Guida alle utilità di riflessione di Guava

1. Panoramica

In questo articolo, esamineremo l' API di riflessione Guava , che è decisamente più versatile rispetto all'API di riflessione Java standard.

Useremo Guava per acquisire tipi generici in fase di esecuzione e faremo un buon uso anche di Invokable .

2. Acquisizione del tipo generico in fase di esecuzione

In Java, i generici vengono implementati con la cancellazione del tipo. Ciò significa che le informazioni sul tipo generico sono disponibili solo in fase di compilazione e, in fase di esecuzione, non sono più disponibili.

Ad esempio, List, le informazioni sul tipo generico vengono cancellate in fase di esecuzione. Per questo motivo, non è sicuro passare oggetti Class generici in fase di esecuzione.

Potremmo finire per assegnare due elenchi che hanno diversi tipi generici allo stesso riferimento, il che chiaramente non è una buona idea:

List stringList = Lists.newArrayList(); List intList = Lists.newArrayList(); boolean result = stringList.getClass() .isAssignableFrom(intList.getClass()); assertTrue(result);

A causa della cancellazione del tipo, il metodo isAssignableFrom () non può conoscere il tipo generico effettivo degli elenchi. Fondamentalmente confronta due tipi che sono solo un elenco senza alcuna informazione sul tipo effettivo.

Utilizzando l'API di riflessione Java standard possiamo rilevare i tipi generici di metodi e classi. Se disponiamo di un metodo che restituisce un List , possiamo usare la reflection per ottenere il tipo restituito di quel metodo: un ParameterizedType che rappresenta List .

La classe TypeToken utilizza questa soluzione alternativa per consentire la manipolazione di tipi generici. Possiamo utilizzare la classe TypeToken per acquisire un tipo effettivo di elenco generico e verificare se è possibile fare riferimento realmente allo stesso riferimento:

TypeToken
    
      stringListToken = new TypeToken
     
      () {}; TypeToken
      
        integerListToken = new TypeToken
       
        () {}; TypeToken
        
          numberTypeToken = new TypeToken
         
          () {}; assertFalse(stringListToken.isSubtypeOf(integerListToken)); assertFalse(numberTypeToken.isSubtypeOf(integerListToken)); assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
         
        
       
      
     
    

Solo integerListToken può essere assegnato a un riferimento di tipo nubmerTypeToken perché una classe Integer estende una classe Number .

3. Acquisizione di tipi complessi utilizzando TypeToken

Diciamo che vogliamo creare una classe parametrizzata generica e vogliamo avere informazioni su un tipo generico in fase di esecuzione. Possiamo creare una classe che abbia un TypeToken come campo per acquisire tali informazioni:

abstract class ParametrizedClass { TypeToken type = new TypeToken(getClass()) {}; }

Quindi, quando si crea un'istanza di quella classe, il tipo generico sarà disponibile in fase di esecuzione:

ParametrizedClass parametrizedClass = new ParametrizedClass() {}; assertEquals(parametrizedClass.type, TypeToken.of(String.class));

Possiamo anche creare un TypeToken di un tipo complesso che ha più di un tipo generico e recuperare le informazioni su ciascuno di questi tipi in fase di esecuzione:

TypeToken
    
      funToken = new TypeToken
     
      () {}; TypeToken funResultToken = funToken .resolveType(Function.class.getTypeParameters()[1]); assertEquals(funResultToken, TypeToken.of(String.class));
     
    

Otteniamo un tipo di ritorno effettivo per Function , ovvero una stringa. Possiamo anche ottenere un tipo di voce nella mappa:

TypeToken mapToken = new TypeToken() {}; TypeToken entrySetToken = mapToken .resolveType(Map.class.getMethod("entrySet") .getGenericReturnType()); assertEquals( entrySetToken, new TypeToken
      
       >() {}); 
      

Qui usiamo un metodo di riflessione getMethod () dalla libreria standard Java per catturare il tipo di ritorno di un metodo.

4. Invokable

L'invokable è un wrapper fluente di java.lang.reflect.Method e java.lang.reflect.Constructor . Fornisce un'API più semplice sopra un'API di riflessione Java standard . Diciamo che abbiamo una classe che ha due metodi pubblici e uno di questi è finale:

class CustomClass { public void somePublicMethod() {} public final void notOverridablePublicMethod() {} }

Ora esaminiamo somePublicMethod () utilizzando l'API Guava e l' API di riflessione standard Java :

Method method = CustomClass.class.getMethod("somePublicMethod"); Invokable invokable = new TypeToken() {} .method(method); boolean isPublicStandradJava = Modifier.isPublic(method.getModifiers()); boolean isPublicGuava = invokable.isPublic(); assertTrue(isPublicStandradJava); assertTrue(isPublicGuava);

Non c'è molta differenza tra queste due varianti, ma controllare se un metodo è sovrascrivibile è un'attività davvero non banale in Java. Fortunatamente, il metodo isOverridable () della classe Invokable lo rende più semplice:

Method method = CustomClass.class.getMethod("notOverridablePublicMethod"); Invokable invokable = new TypeToken() {}.method(method); boolean isOverridableStandardJava = (!(Modifier.isFinal(method.getModifiers()) || Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers()) || Modifier.isFinal(method.getDeclaringClass().getModifiers()))); boolean isOverridableFinalGauava = invokable.isOverridable(); assertFalse(isOverridableStandardJava); assertFalse(isOverridableFinalGauava);

Vediamo che anche un'operazione così semplice richiede molti controlli utilizzando l' API di riflessione standard . La classe Invokable lo nasconde dietro l'API che è semplice da usare e molto concisa.

5. conclusione

In questo articolo, abbiamo esaminato l'API di riflessione Guava e l'abbiamo confrontata con Java standard. Abbiamo visto come acquisire tipi generici in fase di esecuzione e come la classe Invokable fornisce API eleganti e facili da usare per il codice che utilizza la reflection.

L'implementazione di tutti questi esempi e frammenti di codice può essere trovata nel progetto GitHub: questo è un progetto Maven, quindi dovrebbe essere facile da importare ed eseguire così com'è.