2

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?

7
  • 1
    What's the compile error? Can you post the Item class? Commented Apr 19, 2018 at 18:47
  • @pkpnd Item is just a bean; the getItemId is just a getter of an Integer on that bean. Commented Apr 19, 2018 at 18:50
  • Code will work only if method getItemId of class Item is a static no-arg method returning int or Integer? If it is a non-static method, then you need an instance to call the method, e.g. someItem::getItemId would work. Commented Apr 19, 2018 at 18:50
  • I see in documentation that it is possible to pass in a lambda expression that is an instance method; it doesn't have to be static. In the first case, the instance method is known to both caller and called, but in the second, the instance method is known to the caller and only its interface is known to the called. I am trying to determine how to specify that. Commented Apr 19, 2018 at 18:52
  • @arcy "I see in documentation that it is possible to pass in a lambda expression that is an instance method; it doesn't have to be static." => Not for a Supplier<Integer>, but for a Function<Item, Integer>. Passing the method reference Item::getItemId uses an instance as a first argument for the function (the Item part). Commented Apr 19, 2018 at 19:00

2 Answers 2

3

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());
Sign up to request clarification or add additional context in comments.

2 Comments

Should have made a point of this earlier -- I want the listGetter method to return a uniform list of things, but different things for different calls -- that's why I specified it as List<Object>. Then I want the second function passed in to perform the correct method on whatever was returned to get the ID I'm after. So I can't cast within the method, I don't know what type of object to cast to there.
@Andreas OP says he cannot use Item anyway, so it looks like he would need a cast on the calling side.
1

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());

8 Comments

I'm attempting to do this so that the list returned by listGetter is all one type of object, but could be different types for different calls. It might return a list of suppliers in one call, a list of vendors in the other. The purpose of the overall call would be to get a list of IDs of either suppliers or vendors, depending on what was passed in.
@arcy I updated the answer, see if it meets your needs.
You missed that I said I cannot change the classes of which I'm getting lists. I can define all the interfaces I want, but I cannot make them implement them.
@arcy FYI your latest revision was long after the update to my answer, so I didn't "miss" that detail.
sorry, it was in an early version of the post, and I guess it got edited out. I can't alter them, in any event.
|

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.