Как должны быть реализованы типы иерархии типов? - PullRequest
0 голосов
/ 27 декабря 2018

Когда дженерики были добавлены в 1.5, java.lang.reflect добавил интерфейс Type с различными подтипами для представления типов.Class модернизирован для реализации Type для типов до 1.5.Type доступны подтипы для новых типов универсального типа от 1,5.

Это все хорошо.Немного неловко, так как Type должен быть опущен, чтобы сделать что-нибудь полезное, но выполнимое с пробным, ошибочным, изменчивым и (автоматическим) тестированием.За исключением случаев, когда речь идет о реализации ...

Как должны быть реализованы equals и hashCode.Описание API для подтипа ParameterizedType для Type гласит:

Экземпляры классов, которые реализуют этот интерфейс, должны реализовывать метод equals (), который приравнивает любые два экземпляра, которые разделяют одно и то же объявление универсального типа.и имеют одинаковые параметры типа.

(я думаю, это означает getActualTypeArguments и getRawType, но не getOwnerType ??)

Мы знаем из общего контракта java.lang.Object что hashCode также должно быть реализовано, но, по-видимому, нет никакой спецификации относительно того, какие значения должен выдавать этот метод.

Ни один из других подтипов Type, по-видимому, не упоминает equals или hashCode, кроме этого Class имеет различные экземпляры на значение.

Так что я могу вставить в свои equals и hashCode?

(Если вам интересно, я пытаюсьподстановить параметры типа для реальных типов. Поэтому, если я знаю, во время выполнения TypeVariable<?> T равно Class<?> String, тогда я хочу заменить Type s, поэтому List<T> становится List<String>, T[] становится String[], List<T>[] (может случиться!) СтановитсяList<String>[] и т. Д.)

Или мне нужно создать собственную иерархию параллельных типов (без дублирования Type по предполагаемым правовым причинам)?(Есть ли библиотека?)

Редактировать: Было несколько вопросов о том, зачем мне это нужно.Действительно, зачем вообще смотреть на информацию о типовых типах?

Я начинаю с неуниверсального типа класса / интерфейса.(Если вам нужны параметризованные типы, такие как List<String>, вы всегда можете добавить слой косвенности с новым классом.) Затем я следую за полями или методами.Они могут ссылаться на параметризованные типы.До тех пор, пока они не используют подстановочные знаки, я все еще могу определять реальные статические типы, когда сталкиваюсь с подобными T.

Таким образом, я могу делать все с помощью высококачественной статической типизации.Ни одна из этих instanceof динамических проверок типов в поле зрения.

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

Текущее состояние кода, который я использую для замены ниже.typeMap является Map<String,Type>.Представлено как снимок «как есть».Во всяком случае, не прибрано (throw null;, если вы мне не верите).

   Type substitute(Type type) {
      if (type instanceof TypeVariable<?>) {
         Type actualType = typeMap.get(((TypeVariable<?>)type).getName());
         if (actualType instanceof TypeVariable<?>) { throw null; }
         if (actualType == null) {
            throw new IllegalArgumentException("Type variable not found");
         } else if (actualType instanceof TypeVariable<?>) {
            throw new IllegalArgumentException("TypeVariable shouldn't substitute for a TypeVariable");
         } else {
            return actualType;
         }
      } else if (type instanceof ParameterizedType) {
         ParameterizedType parameterizedType = (ParameterizedType)type;
         Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
         int len = actualTypeArguments.length;
         Type[] actualActualTypeArguments = new Type[len];
         for (int i=0; i<len; ++i) {
            actualActualTypeArguments[i] = substitute(actualTypeArguments[i]);
         }
         // This will always be a Class, wont it? No higher-kinded types here, thank you very much.
         Type actualRawType = substitute(parameterizedType.getRawType());
         Type actualOwnerType = substitute(parameterizedType.getOwnerType());
         return new ParameterizedType() {
            public Type[] getActualTypeArguments() {
               return actualActualTypeArguments.clone();
            }
            public Type getRawType() {
               return actualRawType;
            }
            public Type getOwnerType() {
               return actualOwnerType;
            }
            // Interface description requires equals method.
            @Override public boolean equals(Object obj) {
               if (!(obj instanceof ParameterizedType)) {
                  return false;
               }
               ParameterizedType other = (ParameterizedType)obj;
               return
                   Arrays.equals(this.getActualTypeArguments(), other.getActualTypeArguments()) &&
                   this.getOwnerType().equals(other.getOwnerType()) &&
                   this.getRawType().equals(other.getRawType());
            }
         };
      } else if (type instanceof GenericArrayType) {
         GenericArrayType genericArrayType = (GenericArrayType)type;
         Type componentType = genericArrayType.getGenericComponentType();
         Type actualComponentType = substitute(componentType);
         if (actualComponentType instanceof TypeVariable<?>) { throw null; }
         return new GenericArrayType() {
            // !! getTypeName? toString? equals? hashCode?
            public Type getGenericComponentType() {
               return actualComponentType;
            }
            // Apparently don't have to provide an equals, but we do need to.
            @Override public boolean equals(Object obj) {
               if (!(obj instanceof GenericArrayType)) {
                  return false;
               }
               GenericArrayType other = (GenericArrayType)obj;
               return
                   this.getGenericComponentType().equals(other.getGenericComponentType());
            }
         };
      } else {
         return type;
      }
   }

