代码之家  ›  专栏  ›  技术社区  ›  Tom Hawtin - tackline

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

  •  14
  • Tom Hawtin - tackline  · 技术社区  · 7 年前

    当泛型被添加到1.5时, java.lang.reflect 添加了 Type Class 是为了实施 类型 类型 子类型可用于1.5中泛型类型的新类型。

    类型

    应该怎么做 equals hashCode 执行。的API描述 ParameterizedType 类型

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

    (我想这意味着 getActualTypeArguments getRawType getOwnerType ??)

    java.lang.Object 那个 哈希码

    没有其他类型的 类型 似乎提到 等于 哈希码 ,除此之外 每个值具有不同的实例。

    所以我该在我的 哈希码

    (如果您想知道,我正在尝试用类型参数替换实际类型。所以如果我知道 运行时 TypeVariable<?> T Class<?> String 那我想换一个 类型 List<T> 变成 List<String> T[] 变成 String[] , List<T>[] 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 回复  |  直到 7 年前
        1
  •  5
  •   Jesse Wilson    7 年前

    我已经用不满意的方式解决这个问题10年了。首先是 Guice’s MoreTypes.java ,复制粘贴并修订 Gson’s GsonTypes.java Moshi’s Util.java .

    摩希有我最好的方法,那不是说它是好的。

    你不能打电话 equals()

    这是因为Java类型api提供了多种不兼容的方法来对简单类的数组建模。你可以做一个 Date[] 作为一个 Class<Date[]> 或者作为 GenericArrayType 其组件类型为 Date 日期[] 后者从反射作为场的参数 List<Date[]> .

    未指定哈希代码。

    toString方法不好

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

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

        2
  •  4
  •   Andrey Tyukin    7 年前

    下面是一个直接依赖于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);
      }
    }
    

    E 通过 String 在里面 List<E>

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

    主要思路如下:

    • 得到 sun.reflect.generics.reflectiveObjects.XyzImpl 班级
    • 找到他们的构造器,确保他们 accessible
    • 包装构造函数 .newInstance
    • 在一个简单的递归方法中使用helper方法 substituteTypeVariable 重建 Type

    我并没有实现每一个案例,但它也应该可以处理更复杂的嵌套类型(因为 替代变量 ).

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

    但是,有一个 @SuppressWarnings for that .

    上面的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)
      }
    }