【问题标题】:How should Type type-hierarchy types be implemented?Type type-hierarchy 类型应该如何实现?
【发布时间】:2018-12-27 16:18:19
【问题描述】:

当泛型添加到 1.5 时,java.lang.reflect 添加了一个带有各种子类型的 Type 接口来表示类型。 Class 被改造为在 1.5 之前的类型中实现 TypeType 子类型可用于 1.5 中的新泛型类型。

这一切都很好。有点尴尬,因为Type 必须沮丧才能做任何有用的事情,但可以通过试验、错误、摆弄和(自动)测试来实现。除非涉及到实施......

equalshashCode应该如何实现。 TypeParameterizedType 子类型的 API 描述说:

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

(我猜这意味着getActualTypeArgumentsgetRawType 但不是getOwnerType??)

我们从java.lang.Object 的通用合同中知道,hashCode 也必须实现,但似乎没有说明该方法应该产生什么值。

Type 的其他子类型似乎都没有提到 equalshashCode,除了 Class 的每个值都有不同的实例。

那么我在equalshashCode 中输入了什么?

(如果您想知道,我正在尝试将类型参数替换为实际类型。所以如果我知道 在运行时 TypeVariable<?> TClass<?> String 那么我想要替换Types,所以List<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;
      }
   }

【问题讨论】:

  • 为什么需要添加equals/hashCode?你确定内置类型不正确吗?
  • @PeterLawrey 我需要检查类型是否匹配。当似乎没有规范时,内置实现怎么可能相等。我的意思是你可能可以通过观察来计算出实现的作用——这不是你要使用密码学的东西。
  • 实现似乎有这些方法来自读取源代码。您可以尝试创建自己的系统,但这似乎需要做很多工作,因为它可能永远不需要。一个简单的解决方案是比较toString() 结果。
  • @PeterLawrey 我不确定toString 是否保证是唯一的。 (还有 bleurgh,真是个 hack。而且不适合我的 HashMap。)
  • 另外,对递归类型要格外小心,即&lt;U extends Comparable&lt;U&gt;&gt;

标签: java reflection


【解决方案1】:

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

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

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

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

未指定哈希码。

我还开始着手实现 Android 使用的这些类。非常早期的 Android 版本与 Java 具有不同的哈希码,但您今天在野外发现的所有内容都使用与 Java 相同的哈希码。

toString 方法不好

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

复制粘贴并伤心

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

【讨论】:

    【解决方案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);
      }
    }
    

    StringList&lt;E&gt; 中替换E 的结果如下:

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

    主要思路如下:

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

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

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

    警告:ParameterizedTypeImpl 是内部专有 API,可能会在未来版本中删除

    但是,有一个@SuppressWarnings for that

    上面的 Java 代码是通过翻译下面的小 Scala sn-p 得到的(这就是为什么 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)
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-08-02
      • 2018-03-30
      • 1970-01-01
      • 2022-07-19
      • 2020-12-16
      • 2019-09-03
      • 2013-05-07
      相关资源
      最近更新 更多