检查 Predicate 泛型的类型

huangapple go评论85阅读模式
英文:

Check type of Predicate generic

问题

我有两个类,它们的内部内容并不重要。

class ClassA {
    //... 
}

class ClassB {
    //...
}

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

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()));
        // 适用于 Predicate<ClassA> 的逻辑
    } else {
        System.out.println(predicate.test(new ClassB()));
        // 适用于 Predicate<ClassB> 的逻辑
    }
}

但是,在测试期间,我传递了两个Predicate,并且失败了,出现了Exception in thread "main" java.lang.ClassCastException:错误。

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

我已经进行了调试,isAssignableFrom() 总是返回 false,所以错误在这里是显而易见的。我不确定这是否是正确的方法,但我还没有想出其他办法。有没有办法检查Predicate的类型?

我知道我正在尝试实现的并不理想,但这是目前的要求...

英文:

I'm having 2 classes, their internals doesn't matter at all.

class ClassA {
    //... 
}

class ClassB {
    //...
}

And I'm having 2 predicates that use those classes, let's say they look like this

private Predicate&lt;ClassA&gt; classAPredicate() {
    return Objects::nonNull;
}

private Predicate&lt;ClassB&gt; classBPredicate() {
    return Objects::nonNull;
}

Now, I'm having generic method in external library that is already beeing used by many users and unfortunatelly, it has pretty generic input parameter which is Object which in 90% of cases is Predicate.

What I need to do, is to extend this method functionality by checking type of passed Predicate and based on that, perform some operations.

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&lt;ClassA&gt;
    } else {
        System.out.println(predicate.test(new ClassB()));
        // logic specific to Predicate&lt;ClassB&gt;
    }
}

But, during tests I'm passing both Predicates and it fails with Exception in thread &quot;main&quot; java.lang.ClassCastException:

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

I've been debugging and isAssignableFrom() is always returning false so the error is obvious here. I'm not sure if that is the right approach, but I didn't came up with anything else yet. Is there any way to check what is the type of that Predicate?

I know that what I'm trying to implement isn't ideal, but that is current requirement...

答案1

得分: 2

在上面的代码中,谓词类(predicate class)不能从A类(Class A)赋值而来。

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

这会导致执行else条件,该条件将一个B类的实例传递给用于类型A的谓词(Predicate),从而导致类型转换异常。由于类型擦除,很难确定是应该传递A类的实例还是B类的实例给谓词。有三个选项:

1)尝试每种输入类型,直到找到一个不会抛出ClassCastException的类型。
2)在新方法中处理预期的行为,而不是现有的测试函数。
3)定义一个比Predicate更具体的接口,该接口还具有一个方法来获取谓词测试的类型,并在条件中使用测试类型。例如:

public interface TypedPredicate<T> extends Predicate<T> {
    Class<T> getTestType();
}
英文:

In the above, the predicate class is not assignable from Class A.

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

This causes the else condition to run which passes an instance of B to the Predicate for type A which causes a cast exception. Due to type erasure, it will not be easy to resolve whether an instance of A or B should be passed to the predicate. 3 options are:

  1. Try each input type until one doesn't throw a ClassCastException.
  2. Handle the expected behavior in a new method instead of the existing test function.
  3. Define a more specific interface than Predicate which also has a method to get the type the predicate tests and use the test type in the condition instead. Ex:
public interface TypedPredicate&lt;T&gt; extends Predicate&lt;T&gt; { Class&lt;T&gt; getTestType(); }

答案2

得分: 0

好的,

我已经做了Java泛型工作已经有三年了。我可以引用关于“重现Java泛型”的十几篇Stack Overflow帖子:SO1,SO2SO3。最重要的是,如果您打算多年多年地编写Java代码,您必须知道**“泛型类型参数”在运行时没有字段或额外的方法来检索它们的情况下根本无法访问**。Java泛型(看起来像是**STUFF&lt;TYPE&gt;这样的语法,带有大于号、小于号符号,严格来说是严格的编译时特性**)。在运行时,Java运行时环境根本不知道类型参数的类型是什么,它所能做的就是在试图误用时抛出ClassCastException异常。

**注意:**如果您认为Java运行时环境不知道类型参数的类型并且不关心它,那么以一种使其抛出ClassCastException异常的方式“误用”泛型类型听起来应该很奇怪。大多数情况下,异常被抛出的方式是,如果您在泛型内部编写的代码做了假设,并且如果假设错误,那么这个异常就会被抛出。

请阅读Sun / Oracle关于“重现泛型类型参数”的“待办事项”清单。而且,最重要的是,这个概念在Java中有一个非常实际的名称,您应该经常阅读有关它的内容 - 它被称为“运行时类型擦除”答案之前发布的解决方案说要使用try-catch (ClassCastException)块,这实际上是一个有效的答案。

另外:关于创建这种TypedPredicate&lt;T&gt; extends Predicate&lt;T&gt;类型的答案不正确,如果您打算以允许Java Lambda语法与其一起工作的任何方式使用您的TypedPredicate&lt;T&gt;。当您添加以下方法时:

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

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

