问题描述
我有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
是什么类型?
我知道我要实现的目标并不理想,但这是当前的要求...
解决方法
在上面,谓词类不能从A类分配。
if(predicate.getClass().isAssignableFrom(ClassA.class))
这将导致else条件运行,该条件将B的实例传递给类型A的谓词,这将导致强制转换异常。由于类型擦除,要决定是否应将A或B的实例传递给谓词并不容易。 3个选项是:
- 尝试每种输入类型,直到没有引发ClassCastException为止。
- 用新方法而不是现有的测试功能来处理预期的行为。
- 定义一个比Predicate更具体的接口,该接口还具有一种获取谓词测试类型并在条件中使用测试类型的方法。例如:
public interface TypedPredicate<T> extends Predicate<T> { Class<T> getTestType(); }
,
好吧
我从事Java Generics已经三年了。我可以在这里引用有关“ Reifying Java Generics”的许多Stack Overflow帖子:SO1,SO2,SO3。最重要的是,如果您打算编写Java多年,则必须知道 "Generic Type Parameter"
只是在运行时无法访问而没有字段,或其他方法来检索它们。 Java泛型(带有大于,小于符号的 STUFF<TYPE>
语法,具有严格的编译时功能)。在运行时,JRE根本不知道Type-Parameter的类型是什么-如果尝试进行滥用,它只能做ClassCastException
。
注意::如果您认为JRE不知道并且不在乎该类型,那么“滥用”该泛型类型使其抛出ClassCastException
应该听起来很奇怪。参数是。通常,引发异常的方式是,如果您在泛型内部编写的代码产生了推定,并且如果它做出了错误的推定,则将引发该异常。
阅读Sun / Oracle关于“ Reifying Generic Type Parameters.”的“待办事项”清单此外,最重要的是,此概念有一个非常真实的名称,您应该一直在Java中阅读它-,它被称为“ Run Time Type Erasure” 在此堆栈溢出答案之前发布的解决方案说,使用try-catch (ClassCastException)
块实际上是一个有效答案。
ALSO:如果您打算以任何希望允许使用的方式使用TypedPredicate<T> extends Predicate<T>
,则有关创建此类TypedPredicate<T>
的答案不是正确的答案Java Lambda语法即可使用。当您添加以下方法时:
公共接口TypedPredicate扩展了谓词{类 getTestType(); }
您将无法使用语法@FunctionalInterface
-这是类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"
的类的以下实现。不能将其称为 "@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>
实例的唯一方法是将其传递给构造函数,如我所发布的示例。