为什么返回呼叫者只能用于“列表”,而不能用于“地图”?

时间:2018-11-29 03:04:40

标签: kotlin

在某些情况下,我想“脱离” ArrayList<Pair<K,V>> set_of_pairs = new ArrayList<Pair<K,V>>(); public void insert(K key, V value) { Pair<K,V> pair = new Pair<K,V>(key,value); set_of_pairs.add(pair); } public void traverse(Visitor<K,V> visitor) { } public V retrieve(K key) { int i = 0; if (set_of_pairs.containsKeys(key) == false) { return null; } else { for(Pair<K,V> set_of_pairs: pair) { if (pair.getKey() == key) { return pair.getValue(); } } } } 并直接返回到forEach的呼叫者。

使用bar可以起作用:

List

但是如果fun bar(collection: List<String>): Boolean { collection.forEach { value -> if ("foo".equals(value)) return true } return false } collection,则会出现编译错误:

Map

为什么?

(在这种情况下,请不要介意使用forEach进行简单搜索。这只是一个最小的示例。实际代码要复杂得多。)

2 个答案:

答案 0 :(得分:4)

Ricky Mo's answer在解释为什么会发生错误的正确路线上,但我认为还有更多的方法可以解决错误。

简而言之:

  • 您当前在forEach上对List的调用正在调用Kotlin标准库函数Iterable.forEach,它是一个内联函数,位于bar中您传递给它的lambda。此函数使用一个lambda参数,该参数本身只有一个参数。
  • 在另一种情况下,使用Map,实际上是在Map上定义的Java thus allowing you to return方法,该方法采用BiConsumer,这实际上是一个接口两参数lambda。 Java没有内联的概念,因此您不能从此BiConsumer进行非本地返回。

我们来谈谈解决方案。

  1. Iterable.forEach的情况下,您也可以使用Kotlin Map,因为它的 Iterable。要调用此forEach,您只需传递一个采用单个参数而不是两个参数的lambda:

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

    返回值将在这里起作用,因为内嵌 this forEach

  2. 您也可以通过在地图条目上使用forEach来进行上一个呼叫:

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

    此语法与您的原始调用非常接近(也许很烦人),但是此lambda仍具有单个参数,从而使它成为对Kotlin标准库forEach函数而不是Java方法的调用。需要两个参数。

  3. 作为最后一个较小的步骤,如果不在lambda中使用值,可以使用_作为值的名称:

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

答案 1 :(得分:3)

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的lambda实际上是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将单方法接口实现转换为lambda,它给您一种幻觉,即map.forEach就像接受function type的内联调用一样,就像List一样。事实是,map.forEach接受的lambda不是kotlin function type,而是BiConsumer实现,最重要的是,它不是inline

相关问题