您看到了答案中的这一部分(因为答案上有一个绿色的勾号):

{ Class&lt;T&gt; getTestType(); }
// 你能回答在这个“额外方法”的方法体中会写什么吗?

以下继承了**“Predicate”的类的实现不能在没有构造函数的情况下实例化。它不能被称为“@FunctionalInterface”,也不能使用lambda表达式**创建它们:

// @FunctionalInterface(已注释掉)
public class TypedPredicate&lt;A&gt; implements Predicate&lt;A&gt;
{
    public boolean test(A a) { return pred.test(a); }

    // 这就是“类A”的访问方式。在这个版本中,它是一个公共(和final)字段。
    public final Class&lt;A&gt; className;

    // 注意:这是这里最重要的部分,变量类型参数‘A’的类必须作为参数传递给构造函数。程序员必须明确告诉代码实际上是什么类型的‘A’!这是我所说的Java擦除风格编程的**悲剧**。

    public TypedPredicate(Predicate&lt;A&gt; pred, Class&lt;A&gt; className)
    {
        this.pred = pred;
        this.className = className;
    }

    // 再次说明,由于需要构造函数,这不能被称为“功能性接口”,它也不会很像java.util.function.Predicate&lt;T&gt;,但它确实实现了该接口。
}

最好的解决方案是重新调整您的逻辑,以使您不需要**“猜测”**一个Predicate的类型!其次,最好的解决方案是尝试在前一个答案中建议的catch (ClassCastException)版本。

**最后:关于java.lang.Class.isAssignableFrom(...)的这个想法是正确的 - 但仅当您实际上拥有Class&lt;T&gt; clazz的实例时,可以这么说。获得Class&lt;T&gt;**的实例的唯一方法是将它传递给构造函数,就像我发布的示例一样。

英文:

Well,

I have been doing Java Generics for going on three years now. I can cite a dozen Stack Overflow posts about "Reifying Java Generics" here: SO1, SO2, SO3. Most importantly, if you are intending to write Java for years and years, you must know that the &quot;Generic Type Parameter&quot; are simply NOT ACCESSIBLE at Run-Time without fields, or extra methods to retrieve them. Java Generics (The syntax that looks like: STUFF&lt;TYPE&gt; with the greater-than, less-than symbols is STRICTLY A COMPILE-TIME FEATURE). At RunTime, the JRE simply has no idea what the Type of the Type-Parameter is - and all it can do is throw ClassCastException if an attempt to misuse occurs.

NOTE: 'Misuse' the generic type such that it throws ClassCastException should sound odd if you are thinking that the JRE does not know and does not care what the type of the type parameter is. Mostly, the way exceptions are thrown, is such that if the code you write inside of a generic makes presumptions, and if it has made faulty presumptions, then this exception will throw.

Read Sun / Oracle's "To Do" list about "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" The solution posted before this Stack Overflow Answer says to use try-catch (ClassCastException) blocks, which is, actually, a valid answer.

ALSO: The answer about creating this type of TypedPredicate&lt;T&gt; extends Predicate&lt;T&gt; is not the correct answer if you intend to use your TypedPredicate&lt;T&gt; in any way that expects to allow Java Lambda Syntax to work with it. When you add the following method:

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

You will not be able to use the syntax @FunctionalInterface - which is one of the primary benefits of the class java.util.function.Predicate&lt;T&gt; Furthermore, there is a more severe problem in that, the Type of T is not accessible to the programmer and is not known at RunTime by the JRE

You see this part right here (since the answer has a green check mark):

{ Class&lt;T&gt; getTestType(); }
// Can you answer what you would write inside the method body of this
// &#39;extra-method&#39; that you have added to Predicate&lt;T&gt; ???

The following implementation of a class that extends &quot;Predicate&quot; cannot be instantiated without a constructor. It cannot be called a &quot;@FunctionalInterface&quot; and lambda-expression cannot be used to create them:

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

    // This is how the &quot;Class of A&quot; becomes accessible.  It this
    // version it is a public (and final) field.
    public final Class&lt;A&gt; className;

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

    public TypedPredicate(Predicate&lt;A&gt; pred, Class&lt;A&gt; className)
    {
        this.pred = pred;
        this.className = className;
    }

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

The best solution would realign whatever logic you have such that you do not need to guess what type a Predicate is! The next best thing would be to try the catch (ClassCastException) version that was suggested in the previous answer.

FINALLY: This idea regarding the java.lang.Class.isAssignableFrom(...) has the right idea behind it - but only if you actually have the Class&lt;T&gt; clazz as an instance in front of you, so to speak. The only way to get an instance of Class&lt;T&gt; would be to pass it to a constructor as in the example I have posted.

huangapple
  • 本文由 发表于 2020年9月21日 22:26:45
  • 转载请务必保留本文链接:https://go.coder-hub.com/63994313.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定