检查谓词泛型的类型

Check type of Predicate generic

我有 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 的类型吗?

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

在上面,谓词 class 不能从 Class 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(); }

嗯,

我一直在做 Java 泛型,现在已经三年了。我可以在这里引用十几个关于“Reifying Java Generics”的 Stack Overflow 帖子:SO1,, SO3。最重要的是,如果你打算写 Java 年复一年,你必须知道 "Generic Type Parameter" 只是 NOT ACCESSIBLE 在 Run-Time 没有字段,或者没有额外的方法来检索它们。 Java 泛型(看起来像这样的语法:STUFF<TYPE> 和 greater-than、less-than 符号是 STRICTLY A COMPILE-TIME 特征)。在运行时,JRE 根本不知道 Type-Parameter 的类型是什么 - 它所能做的就是在尝试滥用时抛出 ClassCastException

注意: 'Misuse' 抛出 ClassCastException 的泛型类型听起来很奇怪,如果您认为 JRE 不知道也不知道关心类型参数的类型是什么。大多数情况下,抛出异常的方式是这样的,如果您在泛型中编写的代码进行了假设,并且如果它做出了错误的假设,那么将抛出此异常。

阅读 Sun / Oracle 关于“Reifying Generic Type Parameters." Also, most importantly, this concept has a very real name that you should read about all the time in Java - and it is called "Run Time Type Erasure 的“待办事项”列表 在此 Stack Overflow Answer 之前发布的解决方案说使用 try-catch (ClassCastException) 块,实际上,一个有效的答案。

另外: 如果您打算以任何预期的方式使用您的 TypedPredicate<T>,关于创建此类 TypedPredicate<T> extends Predicate<T> 的答案不是正确的答案允许 Java Lambda Syntax 使用它。当您添加以下方法时:

public interface TypedPredicate extends Predicate { Class getTestType(); }

您将无法使用语法 @FunctionalInterface - 这是 class java.util.function.Predicate<T> 此外,还有一个更严重的问题,程序员无法访问 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" 的 class 的以下实现。它不能被称为 "@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<T> clazz 作为你面前的一个实例,可以这么说。获取 Class<T> 实例的唯一方法是将其传递给构造函数,就像我发布的示例中那样。