功能接口:Kotlin中的自我厌恶

2023-10-18 16:50

本文主要是介绍功能接口:Kotlin中的自我厌恶,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

This post is a copy from previous posts on Medium (initial, follow-up) But since I'm planning on deleting my Medium account I moved them here.

Kotlin是一种很棒的编程语言。 经过大约12年的Java编程工作,与Kotlin一起工作了多年之后,感觉就像戴上眼镜一样:有太多的爱。

但是,就像每一个恋爱关系一样,您在生活的晚些时候只会发现一些怪癖。 在将越来越多的Java代码迁移到Kotlin代码之后,我注意到了一些比较奇怪且坦率的令人讨厌的地方。

就是这样科特林处理功能接口。

Java 7: a blast from the past

让我们回到没有lambda的世界。 太冗长了!

interface JavaInterface {String doSomething(Item item);
}String delegateWork(JavaInterface f) {return f.doSomething(item);
}void doWork() {delegateWork(new JavaInterface() {@Overridepublic String doSomething(Item item) {return "Item = " + item;}});
}

Java 8: Lambdas to the rescue!

最终,Java 8为我们提供了Lambdas,我们可以摆脱很多代码,专注于重要的事情。 同样,我们也不必为每个简单的函数编写自己的函数接口,而只需使用oracle提供的某些函数即可,例如:java.util.function.Function<T, R>

@FunctionalInterface
interface JavaInterface {String doSomething(Item item);
}String delegateWork(JavaInterface f) {return f.doSomething(item);
}String delegateOtherWork(Function<Item, String> f) {return f.apply(item);
}void doWork() {delegateWork(item -> "Item = " + item);delegateOtherWork(item -> "Item = " + item);
}

一切都很好,直到您意识到即使您现在拥有函数类型,它们仍不是该语言的一等公民。 要证明吗? 猜猜Java中必须引入多少个“函数类型”? 一? 三? 五?

43!

Don't believe me, see for yourself: https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

And if that's not enough for you, add jOOL to the mix and you have access to 35 more: https://github.com/jOOQ/jOOL/tree/master/jOOL/src/main/java/org/jooq/lambda/function
Because who wouldn't love coming across a method signature that looks like this:

Function5<String, String, String, String, String, Tuple3<String, String, String>> higherOrder(Function12<String, Integer, String, Object, Object, Object, BiFunction<String, Integer, String>, String, Integer, Long, String, Double, Optional<Tuple2<String, String>>>)

😜旁注:jOOL实际上是一个非常简洁的库,值得一试。

Kotlin help us!

现在,将Kotlin添加到混合中。 在科特林,职能是一等公民。 因此,无需记住数十种稍有不同的功能类型。 您只需要记住Kotlin的函数类型语法:

(Parameter1Type, Parameter2Type, ParameterNType) -> ReturnType

就是这样,仅此而已。

Trouble in paradise

好吧,我们为什么在这里,怎么了?

如前所述,随着我将越来越多的代码从Java迁移到Kotlin。 使用自定义功能接口时遇到一些问题。 因为有时候您想要那种额外的描述性。

回到我们的Java 8示例

@FunctionalInterface
interface JavaInterface {String doSomething(Item item);
}class JavaComponent {private Item item = new Item();String delegateWork(JavaInterface f) {return f.doSomething(item);}String delegateOtherWork(Function<Item, String> f) {return f.apply(item);}
}

现在让我们从Kotlin代码中使用它

delegateWork { "Print $it" }
delegateOtherWork { "Print $it" }

很好,这很棒,正好符合我们的期望! 好吧,现在让我们迁移一下Java组件上科特林。 请注意,我们已经更改了java.util.function.Function<Item, String>到Kotlin函数类型(Item) -> String

class KotlinComponent(private val item: Item = Item()) {fun delegateWork(f: JavaInterface): String {return f.doSomething(item)}fun delegateOtherWork(f: (Item) -> String): String {return f.invoke(item)}
}

让我们看看使用Java代码中的这些高阶函数会发生什么。

delegateWork(item -> "Print: " + item);
delegateOtherWork(item -> "Print: " + item);

没有什么与众不同的,我们可以对两种方法使用相同的lambda。 让我们看看当我们完成Kotlin的预期时会发生什么:

delegateWork { "Print $it" }Error: Kotlin: Type mismatch: inferred type is () -> String but JavaInterface was expected

What happened? It seems the compiler can't figure out that the signature of the lambda is the same as the functional interface method. https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions

因此,我们必须明确地说出我们的期望:

delegateWork(JavaInterface { "Print $it" })

我认为这很令人失望,但还算不错。 现在让我们看看将接口迁移到Kotlin时会发生什么:

interface KotlinInterface {fun doSomething(item: Item): String
}class KotlinComponent(private val item: Item = Item()) {fun delegateWork(f: KotlinInterface): String {return f.doSomething(item)}fun delegateOtherWork(f: (Item) -> String): String {return f.invoke(item)}
}

