0

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<ClassA> classAPredicate() {
    return Objects::nonNull;
}

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

But, during tests I'm passing both Predicates and it fails with Exception in thread "main" 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...

Weeedooo
  • 501
  • 8
  • 25
  • The predicate can't be tested against ClassA or ClassB, you need to lookup `getGenericInterfaces` or `getGenericSuperclass` in order to check the class in which the predicate is referencing. – kendavidson Sep 21 '20 at 14:32
  • 1
    You can't get the generic type parameter of a `Predicate` at runtime because of [type erasure](https://en.wikipedia.org/wiki/Generics_in_Java#Problems_with_type_erasure). If your requirement is to receive a `Predicate` with no other information and then try and deduce its generic type parameter (without runtime exceptions), then you will have to renegotiate your requirements, because it is not possible. – khelwood Sep 21 '20 at 14:37

2 Answers2

2

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<T> extends Predicate<T> { Class<T> getTestType(); }
mike1234569
  • 636
  • 2
  • 5
  • Well that is what I was expecting... so the only way to check if I'm dealing with `Predicate` is to try to cast and catch exception? That's even less elegant solution than before. – Weeedooo Sep 21 '20 at 14:49
  • It is the only way (that I know of) with the existing constraints. There are other solutions (ex: instead of passing a Predicate, create a new interface 'TypedPredicate' which also includes a method to get the type of the class being tested and check for that instead). – mike1234569 Sep 21 '20 at 14:55
  • Actually that is doable with existing constraints, as I have access to that libary. I just can't modify method input, but I can add additional `if(obj instanceof TypedPredicate)` instead. Still not ideal, but better than catching Cast Exceptions – Weeedooo Sep 21 '20 at 14:57
  • Ahh nice! In that case casting to a TypedPredicate then using the 'test type' to resolve the instance to test with should work. – mike1234569 Sep 21 '20 at 14:59
0

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 "Generic Type Parameter" are simply NOT ACCESSIBLE at Run-Time without fields, or extra methods to retrieve them. Java Generics (The syntax that looks like: STUFF<TYPE> 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<T> extends Predicate<T> is not the correct answer if you intend to use your TypedPredicate<T> in any way that expects to allow Java Lambda Syntax to work with it. When you add the following method:

public interface TypedPredicate extends Predicate { Class 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<T> 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<T> getTestType(); }
// Can you answer what you would write inside the method body of this
// 'extra-method' that you have added to Predicate<T> ???

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

// @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.
}

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<T> clazz as an instance in front of you, so to speak. The only way to get an instance of Class<T> would be to pass it to a constructor as in the example I have posted.

Y2020-09
  • 1
  • 5