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

相关文章

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

Kotlin 作用域函数apply、let、run、with、also使用指南

《Kotlin作用域函数apply、let、run、with、also使用指南》在Kotlin开发中,作用域函数(ScopeFunctions)是一组能让代码更简洁、更函数式的高阶函数,本文将... 目录一、引言:为什么需要作用域函数?二、作用域函China编程数详解1. apply:对象配置的 “流式构建器”最

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

SpringIntegration消息路由之Router的条件路由与过滤功能

《SpringIntegration消息路由之Router的条件路由与过滤功能》本文详细介绍了Router的基础概念、条件路由实现、基于消息头的路由、动态路由与路由表、消息过滤与选择性路由以及错误处理... 目录引言一、Router基础概念二、条件路由实现三、基于消息头的路由四、动态路由与路由表五、消息过滤

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu

基于SpringBoot实现文件秒传功能

《基于SpringBoot实现文件秒传功能》在开发Web应用时,文件上传是一个常见需求,然而,当用户需要上传大文件或相同文件多次时,会造成带宽浪费和服务器存储冗余,此时可以使用文件秒传技术通过识别重复... 目录前言文件秒传原理代码实现1. 创建项目基础结构2. 创建上传存储代码3. 创建Result类4.

Python+PyQt5实现多屏幕协同播放功能

《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧... 目录一、项目概述:突破传统播放限制二、核心技术解析2.1 多屏管理机制2.2 播放引擎设计2.3 专