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

Tom*_*ine 14 java reflection

当泛型添加到1.5时,java.lang.reflect添加了一个Type具有各种子类型的接口来表示类型.Class改装为Type1.5前类型的实施.Type子类型可用于1.5的新类型泛型类型.

这一切都很好.有点尴尬,Type不得不做一些有用的事情,但可以尝试试用,错误,摆弄和(自动)测试.除了涉及实施......

应该怎样equalshashCode实施.ParameterizedType子类型的API描述Type说:

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

(我想这意味着getActualTypeArgumentsgetRawType,但不getOwnerType?)

我们从一般合同中知道,java.lang.ObjecthashCode也必须实施,但似乎没有规定这种方法应该产生什么价值.

Type似乎没有提到其他子类型,equals或者hashCode除了Class每个值具有不同的实例之外.

所以,我该怎么把我equalshashCode

(如果你想知道,我试图替代类型参数的实际类型.所以,如果我知道在运行时 TypeVariable<?> TClass<?> String话,我想替换TypeS,所以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;
      }
   }
Run Code Online (Sandbox Code Playgroud)

Jes*_*son 5

10年来,我一直以不满意的方式解决这个问题.先用Guice'sMoreTypes.java,用GsonGsonTypes.java复制粘贴和修改,再用Moshi'sUtil.java复制.

莫希有我最好的方法,这并不是说它很好.

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

这是因为Java Types API提供了多种不兼容的方法来为简单类的数组建模.你可以让一个Date[]作为Class<Date[]>或作为GenericArrayType其组件类型Date.我相信你会从反射中得到前者的类型Date[],而后者则从反射作为类型字段的参数List<Date[]>.

未指定哈希码.

我还开始研究Android使用的这些类的实现.很早版本的Android与Java有不同的哈希码,但是今天你会发现的所有内容都使用与Java相同的哈希码.

toString方法并不好

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

复制粘贴和悲伤

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


And*_*kin 4

下面是一个直接依赖于 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);
  }
}
Run Code Online (Sandbox Code Playgroud)

Eby的替换结果String如下List<E>

before: java.util.List<E>
after:  java.util.List<java.lang.String>
Run Code Online (Sandbox Code Playgroud)

主要思想如下:

  • 获取sun.reflect.generics.reflectiveObjects.XyzImpl课程
  • 获取它们的构造函数,确保它们是accessible
  • 将构造函数调用.newInstance包装在辅助方法中
  • 在称为的简单递归方法中使用辅助方法,substituteTypeVariable该方法用具体类型替换的类型变量来重建表达式Type

我没有实现每种情况,但它也应该适用于更复杂的嵌套类型(因为 的递归调用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)
  }
}
Run Code Online (Sandbox Code Playgroud)