Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force Stream::filter method to fail compile time when Predicate<? super Object> is passed rather than Predicate<? super T>

I'm playing around with predefined Identity filters for use with the stream api. Unfortunately I'm unable to properly return a generic predicate that is compliant with the stream api documentation.

According to the de-compiler here is the Stream::filter definition:

public interface Stream<T> extends BaseStream<T, Stream<T>> {

    Stream<T> filter(Predicate<? super T> var1);

I'm facing the issue with any Java version that has Streams support (8~15). The issue has nothing to do with my implementation. This code actually is enough in order to reproduce it:

        Collection<String> result = Stream.of("A", "B", "C")
                                          .filter(new Object()::equals)
                                          .filter(Integer.valueOf(-1)::equals)
                                          .collect(Collectors.toSet());

Here, two predicates are applied where both of them aren't <? super String> compliant...

According to this answer this behavior seems to be strange...

How should I prevent users of my library from filtering on ServerState by random Object equality check, etc...?

Ideally I would like to always return proper Predicate<? super T> unfortunately that is not backed up by any compile time error...

Using a linter is not a solution in that case.

Even though I know how lower bounded wildcards work what I've been missing is that a Predicate<? super Integer> could be successfully casted to Predicate<? super String>.

Where:

Predicate<? super String> stringPredicate = (Predicate<? super String>)Filters.is_tClass(Integer.class, 4);
Predicate<? super Server> serverPredicate = (Predicate<? super Server>)Filters.is_comparable(5);

Collection<Integer> result = Stream.of(1, 2, 3)
                                   .filter((Predicate<? super Integer>)stringPredicate)
                                   .filter((Predicate<? super Integer>)serverPredicate)
                                   .filter(Filters.is(new Object()))
                                   .collect(Collectors.toSet());

results in [] empty resultset.

Here is what I have so far, but not happy with any of it:

import java.util.Collection;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Main {

    public static void main(String[] args) {

        Collection<Integer> result = Stream.of(1, 2, 3)
                                           //.filter(Filters.is_tClass(Integer.class, 4)) // enforce user to provide target class
                                           //.filter(Filters.is_comparable(5)) // use only Comparable
                                           .filter(Filters.is(new Server())) // fail runtime with custom exception
                                           .collect(Collectors.toSet());

        System.out.println(result);
    }

    private static class Server {
    }

    private static class Filters {

        private static <T> Predicate<? super T> is(T other) {

            return t -> {

                // simple class equality check - error prone!
                Class<?> tClass = t.getClass();
                Class<?> otherClass = other.getClass();

                if (!tClass.equals(otherClass)) {

                    throw new RuntimeException(
                        String.format("Check equality for [%s ? %s] seems odd. Can not continue...", tClass, otherClass));
                }
                return t.equals(other);
            };
        }

        static <T> Predicate<? super T> is_tClass(Class<T> tClass, T other) {

            return is(other);
        }

        static <T extends Comparable<T>> Predicate<? super T> is_comparable(T other) {

            return is(other);
        }
    }
}

Methods with names of the type is_* did not exist before posting the sample in here and therefor will be removed...

EDIT

Even though I know how lower bounded wildcards work what I've been missing is that a Predicate<? super Integer> could be successfully casted to Predicate<? super String>.

Where:

Predicate<? super String> stringPredicate = (Predicate<? super String>)Filters.is_tClass(Integer.class, 4);
Predicate<? super Server> serverPredicate = (Predicate<? super Server>)Filters.is_comparable(5);

Collection<Integer> result = Stream.of(1, 2, 3)
                                   .filter((Predicate<? super Integer>)stringPredicate)
                                   .filter((Predicate<? super Integer>)serverPredicate)
                                   .filter(Filters.is(new Object()))
                                   .collect(Collectors.toSet());

results in [] empty resultset.

like image 678
dbl Avatar asked Oct 26 '20 11:10

dbl


1 Answers

Here, two predicates are applied where both of them aren't <? super String> compliant

It's not true: the 2 predicates do consume an Object, which is the parent of String.

<? super String> must not be confused with <? extends String>.

like image 133
Benoit Avatar answered Oct 21 '22 21:10

Benoit