1. Обзор
В этой статье мы рассмотрим API-интерфейс отражения
Guava
, который определенно более универсален по сравнению со стандартным API-интерфейсом отражения Java. ``
Мы будем использовать Guava
для захвата универсальных типов во время выполнения, а также будем эффективно использовать Invokable
.
2. Захват универсального типа во время выполнения
В Java дженерики реализованы с удалением типа. Это означает, что информация об универсальном типе доступна только во время компиляции, а во время выполнения она больше недоступна.
Например, List<String>
информация об универсальном типе стирается во время выполнения . Из-за этого небезопасно передавать общие объекты класса во время выполнения.
В итоге мы можем присвоить два списка с разными универсальными типами одной и той же ссылке, что явно не очень хорошая идея:
List<String> stringList = Lists.newArrayList();
List<Integer> intList = Lists.newArrayList();
boolean result = stringList.getClass()
.isAssignableFrom(intList.getClass());
assertTrue(result);
Из-за стирания типа метод isAssignableFrom()
не может знать фактический универсальный тип списков. В основном он сравнивает два типа, которые представляют собой просто список
без какой-либо информации о фактическом типе.
Используя стандартный API отражения Java, мы можем определить общие типы методов и классов. Если у нас есть метод, который возвращает List<String>
, мы можем использовать отражение, чтобы получить возвращаемый тип этого метода — ParameterizedType
, представляющий List<String>
.
Класс TypeToken
использует этот обходной путь, чтобы разрешить манипулирование универсальными типами. Мы можем использовать класс TypeToken
для захвата фактического типа универсального списка и проверки, действительно ли на них можно ссылаться по одной и той же ссылке:
TypeToken<List<String>> stringListToken
= new TypeToken<List<String>>() {};
TypeToken<List<Integer>> integerListToken
= new TypeToken<List<Integer>>() {};
TypeToken<List<? extends Number>> numberTypeToken
= new TypeToken<List<? extends Number>>() {};
assertFalse(stringListToken.isSubtypeOf(integerListToken));
assertFalse(numberTypeToken.isSubtypeOf(integerListToken));
assertTrue(integerListToken.isSubtypeOf(numberTypeToken));
Ссылке типа nubmerTypeToken
можно присвоить только integerListToken
, поскольку класс Integer
расширяет класс Number
.
3. Захват сложных типов с помощью TypeToken
Допустим, мы хотим создать универсальный параметризованный класс и хотим иметь информацию об универсальном типе во время выполнения. Мы можем создать класс с TypeToken
в качестве поля для сбора этой информации:
abstract class ParametrizedClass<T> {
TypeToken<T> type = new TypeToken<T>(getClass()) {};
}
Затем при создании экземпляра этого класса универсальный тип будет доступен во время выполнения:
ParametrizedClass<String> parametrizedClass = new ParametrizedClass<String>() {};
assertEquals(parametrizedClass.type, TypeToken.of(String.class));
Мы также можем создать TypeToken
сложного типа, который имеет более одного универсального типа, и получать информацию о каждом из этих типов во время выполнения:
TypeToken<Function<Integer, String>> funToken
= new TypeToken<Function<Integer, String>>() {};
TypeToken<?> funResultToken = funToken
.resolveType(Function.class.getTypeParameters()[1]);
assertEquals(funResultToken, TypeToken.of(String.class));
Мы получаем фактический возвращаемый тип для Function
, то есть String.
Мы даже можем получить тип записи на карте:
TypeToken<Map<String, Integer>> mapToken
= new TypeToken<Map<String, Integer>>() {};
TypeToken<?> entrySetToken = mapToken
.resolveType(Map.class.getMethod("entrySet")
.getGenericReturnType());
assertEquals(
entrySetToken,
new TypeToken<Set<Map.Entry<String, Integer>>>() {});
Здесь мы используем метод отражения getMethod()
из стандартной библиотеки Java для захвата возвращаемого типа метода.
4. Призываемый
Invokable — это свободная
оболочка java.lang.reflect.Method
и java.lang.reflect.Constructor
. Он предоставляет более простой API поверх стандартного API отражения
Java . Допустим, у нас есть класс с двумя публичными методами и один из них final:
class CustomClass {
public void somePublicMethod() {}
public final void notOverridablePublicMethod() {}
}
Теперь давайте рассмотрим somePublicMethod()
с использованием API Guava и стандартного API отражения
Java :
Method method = CustomClass.class.getMethod("somePublicMethod");
Invokable<CustomClass, ?> invokable
= new TypeToken<CustomClass>() {}
.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<CustomClass, ?> invokable
= new TypeToken<CustomClass>() {}.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);
Мы видим, что даже такая простая операция требует множества проверок с использованием стандартного API рефлексии .
Класс Invokable
скрывает это за простым в использовании и очень лаконичным API.
5. Вывод
В этой статье мы рассмотрели API отражения Guava и сравнили его со стандартной Java. Мы увидели, как захватывать универсальные типы во время выполнения и как класс Invokable
предоставляет элегантный и простой в использовании API для кода, использующего отражение.
Реализацию всех этих примеров и фрагментов кода можно найти в проекте GitHub — это проект Maven, поэтому его должно быть легко импортировать и запускать как есть.