当我们使用Kotlin组件从Java类开始,正如预期的一样,lambda保持完全相同。 如果我们从Kotlin代码中使用它,该怎么办:

delegateWork { "Print $it" }Error: Kotlin: Type mismatch: inferred type is () -> String but KotlinInterface was expected

看来SAM转换再次失败。 现在,如果我们像以前一样明确提及接口,该怎么办?

delegateWork(KotlinInterface { "Print $it" })Error: Kotlin: Interface KotlinInterface does not have constructors

这也没有帮助。 我们需要创建一个匿名对象以使其工作:

delegateWork(object : KotlinInterface {override fun doSomething(item: Item): String {return "Print $item"}
})

Yikes! This feels like working with Java 7 all over again. Sadly this is because Kotlin doesn't yet support SAM conversion for Kotlin interfaces so we have to create this anonymous object. See also:
https://youtrack.jetbrains.com/issue/KT-7770
https://stackoverflow.com/a/43737962/611032

Alias time!

那么,如何避免使用这些冗长的匿名对象,而仍然为该函数使用自定义名称? 我们使用类型别名:

/**
 * Very helpful comment.
 */
typealias KotlinFunctionAlias = (Item) -> Stringfun delegateAliasWork(f: KotlinFunctionAlias): String {return f.invoke(item)
}

因此,现在我们可以按期望的方式传递lambda了,我们仍然可以从函数的自定义名称中受益。

delegateAliasWork { "Print $it" }

这样一切就好了,案件结案了,该回家了。 不幸的是不完全是。

Lost in translation

类型别名的一个小问题是,虽然您可以命名函数类型,但不能命名方法名称:

val iface: JavaInterface = JavaInterface { "Print $it" }
iface.doSomething(item)val alias: KotlinFunctionalAlias = { item -> "Print $item" }
alias.invoke(item)
alias(item)

为类型别名和变量选择好名字可以缓解此问题。 幸运的是,我们的开发人员擅长命名事物things

Type safety

更大的问题是,尽管类型别名为我们提供了不同的名称,但它们并不是真正的不同类型,因此我们实际上并不是安全的类型。

让我们来看一个Java示例,其中的两个功能接口具有相同的方法签名。

JavaInterface1 f1 = item -> "Print " + item;
JavaInterface2 f2 = item -> "Print " + item;
f1 = f2;Error: java: incompatible types: JavaInterface2 cannot be converted to JavaInterface1

这就是我们期望的,我们不想在这里混合苹果和橙子。

如果我们使用Kotlin类型别名做同样的事情,会发生什么? (我想你知道我要去哪里了)

var f1: KotlinFunctionAlias1 = { item -> "Print $item" }
var f2: KotlinFunctionAlias2 = { item -> "Print $item" }
var f3: (Item) -> String = { item -> "Print $item" }
f1 = f2
f2 = f3
f1 = f3

这样做很好,编译器不会抱怨,因为就像我提到的那样,它们实际上并不是不同的类型。 它们都是:(Item) -> String

Solutions

因此,让我们快速回顾一下解决Kotlin接口缺少的SAM转换以及Kotlin接口的优点和缺点的不同方法。

Leave functional interfaces as Java interfaces

+良好的Java互操作性 +支持自定义方法名称 +类型安全

-需要给Kotlin lambda加上接口名称前缀 -需要额外的括号 -需要维护Java代码

Use a type alias for Kotlin function types

+良好的Java互操作性 +易于使用

-不安全输入 -没有自定义方法名称

Use inline classes

我们尚未讨论的另一种选择是使用实验性Kotlin内联类。 您可以使用内联类“包装” Kotlin函数。

inline class KotlinInlineInterface(val doSomething: (Item) -> String)fun delegateInlineWork(f: KotlinInlineInterface): String {return f.doSomething.invoke(item)
}delegateInlineWork(KotlinInlineInterface { "Print $it" })

Even though this works, I don't thinks it's an appropriate way of using inline classes. Also Java interoperability isn't currently supported: https://kotlinlang.org/docs/reference/inline-classes.html#mangling

Always use Kotlin function types

是的,您可以使用(ParamT) -> ReturnT类型无处不在。 通常这就足够了,但是随着您的应用程序的增长,它可能会变得更难阅读和维护,并且更容易出错。

Live with anonymous objects

当然,如果您不介意,则可以只使用匿名对象,希望有一天Kotlin将支持完整的SAM转换,并利用出色的IDE集成将您的匿名对象迁移到lambdas。

¯\(ツ)/¯

Jetbrains Feedback

There has been a short discussion on Reddit: https://www.reddit.com/r/Kotlin/comments/bipj0q/functional_interfaces_selfloathing_in_kotlin/

从那时起,我得到了罗马·伊里扎洛夫(Roman Elizarov)的回应

我尝试了提到的Kotlin编译器选项:

// Gradle Kotlin DSL
tasks.withType<KotlinCompile> {kotlinOptions.freeCompilerArgs += "-XXLanguage:+NewInference"
}
// Gradle Groovy DSL
compileKotlin {kotlinOptions {freeCompilerArgs += "-XXLanguage:+NewInference"}
}