Ответы [ 2 ]

0 голосов
/ 04 января 2019

Я 10 лет безуспешно решал эту проблему.Сначала с Guice's MoreTypes.java, скопированный и исправленный с Gson's GsonTypes.java, и снова с Moshi's Util.java.

У Моши лучший подход, а это не значит, что он хорош.

Вы не можете вызвать equals() для произвольных реализаций Type и ожидать, что он будет работать.

Это связано с тем, что API типов Java предлагают несколько несовместимых способов моделирования массивов простых классов.Вы можете сделать Date[] как Class<Date[]> или GenericArrayType с типом компонента Date.Я полагаю, что вы получите первое из отражения от поля типа Date[], а второе от отражения как параметр поля типа List<Date[]>.

Хеш-коды не являютсяуказано.

Я также начал работать над реализацией этих классов, которые использует Android.В самых ранних версиях Android хэш-коды отличаются от Java, но все, что вы найдете сегодня, использует те же хеш-коды, что и Java.

Методы toString не годятся

Если вы используете типы в сообщениях об ошибках, отстойно, что вам нужно написать специальный код, чтобы красиво их печатать.

Копировать Paste and Be Sad

Моя рекомендация - не использовать equals () + hashCode () с неизвестными реализациями Type.Используйте функцию канонизации для преобразования в конкретную известную реализацию и сравнивайте ее только с теми, которыми вы управляете.

0 голосов
/ 02 января 2019

Вот небольшой эксперимент, который опирается непосредственно на API-интерфейс Sun и отражение (то есть использует отражение для работы с классами, которые реализуют отражение):

import java.lang.Class;
import java.lang.reflect.*;
import java.util.Arrays;
import sun.reflect.generics.reflectiveObjects.*;

class Types {

  private static Constructor<ParameterizedTypeImpl> PARAMETERIZED_TYPE_CONS =
    ((Constructor<ParameterizedTypeImpl>)
      ParameterizedTypeImpl
      .class
      .getDeclaredConstructors()
      [0]
    );

  static {
      PARAMETERIZED_TYPE_CONS.setAccessible(true);
  }

  /** 
   * Helper method for invocation of the 
   *`ParameterizedTypeImpl` constructor. 
   */
  public static ParameterizedType parameterizedType(
    Class<?> raw,
    Type[] paramTypes,
    Type owner
  ) {
    try {
      return PARAMETERIZED_TYPE_CONS.newInstance(raw, paramTypes, owner);
    } catch (Exception e) {
      throw new Error("TODO: better error handling", e);
    }
  }

  // (similarly for `GenericArrayType`, `WildcardType` etc.)

