Elegant solution to the "Lambdas" vs "Generics" problem?

96 views Asked by At

I am looking for a prettier solution here. The problem is to combine Generics and Lambda Expressions.

According to the Java Language Specification this it not possible the way I want.

So my question to you: Do you know a more elegant way to handle that problem? Any suggestions or common practice out there?

My "List" class, where I need the BiPredicate<T, T> in a lot of functions:

package stackoverflow.genericlambdas;

import java.util.ArrayList;
import java.util.function.BiPredicate;

public class MyListImpl<T> {

    private final ArrayList<T> mItems = new ArrayList<>();


    public void addItem(final T pItem) {
        mItems.add(pItem);
    }

    // assume my library is full of such methods
    public int getFirstIndexOf(final BiPredicate<T, T> pIdentifier, final T pItem) {
        for (int i = 0; i < mItems.size(); i++) {
            final T t = mItems.get(i);
            if (pIdentifier.test(t, pItem)) return i;
        }
        return -1; // not found
    }

}

My "Library" class, where I want to store template BiPredicate<T, T>s:

package stackoverflow.genericlambdas;

import java.util.Objects;
import java.util.function.BiPredicate;

public class Library {


    static public class CLUMSY_IDENTITY_CLASS<T> implements BiPredicate<T, T> {
        @Override public boolean test(final T p, final T q) {
            return p == q;
        }
    }
    static public class CLUMSY_EQUALITY_CLASS<T> implements BiPredicate<T, T> {
        @Override public boolean test(final T p, final T q) {
            return Objects.equals(p, q);
        }
    }


    static public final BiPredicate<T, T>   IDENTITY    = (p, q) -> p == q;                 // compilation error
    static public final BiPredicate<T, T>   EQUALITY    = (p, q) -> Objects.equals(p, q);   // compilation error


}

My "usage" class, where I want to use those templates:

package stackoverflow.genericlambdas;

import java.util.Objects;

public class GenericLamdasUse {

    public static void main(final String[] args) {
        final MyListImpl<String> list = new MyListImpl<>();
        list.addItem("aa");
        list.addItem("bb");
        list.addItem("cc");

        final String testItem = "cc";
        final String testItem2 = "c" + (Math.random() < 2 /* always true */ ? "c" : ""); // this builds a new string that is not == the compile constant String "cc"

        final int whatIKnowWorks1 = list.getFirstIndexOf((p, q) -> Objects.equals(p, q), testItem);
        final int whatIKnowWorks2 = list.getFirstIndexOf((p, q) -> p == q, testItem);

        final int whatIKnowWorks3 = list.getFirstIndexOf(new Library.CLUMSY_IDENTITY_CLASS<>(), testItem); // dont't want the re-instantiation
        final int whatIKnowWorks4 = list.getFirstIndexOf(new Library.CLUMSY_EQUALITY_CLASS<>(), testItem); // for ever time i would need that

        final Library.CLUMSY_IDENTITY_CLASS<String> clumsy5 = new Library.CLUMSY_IDENTITY_CLASS<>(); // dont't want the re-instantiation
        final Library.CLUMSY_EQUALITY_CLASS<String> clumsy6 = new Library.CLUMSY_EQUALITY_CLASS<>();
        final int whatIKnowWorks5 = list.getFirstIndexOf(clumsy5, testItem); // for ever time i would need that
        final int whatIKnowWorks6 = list.getFirstIndexOf(clumsy6, testItem); // also, the types <String> have to match each time

        final int whatIWant1 = list.getFirstIndexOf(Library.EQUALITY, testItem); // I want this, but: compilation error
        final int whatIWant2 = list.getFirstIndexOf(Library.IDENTITY, testItem); // I want this, but: compilation error
    }

}
2

There are 2 answers

0
Slaw On BEST ANSWER

If you need to parameterize the BiPredicates with a type variable, then you won't be able to do this via static fields, because the type variable has to be defined in scope. Something like this is typically implemented via static methods.

package stackoverflow.genericlambdas;

import java.util.Objects;
import java.util.function.BiPredicate;

public class Library {

    public static <T> BiPredicate<T, T> identity() {
        return (a, b) -> a == b;
    }

    public static <T> BiPredicate<T, T> equals()
        return Objects::equals;
    }
}

If you don't need the type variable then see Sweeper's answer.

1
Sweeper On

IDENTITY and EQUALITY don't actually need to have generic type parameters if you apply PECS.

If you declare them like this:

public static final BiPredicate<? super Object, ? super Object> IDENTITY = (p, q) -> p == q;
public static final BiPredicate<? super Object, ? super Object> EQUALITY = Objects::equals;

Note that you should use super here because the two type parameters of BiPredicate represent the types that it "consumes".

Then do the same thing to getFirstIndexOf:

public int getFirstIndexOf(final BiPredicate<? super T, ? super T> pIdentifier, final T pItem) {

Now you can simply pass IDENTITY and EQUALITY to getFirstIndexOf without any error:

list.getFirstIndexOf(IDENTITY, "foo");

Of course, if you don't have control over getFirstIndex, then you cannot do this, and would need to declare a method that returns a BiPredicate like in Slaw's answer.

public static <T> BiPredicate<T, T> identity() {
    return (p, q) -> p == q;
}

Note that the JLS doesn't guarantee that no new objects are created when you call a method like that, but in reality it's often optimised to not create new objects.