arcy
arcy

Reputation: 13103

passing in two lambda functions

I want to pass two lambda expressions (or something similar, I'm still getting familiar with all the terminology) into a method; the first one will get a list of items, and the second one will retrieve one Integer object from (each one of) those items.

So I want to have a method something like this:

private List<Integer> getRecentList(List<Integer> searchParams, 
                                    Function<List<Integer>, List<Integer>> listGetter,
                                    Supplier<Integer> idGetter)
{
  List<Object> objectList = listGetter.apply(searchParams);
  List<Integer> idList = new ArrayList<>();
  for (Object o: objectList)
  {
    idList.add(idGetter.get());
  }
  return idList;
}

and call it something like this:

List<Integer> idList = setRecentList(searchParams, 
                                     (a) -> serviceOne.getItemList(a), 
                                     Item::getItemId);

So the first function is one called on an instance variable that the called method will have access to, and the second function is an instance method on any one of the objects returned as a list by the first function.

But the (eclipse) compiler doesn't like Item::getItemId, with or without parentheses at the end.

Do I just have a syntax thing wrong, or is there something else wrong with this idea?


Edit after many helpful comments -- thanks to you all!

I have one problem left. I've now got a method that I think does what I want, but I'm not sure how to pass the second expression to call it. Here is the method:

private List<Integer> getRecentList(List<Integer> salesCodeIdList,
                                    Function<List<Integer>, List> listGetter, 
                                    Function<Object, Integer> idGetter                                 
                                    ) {
    List<Object> objectList = listGetter.apply(salesCodeIdList);
    List<Integer> idList = new ArrayList<>();
    for (Object o : objectList) {
        idList.add(idGetter.apply((o)));
    }
    return idList;
}

AFAIK, I have to leave the second List raw in the getter spec, since different getters will return different types of objects in their lists.

But I still don't know how to invoke it -- I want to pass a method that gets an id from a particular instance of object, i.e., I want to pass a getter on the ID of one of the objects returned by the listGetter. That will be different types of objects on different calls. How would I invoke that?

To go back to examples, if I had a Supplier class with getSupplierId(), and a Vendor class with getVendorId(), and I cannot change those classes, can I pass in the correct method to invoke on the objects in the list depending on which getter retrieved the list?

Upvotes: 2

Views: 135

Answers (2)

k_ssb
k_ssb

Reputation: 6252

Item::getItemId is not a Supplier<Integer>. A Supplier<Integer> is a lambda function that takes no arguments and returns an Integer. But Item::getItemId would require an Item to get the ID from.

Here is probably what you want. Note I've changed your method signature to match the idea that you're getting IDs from items.

class Item {
    int id;
    Item (int i) {
        id = i;
    }
    int getItemId() {
        return id;
    }
}

private static List<Integer> getRecentList(List<Integer> searchParams, 
        Function<List<Integer>, List<Item>> listGetter,
        Function<Item, Integer> idGetter)
{
    List<Item> itemList = listGetter.apply(searchParams);
    List<Integer> idList = new ArrayList<>();
    for (Item item: itemList)
    {
        idList.add(idGetter.apply(item));
    }
    return idList;
}

Edit: If you want this to handle multiple kinds of Items, and they all have IDs, you can do something like the following. First, define an interface to represent the fact that your different kinds of Items all have IDs:

interface ItemWithId {
    abstract int getItemId();
}
class ItemA implements ItemWithId {
    int id;
    ItemA(int i) {
        id = i;
    }
    public int getItemId() {
        return id;
    }
}
class ItemB implements ItemWithId {
    int id;
    ItemB(int i) {
        id = i;
    }
    public int getItemId() {
        return id;
    }
}

Then, your method should use ItemWithId instead of Item:

private List<Integer> getRecentList(List<Integer> searchParams,
        Function<List<Integer>, List<ItemWithId>> listGetter,
        Function<ItemWithId, Integer> idGetter)
{
    List<ItemWithId> itemList = listGetter.apply(searchParams);
    List<Integer> idList = new ArrayList<>();
    for (ItemWithId item: itemList)
    {
        idList.add(idGetter.apply(item));
    }
    return idList;
}

Finally, you can call this by casting an ItemA or ItemB to an ItemWithId:

List<Integer> idList = getRecentList(searchParams,
        (a) -> getItemList(a),
        (item) -> ((ItemWithId) item).getItemId());

Another edit: Ok, so you can't change ItemA or ItemB. You can still make it work:

class ItemA {
    int id;
    ItemA(int i) {
        id = i;
    }
    public int getItemIdA() {
        return id;
    }
}
class ItemB {
    int id;
    ItemB(int i) {
        id = i;
    }
    public int getItemIdB() {
        return id;
    }
}

private List<Integer> getRecentList(List<Integer> searchParams,
        Function<List<Integer>, List<Object>> listGetter,
        Function<Object, Integer> idGetter)
{
    List<Object> itemList = listGetter.apply(searchParams);
    List<Integer> idList = new ArrayList<>();
    for (Object item: itemList)
    {
        idList.add(idGetter.apply(item));
    }
    return idList;
}

List<Integer> idList = getRecentList(searchParams,
        (a) -> getItemList(a),
        (item) -> ((ItemA) item).getItemIdA());
// or
List<Integer> idList = getRecentList(searchParams,
        (a) -> getItemList(a),
        (item) -> ((ItemB) item).getItemIdB());

Upvotes: 1

Sergey Kalinichenko
Sergey Kalinichenko

Reputation: 726479

A likely reason why you get the error is hidden inside the implementation of your method:

private List<Integer> getRecentList(List<Integer> searchParams, 
                                Function<List<Integer>, List<Object>> listGetter,
                                Supplier<Integer> idGetter)
{
    List<Object> objectList = listGetter.apply(searchParams);
    List<Integer> idList = new ArrayList<>();
    for (Object o: objectList)
    {
        idList.add(idGetter.get()); // <<<<<==== Here
    }
    return idList;
}

Note how o from the for loop is not used in the call, indicating that idGetter would get you an ID out of thin air. That's of course is not true: you need to pass an item to idGetter, which means that the call should be

idList.add(idGetter.apply(o));

(from the comment) I can't cast within the method, I don't know what type of object to cast to there.

which in turn means that idGetter should be Function<Object,Integer>:

for (Object o: objectList)
{
    idList.add(idGetter.apply(o));
}

Since you would like to reuse the same function for lists of different types, the call would have to use a lambda that performs the cast at the caller, i.e. at the point where you know the type:

List<Integer> idList = setRecentList(searchParams, 
                                 (a) -> serviceOne.getItemList(a), 
                                 (o) -> ((Item)o).getItemId());

Upvotes: 3

Related Questions