功能接口: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

相关文章

Python使用FastAPI实现大文件分片上传与断点续传功能

《Python使用FastAPI实现大文件分片上传与断点续传功能》大文件直传常遇到超时、网络抖动失败、失败后只能重传的问题,分片上传+断点续传可以把大文件拆成若干小块逐个上传,并在中断后从已完成分片继... 目录一、接口设计二、服务端实现(FastAPI)2.1 运行环境2.2 目录结构建议2.3 serv

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

Debian 13升级后网络转发等功能异常怎么办? 并非错误而是管理机制变更

《Debian13升级后网络转发等功能异常怎么办?并非错误而是管理机制变更》很多朋友反馈,更新到Debian13后网络转发等功能异常,这并非BUG而是Debian13Trixie调整... 日前 Debian 13 Trixie 发布后已经有众多网友升级到新版本,只不过升级后发现某些功能存在异常,例如网络转

基于Java和FFmpeg实现视频压缩和剪辑功能

《基于Java和FFmpeg实现视频压缩和剪辑功能》在视频处理开发中,压缩和剪辑是常见的需求,本文将介绍如何使用Java结合FFmpeg实现视频压缩和剪辑功能,同时去除数据库操作,仅专注于视频处理,需... 目录引言1. 环境准备1.1 项目依赖1.2 安装 FFmpeg2. 视频压缩功能实现2.1 主要功

使用Python实现无损放大图片功能

《使用Python实现无损放大图片功能》本文介绍了如何使用Python的Pillow库进行无损图片放大,区分了JPEG和PNG格式在放大过程中的特点,并给出了示例代码,JPEG格式可能受压缩影响,需先... 目录一、什么是无损放大?二、实现方法步骤1:读取图片步骤2:无损放大图片步骤3:保存图片三、示php

深度解析Python yfinance的核心功能和高级用法

《深度解析Pythonyfinance的核心功能和高级用法》yfinance是一个功能强大且易于使用的Python库,用于从YahooFinance获取金融数据,本教程将深入探讨yfinance的核... 目录yfinance 深度解析教程 (python)1. 简介与安装1.1 什么是 yfinance?

Python脚本轻松实现检测麦克风功能

《Python脚本轻松实现检测麦克风功能》在进行音频处理或开发需要使用麦克风的应用程序时,确保麦克风功能正常是非常重要的,本文将介绍一个简单的Python脚本,能够帮助我们检测本地麦克风的功能,需要的... 目录轻松检测麦克风功能脚本介绍一、python环境准备二、代码解析三、使用方法四、知识扩展轻松检测麦

Kotlin 枚举类使用举例

《Kotlin枚举类使用举例》枚举类(EnumClasses)是Kotlin中用于定义固定集合值的特殊类,它表示一组命名的常量,每个枚举常量都是该类的单例实例,接下来通过本文给大家介绍Kotl... 目录一、编程枚举类核心概念二、基础语法与特性1. 基本定义2. 带参数的枚举3. 实现接口4. 内置属性三、

Java实现TXT文件导入功能的详细步骤

《Java实现TXT文件导入功能的详细步骤》在实际开发中,很多应用场景需要将用户上传的TXT文件进行解析,并将文件中的数据导入到数据库或其他存储系统中,本文将演示如何用Java实现一个基本的TXT文件... 目录前言1. 项目需求分析2. 示例文件格式3. 实现步骤3.1. 准备数据库(假设使用 mysql

Springboot项目登录校验功能实现

《Springboot项目登录校验功能实现》本文介绍了Web登录校验的重要性,对比了Cookie、Session和JWT三种会话技术,分析其优缺点,并讲解了过滤器与拦截器的统一拦截方案,推荐使用JWT... 目录引言一、登录校验的基本概念二、HTTP协议的无状态性三、会话跟android踪技术1. Cook