Search code examples
javagenericslambdaanonymous-functionanonymous-methods

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


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
    }

}

Solution

  • 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.