Руководство по утилизации отражений в Гуаве

Руководство по утилизации отражений в Гуаве

1. обзор

В этой статье мы рассмотрим APIGuavareflection, который определенно более универсален по сравнению со стандартным API отражения Java.

Мы будем использоватьGuava для захвата общих типов во время выполнения, а такжеInvokable.

2. Захват универсального типа во время выполнения

In Java, generics are implemented with type erasure. Это означает, что информация об общем типе доступна только во время компиляции, а во время выполнения она больше не доступна.

Например,List<String>, информация об универсальном типе получаетerased at runtime. В связи с этим небезопасно передавать общие объектыClass во время выполнения.

Мы могли бы в конечном итоге назначить два списка, которые имеют разные универсальные типы для одной и той же ссылки, что явно не очень хорошая идея:

List stringList = Lists.newArrayList();
List intList = Lists.newArrayList();

boolean result = stringList.getClass()
  .isAssignableFrom(intList.getClass());

assertTrue(result);

Из-за стирания типа методisAssignableFrom() не может знать фактический универсальный тип списков. Он в основном сравнивает два типа, которые представляют собой простоList, без какой-либо информации о фактическом типе.

Используя стандартный API отражения Java, мы можем обнаружить универсальные типы методов и классов. Если у нас есть метод, который возвращаетList<String>, мы можем использовать отражение, чтобы получить тип возвращаемого значения этого метода -ParameterizedType, представляющийList<String>.

КлассTypeToken использует этот обходной путь, чтобы разрешить манипулирование универсальными типами. Мы можем использовать классTypeToken для захвата фактического типа общего списка и проверки, действительно ли на них можно ссылаться по той же ссылке:

TypeToken> stringListToken
  = new TypeToken>() {};
TypeToken> integerListToken
  = new TypeToken>() {};
TypeToken> numberTypeToken
  = new TypeToken>() {};

assertFalse(stringListToken.isSubtypeOf(integerListToken));
assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
assertTrue(integerListToken.isSubtypeOf(numberTypeToken));

ТолькоintegerListToken может быть назначен ссылке типаnubmerTypeToken, потому что классInteger расширяет классNumber.

3. Захват сложных типов с использованиемTypeToken

Допустим, мы хотим создать универсальный параметризованный класс и хотим иметь информацию об универсальном типе во время выполнения. Мы можем создать класс с полемTypeToken для сбора этой информации:

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

Затем при создании экземпляра этого класса универсальный тип будет доступен во время выполнения:

ParametrizedClass parametrizedClass = new ParametrizedClass() {};

assertEquals(parametrizedClass.type, TypeToken.of(String.class));

Мы также можем создатьTypeToken сложного типа, который имеет более одного универсального типа, и получить информацию о каждом из этих типов во время выполнения:

TypeToken> funToken
  = new TypeToken>() {};

TypeToken funResultToken = funToken
  .resolveType(Function.class.getTypeParameters()[1]);

assertEquals(funResultToken, TypeToken.of(String.class));

Мы получаем реальный тип возвращаемого значения дляFunction, то естьString.. Мы даже можем получить тип записи на карте:

TypeToken> mapToken
  = new TypeToken>() {};

TypeToken entrySetToken = mapToken
  .resolveType(Map.class.getMethod("entrySet")
  .getGenericReturnType());

assertEquals(
  entrySetToken,
  new TypeToken>>() {});

Здесь мы используем метод отраженияgetMethod() из стандартной библиотеки Java для захвата возвращаемого типа метода.

4. Invokableс

Invokable - это плавная оберткаjava.lang.reflect.Method иjava.lang.reflect.Constructor. Он предоставляет более простой API поверх стандартного API Javareflection. Допустим, у нас есть класс с двумя общедоступными методами, один из которых является final:

class CustomClass {
    public void somePublicMethod() {}

    public final void notOverridablePublicMethod() {}
}

Теперь давайте рассмотримsomePublicMethod(), используя API Guava и стандартный Java APIreflection:

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

Между этими двумя вариантами нет большой разницы, но проверка, является ли метод перезаписываемым, является действительно нетривиальной задачей в Java. К счастью, методisOverridable() из классаInvokable упрощает задачу:

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

Мы видим, что даже такая простая операция требует множества проверок с использованием стандартного APIreflection. КлассInvokable скрывает это за API, простым в использовании и очень кратким.

5. Заключение

В этой статье мы рассмотрели API отражения Guava и сравнили его со стандартным Java. Мы увидели, как захватывать общие типы во время выполнения и как классInvokable предоставляет элегантный и простой в использовании API для кода, использующего отражение.

Реализация всех этих примеров и фрагментов кода можно найти вGitHub project - это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.