Почему возврат к вызывающему срабатывает для List, а не Map?

Я хочу "вырваться" из 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 для простого поиска в этом случае. Это всего лишь минимальный образец. Фактический код намного сложнее.)

обратите внимание, что почти все, что может быть выражено с помощью forEach, также может быть выражено другими средствами ... и часто это приводит к чему-то более простому в конце ... но может потребоваться больше времени, чтобы найти подходящее решение ... (но иногда вы видите, что, возможно, вы выбрали неправильную структуру данных для начала) ... Я просто упоминаю это, потому что вы говорите, что это всего лишь минимальный образец, но оригинал намного сложнее ;-))

Roland 29.11.2018 07:18

возможно, в этом конкретном случае вам также будет интересно следующее: Справка по Kotlin - преобразования SAM

Roland 29.11.2018 07:21
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
63
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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, она не завершает функцию включения.

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

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.

Ответ принят как подходящий

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

Кратко резюмируя этот ответ:

  • Ваш текущий вызов 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
    }
    

    Здесь возврат будет работать, поскольку это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
    }
    

Другие вопросы по теме