1

So I have this piece of code which uses several maps to store some simple classes. Now I also want to look op some of the values based on for example the x position of this item. I tried to do something with Generics and lambda's, but it just didn't work out. Also if there is an easier way to do this, please tell. I have most of my experience in Python, so this might just not be practical in Java.

interface Compare {
     public <T, P> boolean apply(T obj, P comp);
}

class Utils {
    public static <T, P> List<T> retrieve(Collection<T> args, P value, Compare c) {

        List<T> r = new ArrayList<T>();

        for (T i: args) {
            if (c.apply(i, value)) {
                r.add(i);
            }
        }

        return r;

    }
}

class Point {
    int x;
    int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
}

This doesn't raise any errors as of yet, but when I try to actually use the retrieve function on a HashMap it doesn't work.

Point p = Utils.retrieve(points.values(), 0, (Point p, Integer x) -> {return p.x == x;});

There are two errors in eclipse:

  1. At Utils.retrieve, eclipse notes that: The method retrieve(Collection, P, Compare) in the type Utils is not applicable for the arguments (Collection, int, (Point p, Integer x) -> {})
  2. At the lambda expression, eclipse notes that: Illegal lambda expression: Method apply of type Compare is

How would one handle this situation? My thanks in advance.

1
  • 2
    You probably want interface Compare<T, P> { boolean apply(T obj, P comp); }, with the generics on the interface, not the method. Commented Feb 13, 2017 at 20:38

1 Answer 1

1

You're fairly close to getting the code you're after.

Firstly, move the generic parameter declaration to the interface (as the esteemed Esquire Wasserman suggests in his comment).

interface Compare<T, P> {
    public boolean apply(T obj, P comp);
}

Now you just need to modify the way you're calling your new toys. I've created a main method with the following code and it works nicely:

public static void main(String[] args) {
    Set<Point> points = new HashSet<>(8);
    points.add(new Point(0, 0));
    points.add(new Point(1, -1));
    points.add(new Point(1, 1));
    points.add(new Point(-1, -1));
    points.add(new Point(-1, -1));
    System.out.println("Set<Point> points:\n" + points);

    Compare<Point, Integer> sameX = (p, x) -> p.x == x;
    List<Point> matchingPoints =
            Utils.<Point, Integer>retrieve(points, 0, sameX);
    System.out.println("\nmatchingPoints:\n" + matchingPoints);
}

I've used a simple Set<Point> here because I don't know what you're intending to store in a Map, but calling Map.values() will produce a Collection<Point> just as well as the Set<Point> does in this example.

The reason this works is that you specify the parameter types to the generic static method in the angle-brackets immediately before the name of the static method: Utils.<Point, Integer>retrieve and you don't put the types inside the lambda. It's also worth saying here that lambda expressions should be as compact as possible, so rather than {return p.x == x;} you can simply have p.x == x.

Note, however, that in order to see the output in a readable form, you need to override the toString() method in your Point class:

@Override
public String toString() {
    return "Point(" + x + ", " + y + ")";
}

This will give you the following output:

Set<Point> points:
[Point(-1, -1), Point(0, 0), Point(1, -1), Point(-1, -1), Point(1, 1)]

matchingPoints:
[Point(0, 0)]

(Also note that you don't have to move the lambda out of the call to the retrieve method, but it makes the code tidier, and fits better in this narrow Stack Overflow window.)

Addendum

Your follow-up comment made me realise that your Utils.retrieve method was going to hit trouble unless exactly the right sort of Compare method was passed to it. Rather than leave things open to chance, it's better to lock this down by modifying the signature of your retrieve method so that it becomes this:

public static <T, P> List<T> retrieve(Collection<T> args,
        P value, Compare<T, P> c) {

Now the Compare object which is passed to retrieve must take the same types which apply to the Collection<T> and the value. Once you've made this change you can inline your lambda expression like this:

List<Point> matchingPoints = Utils.<Point, Integer>retrieve(
        points, 0, (p, x) -> p.x == x);

You won't get errors now, because the strict type applied to the Compare parameter in your retrieve method allows the IDE (and compiler) to see that the lambda must be of type <Point, Integer>.

Sign up to request clarification or add additional context in comments.

2 Comments

When I try to implement the lambda inline instead of out of the call to the retrieve method, it starts to give errors about it the p and the x being objects. Do you need the <Point, Integer> somewhere?
In trying to inline the lambda, I realised that the original retrieve method was being far too relaxed about the type of Compare object it accepted. I've created an addendum to my answer to explain how to constrain the Compare parameter, and this also makes it possible to inline the lambda without error.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.