为什么在Kotlin中链接.map()和.filter()不能按预期工作?

时间:2018-07-08 21:07:21

标签: functional-programming kotlin java-stream

我试图将对象列表映射到另一种类型的对象列表,然后过滤该列表,然后映射到第三种类型的列表,就像我通过在Java 8+中链接流一样(更改了类和变量名以使其更有意义,但结构与我的代码相同)

val results: List<AccountDto> = listOfPersons
                .map { person -> getPersonAccount(person) }
                .filter { account ->
                    if(validateAccount(account)){ // validateAccount is a function with boolean return type
                        // do something here like logging
                        return true
                    }
                    // do something else...
                    return false
                }
                .map { account ->
                     toDto(account) // returns an AccountDto
                }

我在过滤器lambda内的return truereturn false语句上收到编译器错误:

Error:(217, 32) Kotlin: The boolean literal does not conform to the expected type List<AccountDto>

如果我对过滤谓词使用匿名函数,则可以正常编译:

.filter (fun(account):Boolean{
                if(validateAccount(account)){ 
                        // do something here like logging
                        return true
                    }
                    // do something else...
                    return false
            })

为什么在这种情况下类型推断会失败?

是否可以使我仅使用lambda起作用?

2 个答案:

答案 0 :(得分:9)

return从最近的封闭函数或匿名函数(格式为fun(a1: T1, ..., an: TN): TR = ...)返回。助记符是不合格的return到最近的fun。 Lambda不是匿名函数,因此return实际上是从包含整个map / filter表达式的函数返回的。您可以使用带有标签的退货:

val results: List<AccountDto> = listOfPersons
                .map(::getPersonAccount)
                .filter { account ->
                    if(validateAccount(account)){
                        // etc.
                        return@filter true
                    }
                    // etc.
                    return@filter false
                }
                .map(::toDto)

lambda表达式使用传递给它的函数隐式标记,因此您无需显式添加标签。如果您希望拥有它,可以:

val results: List<AccountDto> = listOfPersons
                .map(::getPersonAccount)
                .filter lam@{ account ->
                    if(validateAccount(account)){
                        // etc.
                        return@lam true
                    }
                    // etc.
                    return@lam false
                }
                .map(::toDto)

但是if / else本身就是一个表达式,因此只要没有return就足够了。

val results: List<AccountDto> = listOfPersons
                .map(::getPersonAccount)
                .filter { account ->
                    if(validateAccount(account)) {
                        // etc.
                        true
                    } else {
                        // etc.
                        false
                    }
                }
                .map(::toDto)

答案 1 :(得分:4)

之所以会出现此错误,是因为lambda中的普通return往往会从最外面的范围(函数本身)返回。

Lambda期望隐式返回(每个执行路径上的最后一个表达式)或显式标记返回return@filter

删除return或用return@filter替换将修复错误。