Перейти к основному содержимому

Руководство по утилитам отражения в Guava

· 4 мин. чтения

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, поэтому его должно быть легко импортировать и запускать как есть.