应如何实现类型类型层次结构类型?

2022-09-04 03:48:08

将泛型添加到 1.5 时,添加了一个具有各种子类型的接口来表示类型。 经过改装以针对 1.5 之前的版本实施。 子类型可用于 1.5 中的新泛型类型。java.lang.reflectTypeClassTypeType

这一切都很好,很好。有点尴尬,因为必须下定决心才能做任何有用的事情,但可以通过试验,错误,摆弄和(自动)测试来做。除了在实施方面...Type

应该如何实施,如何实施。子类型的 API 描述说:equalshashCodeParameterizedTypeType

实现此接口的类的实例必须实现一个 equals() 方法,该方法等于共享相同泛型类型声明并具有相等类型参数的任意两个实例。

(我猜这意味着,但不是??)getActualTypeArgumentsgetRawTypegetOwnerType

我们从总合同中知道也必须实现,但是似乎没有规范这种方法应该产生什么值。java.lang.ObjecthashCode

其他子类型 似乎都没有提到 或 ,除了每个值具有不同的实例之外。TypeequalshashCodeClass

那么我应该在我的和中放入什么?equalshashCode

(如果您想知道,我正在尝试用类型参数替换实际类型。因此,如果我知道在运行时是那么我想替换s,所以成为,成为,(可能发生!)成为,等等。TypeVariable<?>TClass<?>StringTypeList<T>List<String>T[]String[]List<T>[]List<String>[]

还是我必须创建自己的并行类型类型层次结构(不因假定的法律原因而重复)?(有图书馆吗?Type

编辑:关于我为什么需要这个,有几个问题。事实上,为什么要看泛型类型信息呢?

我从非泛型类/接口类型开始。(如果需要参数化类型,则始终可以使用新类添加间接寻址层。然后,我正在遵循字段或方法。这些可能引用参数化类型。只要他们不使用通配符,我仍然可以在面对诸如.List<String>T

通过这种方式,我可以用高质量的静态打字做任何事情。这些动态类型均未在视线范围内进行检查。instanceof

在我的情况下,具体用法是序列化。但它可以适用于反射的任何其他合理使用,例如测试。

我用于下面的替换的当前代码状态。 是 .按“原样”显示快照。无论如何都没有整理(如果你不相信我)。typeMapMap<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;
      }
   }

答案 1

10年来,我一直在以不令人满意的方式解决这个问题。首先是Guice的MoreTypes.java,用Gson的GsonTypes复制粘贴和修改.java,然后再次在Moshi的Util.java中。

Moshi有我最好的方法,这并不是说它很好。

你不能在 Type 的任意实现上调用 equals() 并期望它工作。

这是因为 Java Types API 提供了多种不兼容的方法来对简单类的数组进行建模。您可以将 或 作为其组件类型为 的 。我相信你会从对类型字段的反射中获得前者,而从反射中获得后者作为类型字段的参数。Date[]Class<Date[]>GenericArrayTypeDateDate[]List<Date[]>

未指定哈希代码。

我还开始致力于实现Android使用的这些类。Android的早期版本与Java具有不同的哈希代码,但是您今天在野外找到的所有内容都使用与Java相同的哈希代码。

toString 方法不好

如果您在错误消息中使用类型,那么必须编写特殊代码才能很好地打印它们,这很糟糕。

复制粘贴并悲伤

我的建议是不要将 equals() + hashCode() 与未知 Type 实现一起使用。使用规范化函数转换为特定的已知实现,并仅在您控制的实现中进行比较。


答案 2

下面是一个直接依赖于 Sun API 和反射的小实验(即,它使用反射来处理实现反射的类):

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

by in 的替换结果如下所示:EStringList<E>

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

主要思想如下:

  • 获取课程sun.reflect.generics.reflectiveObjects.XyzImpl
  • 获取他们的构造函数,确保他们是accessible
  • 将构造函数调用包装在帮助器方法中.newInstance
  • 在一个名为的简单递归方法中使用帮助器方法,该方法使用由具体类型替换的类型变量重新生成 -表达式。substituteTypeVariableType

我没有实现每个案例,但它也应该适用于更复杂的嵌套类型(因为递归调用)。substituteTypeVariable

编译器并不喜欢这种方法,它会生成有关内部 Sun API 使用情况的警告:

警告:ParameterizedTypeImpl 是内部专有 API,可能会在将来的发行版中删除

但是,有一个@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)
  }
}