【问题标题】:Check type of Predicate generic检查谓词泛型的类型
【发布时间】:2020-09-21 14:26:45
【问题描述】:

我有 2 个类,它们的内部结构根本不重要。

class ClassA {
    //... 
}

class ClassB {
    //...
}

我有 2 个使用这些类的谓词,假设它们看起来像这样

private Predicate<ClassA> classAPredicate() {
    return Objects::nonNull;
}

private Predicate<ClassB> classBPredicate() {
    return Objects::nonNull;
}

现在,我在外部库中有一个通用方法,该方法已经被许多用户使用,不幸的是,它具有非常通用的输入参数,即Object,在 90% 的情况下为Predicate

我需要做的是通过检查传递的Predicate的类型来扩展这个方法的功能,并在此基础上执行一些操作。

public void test(Object obj) {
    Predicate predicate = (Predicate)obj;
    if(predicate.getClass().isAssignableFrom(ClassA.class)) {
        System.out.println(predicate.test(new ClassA()));
        // logic specific to Predicate<ClassA>
    } else {
        System.out.println(predicate.test(new ClassB()));
        // logic specific to Predicate<ClassB>
    }
}

但是,在测试期间,我同时通过了 Predicates 并且它失败了 Exception in thread "main" java.lang.ClassCastException:

 test(classAPredicate());
 test(classBPredicate());

我一直在调试,isAssignableFrom() 总是返回 false,所以这里的错误很明显。我不确定这是否是正确的方法,但我还没有想出其他任何方法。有什么方法可以检查Predicate 的类型是什么?

我知道我想要实现的并不理想,但这是当前的要求......

【问题讨论】:

  • 无法针对 ClassA 或 ClassB 测试谓词,您需要查找 getGenericInterfacesgetGenericSuperclass 以检查谓词所引用的类。
  • 由于type erasure,您无法在运行时获取Predicate 的泛型类型参数。如果您的要求是接收没有其他信息的Predicate,然后尝试推断其泛型类型参数(没有运行时异常),那么您将不得不重新协商您的要求,因为这是不可能的。

标签: java types predicate


【解决方案1】:

在上面,谓词类不能从 A 类分配。

if(predicate.getClass().isAssignableFrom(ClassA.class))

这会导致 else 条件运行,它将 B 的实例传递给类型 A 的谓词,这会导致强制转换异常。由于类型擦除,要解决是否应该将 A 或 B 的实例传递给谓词并不容易。 3 个选项是:

  1. 尝试每种输入类型,直到不抛出 ClassCastException。
  2. 用新方法而不是现有测试函数处理预期行为。
  3. 定义一个比 Predicate 更具体的接口,它还具有获取谓词测试的类型并在条件中使用测试类型的方法。例如:
public interface TypedPredicate<T> extends Predicate<T> { Class<T> getTestType(); }

【讨论】:

  • 嗯,这就是我所期待的......所以检查我是否正在处理 Predicate&lt;ClassAPredicate&lt;ClassB&gt; 的唯一方法是尝试强制转换和捕获异常?这比以前更不优雅。
  • 这是(据我所知)现有约束的唯一方法。还有其他解决方案(例如:创建一个新接口“TypedPredicate”,而不是传递一个谓词,它还包括一个方法来获取正在测试的类的类型并检查它)。
  • 实际上这在现有约束下是可行的,因为我可以访问该库。我只是不能修改方法输入,但我可以添加额外的if(obj instanceof TypedPredicate)。仍然不理想,但比捕获 Cast Exceptions 更好
  • 啊,太好了!在这种情况下,转换为 TypedPredicate 然后使用“测试类型”来解析要测试的实例应该可以工作。
【解决方案2】:

嗯,

我从事 Java 泛型已经三年了。我可以在这里引用十几个关于“Reifying Java Generics”的 Stack Overflow 帖子:SO1、SO2SO3。最重要的是,如果您打算编写 Java 多年,您必须知道 "Generic Type Parameter" 在运行时只是 NOT ACCESSIBLE 没有字段或额外方法来检索它们。 Java 泛型(语法类似于:STUFF&lt;TYPE&gt; 与大于、小于符号是严格的编译时功能)。在运行时,JRE 根本不知道类型参数的类型是什么 - 如果发生误用,它所能做的就是抛出 ClassCastException

注意:如果您认为 JRE 不知道也不关心类型参数的类型,那么“滥用”泛型类型以使其抛出 ClassCastException 听起来很奇怪.大多数情况下,抛出异常的方式是,如果你在泛型中编写的代码做出了假设,并且如果它做出了错误的假设,那么这个异常就会抛出。

阅读 Sun / Oracle 的关于“Reifying Generic Type Parameters.”的“待办事项”列表此外,最重要的是,这个概念有一个非常真实的名称,您应该在 Java 中一直阅读它 - 它被称为“@ 987654324@" 在这个 Stack Overflow Answer 之前发布的解决方案说使用 try-catch (ClassCastException) 块,这实际上是一个有效的答案。

另外:如果您打算以任何允许 Java Lambda 语法 使用它。当你添加以下方法时:

公共接口 TypedPredicate 扩展 Predicate { 类 获取测试类型(); }

您将无法使用语法 @FunctionalInterface - 这是 java.util.function.Predicate&lt;T&gt; 类的主要好处之一 此外,还有一个更严重的问题问题在于,程序员无法访问 T 的类型,并且 JRE 在运行时不知道该类型

你在这里看到这部分(因为答案有一个绿色的复选标记):

{ Class<T> getTestType(); }
// Can you answer what you would write inside the method body of this
// 'extra-method' that you have added to Predicate<T> ???

扩展"Predicate"的类的以下实现不能在没有构造函数的情况下实例化。它不能被称为 "@FunctionalInterface" 并且 lambda-expression 不能用于创建它们:

// @FunctionalInterface (Commented Out)
public class TypedPredicate<A> implements Predicate<A>
{
    public boolean test(A a) { return pred.test(a); }

    // This is how the "Class of A" becomes accessible.  It this
    // version it is a public (and final) field.
    public final Class<A> className;

    // NOTE: This is the most important part here, the class of
    //       Variable-Type Parameter 'A' must be passed as a parameter
    //       to the constructor.  The programmer *LITERALLY* has to tell
    //       the code what type 'A' actually is!  This is the *BANE* of
    //       what I call the Java-Erasure-Fiasco programming.

    public TypedPredicate(Predicate<A> pred, Class<A> className)
    {
        this.pred = pred;
        this.className = className;
    }

    // Again, because a constructor is necessary, this cannot be
    // called a "Functional Interface" and it will not work very
    // much like a java.util.function.Predicate<T>, but it will 
    // indeed implement the interface.
}

最好的解决方案是重新调整您拥有的任何逻辑,这样您就不需要 guess 谓词是什么类型!下一个最好的办法是尝试上一个答案中建议的catch (ClassCastException) 版本。

最后:这个关于 java.lang.Class.isAssignableFrom(...) 的想法背后有正确的想法 - 但前提是你真的拥有 Class&lt;T&gt; clazz 作为你面前的一个例子,可以这么说。获取 Class&lt;T&gt; 实例的唯一方法是将其传递给构造函数,如我发布的示例所示。

【讨论】:

    猜你喜欢
    • 2020-01-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-18
    • 2016-10-10
    • 1970-01-01
    • 2018-11-23
    相关资源
    最近更新 更多