Почему возврат к вызывающей программе работает для `List`, а не для` Map`? - PullRequest
0 голосов
/ 29 ноября 2018

Я хочу "вырваться" из forEach и вернуться непосредственно к абоненту bar при определенных условиях.

С List это работает:

fun bar(collection: List<String>): Boolean {
    collection.forEach { value ->
        if ("foo".equals(value))
            return true
    }
    return false
}

Но если collection является Map, я получаю ошибку компиляции:

fun bar(collection: Map<String, String>): Boolean {
    collection.forEach { key, value ->
        if ("foo".equals(key))
            return true // compilation error: 'return' is not allowed here. 
    }
    return false
}

Почему?

(Пожалуйста, не против использования forEach для простого поиска в этом случае. Это просто минимальный пример. Фактический код намного сложнее.)

Ответы [ 2 ]

0 голосов
/ 29 ноября 2018

Ответ Рики Мо находится на правильном пути объяснения причины вашей ошибки, но я думаю, что есть еще кое-что о том, как ее исправить.

Чтобы кратко повторить этот ответ:

  • Ваш текущий вызов forEach на List вызывает функцию стандартной библиотеки Kotlin Iterable.forEach, которая является встроенной функцией, , что позволяет вамчтобы вернуть из bar в лямбду, которую вы передаете ей.Эта функция принимает лямбда-параметр, который сам по себе имеет только один параметр.
  • В другом случае с Map вы фактически вызываете определенный метод Java forEachна Map, который принимает BiConsumer, интерфейс, который по сути является двухпараметрической лямбда-выражением.В Java нет концепции встраивания, поэтому вы не можете сделать нелокальный возврат из этого BiConsumer.

Давайте поговорим о решениях.

  1. Выможно использовать Kotlin Iterable.forEach и в случае Map, поскольку он равен и Iterable.Чтобы вызвать это forEach, вам просто нужно передать лямбду, которая принимает один параметр вместо двух:

    collection.forEach { entry ->
        if ("foo".equals(entry.key))
            return true
    }
    

    Здесь будет работать возврат, так как this forEach

  2. Вы также можете выполнить предыдущий вызов, используя деструктурирование в записях карты:

    collection.forEach { (key, value) ->
        if ("foo".equals(key))
            return true
    }
    

    Этот синтаксисочень близко к вашему первоначальному вызову (возможно, это раздражает), но у этой лямбды все еще есть один параметр, что делает это вызовом стандартной библиотеки forEach библиотеки Kotlin вместо метода Java, который принимает два параметра.

  3. В качестве последнего незначительного шага вы можете использовать _ в качестве имени значения, если вы не используете его в лямбда-выражении:

    collection.forEach { (key, _) ->
        if ("foo".equals(key))
            return true
    }
    
0 голосов
/ 29 ноября 2018

Map имеет другую реализацию forEach.Вы можете посмотреть исходный код.

Для List:

public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

Для Map (это Java):

default void forEach(BiConsumer<? super K, ? super V> action) {
    Objects.requireNonNull(action);
    for (Map.Entry<K, V> entry : entrySet()) {
        K k;
        V v;
        try {
            k = entry.getKey();
            v = entry.getValue();
        } catch (IllegalStateException ise) {
            // this usually means the entry is no longer in the map.
            throw new ConcurrentModificationException(ise);
        }
        action.accept(k, v);
    }
}

list.forEach принять function type, в то время как map.forEach принять BiConsumer экземпляр.

Для List, как подсказывается ключевым словом inline, вы можете заменить вызов forEach на

for (value in collection) 
{
    if("foo".equals(value))
    {
        return true
    }
}

и все имеет смысл с возвратом.

Лямбда, которую вы передаете map.forEach, на самом деле является реализацией функции-члена accept интерфейса BiConsumer, тип которого void.Вот почему возвращать Boolean не имеет смысла.Даже вы просто return, это просто конец метода accept.Поскольку это не функция kotlin inline, она не завершит функцию включения.

Исходный код Java BiConsumer

public interface BiConsumer<T, U> {

    /**
     * Performs this operation on the given arguments.
     *
     * @param t the first input argument
     * @param u the second input argument
     */
    void accept(T t, U u);

    /**
     * Returns a composed {@code BiConsumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code BiConsumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);

        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}

Без каких-либо упрощений ваша функция на самом деле выглядитвот так:

fun bar(collection: Map<String, String>): Boolean {
    val action : BiConsumer<String,String> = object : BiConsumer<String, String> {
        override fun accept(t: String, u: String) {
            //return boolean is not allow here
            //return at here just end the accept function. bar is not affected
        }
    }
    collection.forEach(action)
    return false
}

Поскольку kotlin преобразует реализацию интерфейса одного метода в лямбду, создается иллюзия, что map.forEach выглядит как встроенный вызов, принимающий function type точно так же, как List.Правда состоит в том, что лямбда, принятая map.forEach, - это не kotlin function type, а реализация BiConsumer, а самое главное, это не inline.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...