Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to link generic in lambda function between argument and result

I would like to link generic type from argument and result in a Function. Here is a simple example :

private void take(final Function<ListIterator<?>, ?> takeFunction) {
    final ListIterator<String> stringItr = new ArrayList<String>().listIterator();
    final ListIterator<Integer> intItr = new ArrayList<Integer>().listIterator();
    final String string = (String) takeFunction.apply(stringItr);
    final Integer integer = (Integer) takeFunction.apply(intItr);
}

take(itr -> itr.next());
take(itr -> itr.previous());

I don't want to do cast because it is obvious that my function returns what is in the list. I would like to write something like this :

private void take(final Function<ListIterator<T>, T> takeFunction) {
    final ListIterator<String> stringItr = new ArrayList<String>().listIterator();
    final ListIterator<Integer> intItr = new ArrayList<Integer>().listIterator();
    final String string = takeFunction.apply(stringItr);
    final Integer integer = takeFunction.apply(intItr);
}

Is there any way to achieve that ?

Thanks in advance

Edit : Some people asked a more real code. I try to create an iterator on a list of list, but I want my sub lists are loading only when needing. I would like to make a generic method to be called by my own ListIterator::next and previous.

Here is a more concrete code :

    List<List<String>> lists;
    ListIterator<List<String>> curListsIter;
    ListIterator<String> curStringIter;

    @Override
    public String next() {
        return get(
                listIterator -> listIterator.hasNext(),
                listIterator -> listIterator.next(),
                list -> list.listIterator());
    }

    @Override
    public String previous() {
        return get(
                listIterator -> listIterator.hasPrevious(),
                listIterator -> listIterator.previous(),
                list -> list.listIterator(list.size()));
    }

    private String get(final Function<ListIterator<?>, Boolean> has,
            final Function<ListIterator<?>, ?> item,
            final Function<List<String>, ListIterator<String>> iterator) {
        // The current sub list has an item to get
        if (has.apply(curStringIter)) {
            return (String) item.apply(curStringIter);
        }
        // There is a list in the direction
        else if (has.apply(curListsIter)) {
            curStringIter = iterator.apply(
                (List<String>) item.apply(curListsIter));
            return get(has, item, iterator);
        }
        // No more item in sub list nor list
        else {
            throw new NoSuchElementException();
        }
    }

This code compiles and potentially works but I would like to do that without both casts in get method (into 'String' and 'List').

like image 483
user3468272 Avatar asked Mar 05 '23 13:03

user3468272


2 Answers

It seems to me that you need to define your own Function interface. Something like:

interface IterFunction {
    <T> T apply(Iterator<T> t);
}

and now the code works:

private void take(final IterFunction takeFunction) {
    final ListIterator<String> stringItr = new ArrayList<String>().listIterator();
    final ListIterator<Integer> intItr = new ArrayList<Integer>().listIterator();
    final String string = takeFunction.apply(stringItr);
    final Integer integer = takeFunction.apply(intItr);
}
like image 198
grape_mao Avatar answered Mar 16 '23 00:03

grape_mao


Building on this answer, you can solve your actual problem like this:

public class Example {
    List<List<String>> lists;
    ListIterator<List<String>> curListsIter;
    ListIterator<String> curStringIter;

    @Override
    public String next() {
        return get(ListIterator::hasNext, ListIterator::next, List::listIterator);
    }

    @Override
    public String previous() {
        return get(ListIterator::hasPrevious, ListIterator::previous, Example::atEnd);
    }

    private static <T> ListIterator<T> atEnd(List<T> list) {
        return list.listIterator(list.size());
    }

    interface IterFunction {
        <T> T apply(ListIterator<T> t);
    }
    interface GetIterFunction {
        <T> ListIterator<T> apply(List<T> t);
    }
    private String get(Predicate<ListIterator<?>> has,
                       IterFunction item, GetIterFunction getIter) {
        if(curListsIter == null) curListsIter = getIter.apply(lists);
        while(curStringIter == null || !has.test(curStringIter)) {
            // may throw NoSuchElementException
            curStringIter = getIter.apply(item.apply(curListsIter));
        }
        return item.apply(curStringIter);
    }
}

A functional interface with a generic function method can not get implemented via lambda expression, as there is no syntax for declaring type variables for them. But they can be implemented via method references, which is simple here, as most functions do just call an existing method.

Only acquiring a list iterator pointing at the list’s end is not expressible as a single invocation of an existing method, that’s why we have to create a new one here, the atEnd method. Note that this is basically what the compiler does for lambda expressions under the hood, generating a synthetic method holding the lambda’s body and creating a method reference to it. But when declaring the method manually, we can make it generic, unlike lambda expressions.

like image 37
Holger Avatar answered Mar 16 '23 01:03

Holger