Java: Filter items that are comparable to a wildcard type

92 Views Asked by At

I'm writing an interpreter for a DSL and I ran into a problem where I want to compare two values of unknown type. I have tried to reduce the problem to the following.

I want to define a function with two parameters with the following specification.

public boolean isMinimumOrThrow(Object minToCheck, Collection<Object> coll);
  1. If minToCheck is not of type Comparable<?>, then I want to throw a runtime exception.
  2. Otherwise, I want to filter out all objects in coll that I can compare with minToCheck, and then return whether minToCheck is less than or equal to every comparable object in coll.

First attempt

I get in trouble when defining the filter, i.e.,

public boolean isMinimumOrThrow(Object minToCheck, Collection<Object> coll) {
    if (!(minToCheck instanceof Comparable<?>)) {
        throw new RuntimeException();
    }
    Comparable<?> comparableMin = (Comparable<?>)minToCheck;
    return coll.stream().filter(...).allMatch(item -> comparableMin.compareTo((...)item) <= 0);
}

I'm not sure whether it's possible to implement the ....

Second attempt

To work around the problem, I've tried to loosen the restriction of Comparable a bit, and defined my own interface MyComparable<T>

public interface MyComparable<T> extends Comparable<T> {
    public Collection<T> filterComparable(Collection<?> values);
}

but even then, I still get a type error

The method compareTo(capture#13-of ?) in the type Comparable<capture#13-of ?> is not applicable for the arguments (capture#12-of ?)

with the following code

public boolean isMinimumOrThrow(Object minToCheck, Collection<Object> coll) {
    if (!(minToCheck instanceof MyComparable<?>)) {
        throw new RuntimeException();
    }
    MyComparable<?> comparableMin = (MyComparable<?>)minToCheck;
    return comparableMin.filterComparable(coll).stream().allMatch(item -> comparableMin.compareTo(item) <= 0);
}

Any ideas?

2

There are 2 best solutions below

2
Sebastian On BEST ANSWER

Maybe there is a cleaner solution, but you could stick to your first attempt. The filter can be implemented by simply trying a compareTo in a try-catch. This in turn only compiles if you use raw Comparable in intermediate steps. The result will be typesafe again (I hope), but needs to suppress some warnings for the intermediate steps. So my suggestion is the following:

@SuppressWarnings("unchecked")
public static boolean isMinimumOrThrow(Object minToCheck, Collection<Object> coll) {
    if (!(minToCheck instanceof Comparable)) {
        throw new RuntimeException();
    }
    @SuppressWarnings("rawtypes")
    Comparable comparableMin = (Comparable)minToCheck;
    return coll.stream().filter(item -> {
        boolean isComparable = true;
        try {
            comparableMin.compareTo(item);
        } catch (Exception e) {
            isComparable = false;
        }

        return isComparable;
    }).allMatch(item -> comparableMin.compareTo(item) <= 0);
}

public static void main(String[] args) {
    Collection<Object> list = Arrays.asList("bla",5,"blub",2,7);

    System.out.println("Is 0 minimum: " + isMinimumOrThrow(0, list)); // true
    System.out.println("Is 2 minimum: " + isMinimumOrThrow(2, list)); // true
    System.out.println("Is 3 minimum: " + isMinimumOrThrow(3, list)); // false
    System.out.println("Is 'bla' minimum: " + isMinimumOrThrow("bla", list)); // true
    System.out.println("Is 'blub' minimum: " + isMinimumOrThrow("blub", list)); // false
    System.out.println("Is 'some object' minimum: " + isMinimumOrThrow(new Object(), list)); // throws RuntimeException

}
2
tykey100 On

I did a couple of minor changes to your code and I believe I got the function working as you wanted.

First I defined the function as generic:

<T> boolean isMinimumOrThrow(T minToCheck, Collection<T>)

And I simplified a little bit your filter and added a cast to T on item:

coll.stream().allMatch(item -> comparableMin.compareTo( (T) item) <= 0);

This is using standard Comparable. Your main problem I think was missing the cast on "item".

A full working example with integers:

import java.util.Collection;
import java.util.List;
import java.util.Arrays;
import java.lang.Comparable;
public class Program
{
    static <T> boolean isMinimumOrThrow(T minToCheck, Collection<T> coll) {
        if (!(minToCheck instanceof Comparable<?>)) {
            throw new RuntimeException();
        }
        Comparable<T> comparableMin = (Comparable<T>) minToCheck;
        return coll.stream().allMatch(
                item -> comparableMin.compareTo( (T) item) <= 0
            );
    }
    public static void main(String[] args) {
        boolean isMinimum = false;

        Collection<Integer> list = Arrays.asList(1,2,3,4,5);
        
        isMinimum = isMinimumOrThrow(0, list);
        System.out.println("Is minimum: " + isMinimum);
        
    }
}