代码之家  ›  专栏  ›  技术社区  ›  jingx

为什么返回调用方对“list”有效而不是“map”?

  •  1
  • jingx  · 技术社区  · 7 年前

    我想“突破”出 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 回复  |  直到 7 年前
        1
  •  4
  •   zsmb13    7 年前

    Ricky Mo's answer 在解释你的错误发生的原因上是正确的,但我认为关于如何解决它还有更多需要补充的内容。

    简单地回顾一下这个答案:

    • 您当前的呼叫 forEach 在一 List 正在调用Kotlin标准库函数 Iterable.forEach ,它是一个内联函数, thus allowing you to return bar 在lambda中传递给它。这个函数接受一个lambda参数,它本身只有一个参数。
    • 在另一种情况下, Map 实际上是调用Java forEach 方法定义于 地图 那需要一个 BiConsumer ,一个基本上是双参数lambda的接口。Java没有内联的概念,因此不能从中执行非本地返回。 消费者 .

    让我们谈谈解决方案。

    1. 你可以用Kotlin 不可更改.foreach 如果是 地图 因为它 Iterable . 调用此 前额 ,只需传递一个lambda,该lambda采用单个参数而不是两个参数:

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

      回来在这里工作,因为 前额 内联。

    2. 您也可以通过这种方式进行上一个调用,使用 destructuring 在地图条目上:

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

      此语法与原始调用非常接近(可能令人恼火),但此lambda仍然有一个参数,使其成为对Kotlin标准库的调用 前额 函数而不是Java方法,它采用两个参数。

    3. 作为最后一个次要步骤,您可以使用 _ 如果不在lambda中使用该值,则作为该值的名称:

      collection.forEach { (key, _) ->
          if ("foo".equals(key))
              return true
      }
      
        2
  •  3
  •   Ricky Mo    7 年前

    Map 有不同的实现 forEach . 您可以查看源代码。

    为了 List :

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

    为了 地图 (这是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 实例。

    为了 ,如 inline 关键字,可以替换 前额 呼叫

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

    一切都有意义。

    你传递到的lambda 地图.foreach 实际上是成员函数的实现 accept 双吸收器 接口,类型是 void . 这就是为什么返回 Boolean 没有道理。即使你只是 return 就这样结束了 接受 方法。因为这不是Kotlin 内联的 函数,它不会结束封闭函数。

    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,因此它给您一种错觉,即 地图.foreach 看起来像是接受 功能类型 就像 . 事实是lambda被 前额 不是科特林 功能类型 但一 双吸收器 而实际上,最重要的是,它不是 内联的 .