Как связать универсальное в лямбда-функции между аргументом и результатом - PullRequest
0 голосов
/ 28 августа 2018

Я хотел бы связать универсальный тип из аргумента и привести к функции. Вот простой пример:

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

Я не хочу выполнять приведение, потому что очевидно, что моя функция возвращает то, что находится в списке. Я хотел бы написать что-то вроде этого:

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

Есть ли способ достичь этого?

Заранее спасибо

Редактировать: Некоторые люди спрашивают более реальный код. Я пытаюсь создать итератор в списке списка, но я хочу, чтобы мои подсписки загружались только при необходимости. Я хотел бы сделать общий метод, который будет вызываться моим собственным ListIterator :: next и previous.

Вот более конкретный код:

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

Этот код компилируется и потенциально работает, но я хотел бы сделать это без обоих приведений в методе get (в 'String' и 'List').

Ответы [ 3 ]

0 голосов
/ 28 августа 2018

Мне кажется, что вам нужно определить свой собственный Function интерфейс. Что-то вроде:

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

и теперь код работает:

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);
}
0 голосов
/ 28 августа 2018

Опираясь на этот ответ , вы можете решить свою актуальную проблему следующим образом:

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

Функциональный интерфейс с методом обобщенной функции не может быть реализован с помощью лямбда-выражения, поскольку для них нет синтаксиса объявления переменных типа. Но они могут быть реализованы с помощью ссылок на методы, что здесь просто, так как большинство функций просто вызывают существующий метод.

Только получение итератора списка, указывающего на конец списка, не может быть выражено как одиночный вызов существующего метода, поэтому мы должны создать здесь новый метод atEnd. Обратите внимание, что это в основном то, что компилятор делает для лямбда-выражений под капотом, генерируя синтетический метод, содержащий тело лямбда-выражения, и создавая ссылку на метод. Но при объявлении метода вручную мы можем сделать его универсальным, в отличие от лямбда-выражений.

0 голосов
/ 28 августа 2018

В примере вы пытаетесь связать T с Integer и String, но ничто не гарантирует, что T является одним из них. Что касается Java, T - это Object, поскольку нет привязки ни к чему более конкретному, и поэтому требуется приведение, чтобы вы понимали, что выполняете небезопасные операции с типами.

Тем не менее, код, который использует только общие типы вместо смешения T с конкретными типами, работает нормально:

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

Очевидно, ваш пример кода не является вашим реальным кодом, так как вы перебираете пустые списки внутри метода, поэтому, если вы покажете свой реальный код, мы сможем посмотреть, как его реорганизовать, чтобы он работал хорошо.

...