  /** Substitution of type variables. */
  public static Type substituteTypeVariable(
    final Type inType,
    final TypeVariable<?> variable,
    final Type replaceBy
  ) {
    if (inType instanceof TypeVariable<?>) {
      return replaceBy;
    } else if (inType instanceof ParameterizedType) {
      ParameterizedType pt = (ParameterizedType) inType;
      return parameterizedType(
        ((Class<?>) pt.getRawType()),
        Arrays.stream(pt.getActualTypeArguments())
          .map((Type x) -> substituteTypeVariable(x, variable, replaceBy))
          .toArray(Type[]::new),
        pt.getOwnerType()
      );
    } else {
      throw new Error("TODO: all other cases");
    }
  }

  // example
  public static void main(String[] args) throws InstantiationException {

    // type in which we will replace a variable is `List<E>`
    Type t = 
      java.util.LinkedList
      .class
      .getGenericInterfaces()
      [0];

    // this is the variable `E` (hopefully, stability not guaranteed)
    TypeVariable<?> v = 
      ((Class<?>)
        ((ParameterizedType) t)
        .getRawType()
      )
      .getTypeParameters()
      [0];

    // This should become `List<String>`
    Type s = substituteTypeVariable(t, v, String.class);

    System.out.println("before: " + t);
    System.out.println("after:  " + s);
  }
}

Результат подстановки Eпо String в List<E> выглядит следующим образом:

before: java.util.List<E>
after:  java.util.List<java.lang.String>

Основная идея заключается в следующем:

  • Получить sun.reflect.generics.reflectiveObjects.XyzImpl классов
  • Получитьих конструкторы, убедитесь, что они accessible
  • Оберните конструктор .newInstance вызовов в вспомогательных методах
  • Используйте вспомогательные методы в простом рекурсивном методе под названием substituteTypeVariable, который перестраивает Type -выражения с переменными типа, заменяемыми конкретными типами.

Я не реализовывал каждый отдельный случай, но он должен работать и с более сложными вложенными типами (из-за рекурсивного вызова substituteTypeVariable).

Компилятору не очень нравится этот подход, он генерирует предупреждения об использовании внутреннего API Sun:

предупреждение: ParameterizedTypeImpl - это внутренняя поддержкаи может быть удален в будущем выпуске

, но для этого есть @SuppressWarnings .

Приведенный выше код Java был полученперевод следующего небольшого фрагмента Scala (вот почему код Java может выглядеть немного странным и не совсем Java-идиоматическим):

object Types {

  import scala.language.existentials // suppress warnings
  import java.lang.Class
  import java.lang.reflect.{Array => _, _}
  import sun.reflect.generics.reflectiveObjects._

  private val ParameterizedTypeCons = 
    classOf[ParameterizedTypeImpl]
    .getDeclaredConstructors
    .head
    .asInstanceOf[Constructor[ParameterizedTypeImpl]]

  ParameterizedTypeCons.setAccessible(true)

  /** Helper method for invocation of the `ParameterizedTypeImpl` constructor. */
  def parameterizedType(raw: Class[_], paramTypes: Array[Type], owner: Type)
  : ParameterizedType = {
    ParameterizedTypeCons.newInstance(raw, paramTypes, owner)
  }

  // (similarly for `GenericArrayType`, `WildcardType` etc.)

  /** Substitution of type variables. */
  def substituteTypeVariable(
    inType: Type,
    variable: TypeVariable[_],
    replaceBy: Type
  ): Type = {
    inType match {
      case v: TypeVariable[_] => replaceBy
      case pt: ParameterizedType => parameterizedType(
        pt.getRawType.asInstanceOf[Class[_]],
        pt.getActualTypeArguments.map(substituteTypeVariable(_, variable, replaceBy)),
        pt.getOwnerType
      )
      case sthElse => throw new NotImplementedError()
    }
  }

  // example
  def main(args: Array[String]): Unit = {

    // type in which we will replace a variable is `List<E>`
    val t = 
      classOf[java.util.LinkedList[_]]
      .getGenericInterfaces
      .head

    // this is the variable `E` (hopefully, stability not guaranteed)
    val v = 
      t
      .asInstanceOf[ParameterizedType]
      .getRawType
      .asInstanceOf[Class[_]]          // should be `List<E>` with parameter
      .getTypeParameters
      .head                            // should be `E`

    // This should become `List<String>`
    val s = substituteTypeVariable(t, v, classOf[String])

    println("before: " + t)
    println("after:  " + s)
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...