If you're more into other build systems, refer to Kotlin documentation (Maven / Ant) to see how to pass Kotlin compiler arguments.

Problem solved?

首先让我们看看在Kotlin代码中使用Kotlin功能接口时会发生什么:

fun delegateWork(f: KotlinInterface): String {return f.doSomething(item)
}delegateWork { item -> "Print: $item" }Error: Type mismatch: inferred type is (Nothing) -> TypeVariable(_L) but KotlinInterface was expected

显式指定接口呢?

delegateWork(KotlinInterface { item -> "Print $item" }Error: Interface KotlinInterface does not have constructors

mm! 我们仍然需要一个匿名对象。

怎么样使用Kotlin中的Java功能接口码?

fun javaInterface(f: JavaInterface) {val res = f.doSomething(item)output(res)
}javaInterface { item -> "Print: $item" }

最后:正是我们所期望的。 一切都很好,啤酒当之无愧!

Patience young Jedi

如果您观察的话,会在构建过程中看到以下内容:

w: ATTENTION!
This build uses unsafe internal compiler arguments:
-XXLanguage:+NewInferenceThis mode is not recommended for production use,
as no stability/compatibility guarantees are given on
compiler or generated code. Use it at your own risk!

那是什么意思呢? 这意味着它在这里所说的内容:使用起来还不是很安全。 但是了解到JetBrains正朝着这个方向努力时,我建议我们暂时按照以下方式进行操作(从最有利到最不利)

  1. 将功能接口保留为Java代码为Kotlin函数类型使用类型别名(如果您可以将苹果和橙子混合使用)与匿名对象一起生活

谢谢阅读。 一如既往,我乐于接受批评和反馈。

from: https://dev.to//ranilch/functional-interfaces-self-loathing-in-kotlin-4bce

这篇关于功能接口:Kotlin中的自我厌恶的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/233900

相关文章

SpringBoot实现RSA+AES自动接口解密的实战指南

《SpringBoot实现RSA+AES自动接口解密的实战指南》在当今数据泄露频发的网络环境中,接口安全已成为开发者不可忽视的核心议题,RSA+AES混合加密方案因其安全性高、性能优越而被广泛采用,本... 目录一、项目依赖与环境准备1.1 Maven依赖配置1.2 密钥生成与配置二、加密工具类实现2.1

使用Python的requests库调用API接口的详细步骤

《使用Python的requests库调用API接口的详细步骤》使用Python的requests库调用API接口是开发中最常用的方式之一,它简化了HTTP请求的处理流程,以下是详细步骤和实战示例,涵... 目录一、准备工作:安装 requests 库二、基本调用流程(以 RESTful API 为例)1.

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

深度解析Spring Security 中的 SecurityFilterChain核心功能

《深度解析SpringSecurity中的SecurityFilterChain核心功能》SecurityFilterChain通过组件化配置、类型安全路径匹配、多链协同三大特性,重构了Spri... 目录Spring Security 中的SecurityFilterChain深度解析一、Security

Java实现预览与打印功能详解

《Java实现预览与打印功能详解》在Java中,打印功能主要依赖java.awt.print包,该包提供了与打印相关的一些关键类,比如PrinterJob和PageFormat,它们构成... 目录Java 打印系统概述打印预览与设置使用 PageFormat 和 PrinterJob 类设置页面格式与纸张

MySQL 8 中的一个强大功能 JSON_TABLE示例详解

《MySQL8中的一个强大功能JSON_TABLE示例详解》JSON_TABLE是MySQL8中引入的一个强大功能,它允许用户将JSON数据转换为关系表格式,从而可以更方便地在SQL查询中处理J... 目录基本语法示例示例查询解释应用场景不适用场景1. ‌jsON 数据结构过于复杂或动态变化‌2. ‌性能要

Kotlin Map映射转换问题小结

《KotlinMap映射转换问题小结》文章介绍了Kotlin集合转换的多种方法,包括map(一对一转换)、mapIndexed(带索引)、mapNotNull(过滤null)、mapKeys/map... 目录Kotlin 集合转换:map、mapIndexed、mapNotNull、mapKeys、map

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期

Qt使用QSqlDatabase连接MySQL实现增删改查功能

《Qt使用QSqlDatabase连接MySQL实现增删改查功能》这篇文章主要为大家详细介绍了Qt如何使用QSqlDatabase连接MySQL实现增删改查功能,文中的示例代码讲解详细,感兴趣的小伙伴... 目录一、创建数据表二、连接mysql数据库三、封装成一个完整的轻量级 ORM 风格类3.1 表结构

mysql表操作与查询功能详解

《mysql表操作与查询功能详解》本文系统讲解MySQL表操作与查询,涵盖创建、修改、复制表语法,基本查询结构及WHERE、GROUPBY等子句,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随... 目录01.表的操作1.1表操作概览1.2创建表1.3修改表1.4复制表02.基本查询操作2.1 SE