4

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').

  • 1
    Unrelated: consider *not* putting final on every line. Only use it where it makes sense to. Otherwise it turns into useless noise. – GhostCat Aug 28 '18 at 08:12
  • After your added further code: It seems the parameter `iterator` isn't used in the method `get`. This doesn't answer the question but could reduce complexity. – LuCio Aug 28 '18 at 10:03
  • Sorry it was my mistake. It is used now. It is to know if the iterator must start by the beginning or by the end. – user3468272 Aug 28 '18 at 12:16
  • 3
    @GhostCat [99% of the time I would agree 100% of the time with you](https://stackoverflow.com/a/49417253/1059372) – Eugene Aug 28 '18 at 12:23
  • @Eugene Very interesting answer you wrote there! I guess I learned something today, too. – GhostCat Aug 28 '18 at 12:27

3 Answers3

4

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);
}
grape_mao
  • 1,153
  • 1
  • 8
  • 16
  • The code compiles. The code doesn't "work", because it does nothing. – Kayaman Aug 28 '18 at 08:40
  • @Kayaman you are right, and this is an ugly solution, I do think OP's real code can be refactored better and more generic, we need to know what he does with the return value. – grape_mao Aug 28 '18 at 08:42
  • Unfortunatly the IterFuntion is not a functional interface. As stated in docs.oracle.com/javase/specs/jls/se8/html/… you can see that A lambda expression is congruent with a function type if all of the following are true: ... The function type has no type parameters so a lambda can't be assigned to a functional interface which contains type variables in the function signature (which is not the same as having a generic functional interface). So the code take(itr -> itr.next()); doesn't compile anymore – user3468272 Aug 28 '18 at 08:59
  • @grape_mao I edited my post to add an OP's real code – user3468272 Aug 28 '18 at 09:26
  • 1
    @user3468272 the interface `IterFunction` *is* a functional interface, just one that can’t be implemented with lambda expressions. But it can be implemented using method references, e.g. `take(Iterator::next)`. – Holger Aug 28 '18 at 11:12
3

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.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I thought there is nothing better to be done here... how wrong I was :| – Eugene Aug 28 '18 at 12:26
  • @user3468272 Check this answer, and one more thing, I got the same feeling as @Kayaman, the type `String` really bothers me, I think it can be a generic type on class level, which makes the class a generic iterator, but it all depends how you define your class. – grape_mao Aug 28 '18 at 12:35
0

In the example you're trying to bind T to Integer and String, but nothing guarantees that T is either one of those. As far as Java is concerned, T is an Object since there's no binding to anything more specific, and therefore a cast is required so you understand that you're doing type unsafe operations.

However, code that uses just the generic types instead of mixing T with concrete types works fine:

private <T> T take(ListIterator<T> itr, Function<ListIterator<T>, T> takeFunction) {
    return takeFunction.apply(itr);
}

Obviously your example code isn't your real code, since you're iterating empty lists inside a method, so if you show your real code we can look at how to refactor it to work nicely.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • @user3468272 then it looks like you should be able to replace the wildcards with `T`, return type with `T` and `Function, ListIterator>` with `Function, ListIterator>`. Unless you're working with strings all the time, in which case you shouldn't be using generics for that method at all. – Kayaman Aug 28 '18 at 09:32
  • No I can't because T would be String and I couldn't call `item.apply(curListsIter)` since it is a ListIterator of List – user3468272 Aug 28 '18 at 09:35
  • Then it would be `Function, List> item`. – Kayaman Aug 28 '18 at 09:39
  • I can't neither because I couldn't call `item.apply(curStringIter)` since it is a ListIterator of String or T for you. You didn't get my problem. I need a function which either work with `ListIterator` **and** `ListIterator>` and return respectively `String` **and** `List` – user3468272 Aug 28 '18 at 09:48
  • Your problem is the `curStringIter` and `curListsIter` variables outside the method which you're referring to. You'd need to refactor so you don't try to use your generic types to work with those concrete types. – Kayaman Aug 28 '18 at 09:56
  • @user3468272 it appears that your code is an implementation of `Iterator`, which itself is a generic interface. That means your `curListIter` and other variables should be declared as `ListIterator` and `ListIterator>`. Your code can work, you just need to add more generics where needed. – Kayaman Aug 28 '18 at 10:46