Guia dos utilitários de reflexão da goiaba
1. Visão geral
Neste artigo, veremos a APIGuavareflection - que é definitivamente mais versátil em comparação com a API de reflexão Java padrão.
Estaremos usandoGuava para capturar tipos genéricos em tempo de execução e faremos bom uso deInvokable também.
2. Capturando tipo genérico em tempo de execução
In Java, generics are implemented with type erasure. Isso significa que as informações do tipo genérico estão disponíveis apenas em tempo de compilação e, em tempo de execução - não estão mais disponíveis.
Por exemplo,List<String>, as informações sobre o tipo genérico obtémerased at runtime. Devido a esse fato, não é seguro passar objetos genéricosClass em tempo de execução.
Podemos acabar atribuindo duas listas que têm tipos genéricos diferentes à mesma referência, o que claramente não é uma boa ideia:
List stringList = Lists.newArrayList();
List intList = Lists.newArrayList();
boolean result = stringList.getClass()
.isAssignableFrom(intList.getClass());
assertTrue(result);
Devido ao apagamento do tipo, o métodoisAssignableFrom() não pode saber o tipo genérico real das listas. Ele basicamente compara dois tipos que são apenasList sem nenhuma informação sobre o tipo real.
Usando a API de reflexão Java padrão, podemos detectar os tipos genéricos de métodos e classes. Se tivermos um método que retorna umList<String>, podemos usar reflexão para obter o tipo de retorno desse método - umParameterizedType representandoList<String>.
A classeTypeToken usa essa solução alternativa para permitir a manipulação de tipos genéricos. Podemos usar a classeTypeToken para capturar um tipo real de lista genérica e verificar se eles realmente podem ser referenciados pela mesma referência:
TypeToken> stringListToken
= new TypeToken>() {};
TypeToken> integerListToken
= new TypeToken>() {};
TypeToken> numberTypeToken
= new TypeToken>() {};
assertFalse(stringListToken.isSubtypeOf(integerListToken));
assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
Apenas ointegerListToken pode ser atribuído a uma referência do tiponubmerTypeToken porque uma classeInteger estende uma classeNumber.
3. Capturando tipos complexos usandoTypeToken
Digamos que queremos criar uma classe parametrizada genérica, e queremos ter informações sobre um tipo genérico em tempo de execução. Podemos criar uma classe que tenha umTypeToken como um campo para capturar essas informações:
abstract class ParametrizedClass {
TypeToken type = new TypeToken(getClass()) {};
}
Em seguida, ao criar uma instância dessa classe, o tipo genérico estará disponível no tempo de execução:
ParametrizedClass parametrizedClass = new ParametrizedClass() {};
assertEquals(parametrizedClass.type, TypeToken.of(String.class));
Também podemos criar umTypeToken de um tipo complexo que tem mais de um tipo genérico e recuperar informações sobre cada um desses tipos em tempo de execução:
TypeToken> funToken
= new TypeToken>() {};
TypeToken> funResultToken = funToken
.resolveType(Function.class.getTypeParameters()[1]);
assertEquals(funResultToken, TypeToken.of(String.class));
Obtemos um tipo de retorno real paraFunction, que é umString.. Podemos até obter um tipo de entrada no mapa:
TypeToken
Aqui, usamos um método de reflexãogetMethod() da biblioteca padrão Java para capturar o tipo de retorno de um método.
4. Invokable
OInvokable é um envoltório fluente dejava.lang.reflect.Methodejava.lang.reflect.Constructor. Ele fornece uma API mais simples em cima de uma API Javareflection padrão. Digamos que temos uma classe que possui dois métodos públicos e um deles é final:
class CustomClass {
public void somePublicMethod() {}
public final void notOverridablePublicMethod() {}
}
Agora vamos examinar osomePublicMethod() usando a API Guava e a API Java padrãoreflection:
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);
Não há muita diferença entre essas duas variantes, mas verificar se um método é substituível é uma tarefa realmente não trivial em Java. Felizmente, o métodoisOverridable() da classeInvokable torna mais fácil:
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);
Vemos que mesmo uma operação tão simples precisa de muitas verificações usando a APIreflection padrão. A classeInvokable esconde isso por trás da API que é simples de usar e muito concisa.
5. Conclusão
Neste artigo, analisamos a API de reflexão do Guava e a comparamos com o Java padrão. Vimos como capturar tipos genéricos em tempo de execução e como a classeInvokable fornece uma API elegante e fácil de usar para código que está usando reflexão.
A implementação de todos esses exemplos e trechos de código pode ser encontrada emGitHub project - este é um projeto Maven, portanto, deve ser fácil de importar e executar como está.