Kotlin从Factory方法返回相同的对象

我在和Kotlin玩,发现有趣的行为。 所以我们可以说我想要某种工厂:

internal interface SomeStupidInterface { companion object FACTORY { fun createNew(): ChangeListener { val time = System.currentTimeMillis() return ChangeListener { element -> Log.e("J2KO", "time " + time) } } fun createTheSame(): ChangeListener { return ChangeListener { element -> Log.e("J2KO", "time " + System.currentTimeMillis()) } } } fun notifyChanged() } 

在java文件中定义的ChangeListener

 interface ChangeListener { void notifyChange(Object element); } 

然后我尝试从Java使用它,如下所示:

 ChangeListener a = SomeStupidInterface.FACTORY.createNew(); ChangeListener b = SomeStupidInterface.FACTORY.createNew(); ChangeListener c = SomeStupidInterface.FACTORY.createTheSame(); ChangeListener d = SomeStupidInterface.FACTORY.createTheSame(); Log.e("J2KO", "createNew a == b -> " + (a == b)); Log.e("J2KO", "createTheSame c == d -> " + (c == d)); 

结果是:

 createNew: a == b -> false createTheSame: c == d -> true 

我可以理解为什么createNew由于关闭而返回新的对象。 但为什么我从createTheSame方法接收相同的实例?

PS我知道上面的代码是不是惯用的:)

首先注意:您的示例代码不能按原样工作:必须使用Java编写接口,才能使用SAM构造函数。

至于实际的问题,你已经谈到了为什么会发生这种行为。 Lambdas(在这种情况下,SAM构造函数)被编译为匿名类(除非它们被内联)。 如果它们捕获任何外部变量,那么对于每个调用,都将创建一个匿名类的新实例。 否则,由于它们不必具有任何状态,只有一个实例会返回lambda的每个调用。 我想这是出于性能原因,如果没有别的。 (请参阅Kotlin行动手册,了解本段的内容。)

如果您想每次都返回一个新实例而不捕获任何变量,则可以使用完整的object符号:

 fun createNotQUiteTheSame(): ChangeListener { return object : ChangeListener { override fun notifyChanged(element: Any?) { println("time " + System.currentTimeMillis()) } } } 

多次调用上述函数将为每个调用返回不同的实例。 有趣的是,IntelliJ将建议将其转换为原始的SAM转换语法:

 fun createNotQUiteTheSame(): ChangeListener { return ChangeListener { println("time " + System.currentTimeMillis()) } } 

正如你已经发现的那样,每次都返回相同的实例。

我想这个转换是提供的,因为比较这些无状态实例是否相等是非常有边缘的情况。 如果您需要在返回的实例之间进行比较,则最好使用完整的object符号。 然后你甚至可以给每个监听器添加一些额外的状态,例如以一个id的形式。

这与性能有关。 创造更少的对象显然是更好的表现,所以这就是Kotlin所要做的。

对于每个lambda,Kotlin生成一个实现适当接口的类。 所以例如下面的Kotlin代码:

 fun create() : () -> Unit { return { println("Hello, World!") } } 

对应于类似的东西:

 Function0 create() { return create$1.INSTANCE; } final class create$1 implements Function0 { static final create$1 INSTANCE = new create$1(); void invoke() { System.out.println("Hello, World!"); } } 

你可以在这里看到总是返回相同的实例。


但是,如果引用lamdba作用域之外的变量,则这将不起作用:单例实例无法访问该变量。

 fun create(text: String) : () -> Unit { return { println(text) } } 

相反,对于create每一次调用,都需要实例化一个类的新实例,该实例可以访问text变量:

 Function0 create(String text) { return new create$1(text); } final class create$1 implements Function0 { final String text; create$1(String text) { this.text = text; } void invoke() { System.out.println(text); } } 

这就是为什么你的ab实例是相同的,但cd不是。

它看起来像你尝试使用Kotlin接口使用SAM转换 。

请注意,SAM转换仅适用于接口,不适用于抽象类,即使这些也只有一个抽象方法。

另请注意,此功能仅适用于Java interop; 由于Kotlin具有适当的函数类型,函数自动转换为Kotlin接口的实现是不必要的,因此不受支持。

为了实现像你想要的接口,你需要使用对象表达式。 也看高阶功能 – 我认为你需要他们为您的解决方案。

 internal interface SomeStupidInterface { interface ChangeListener { fun notifyChanged(element: Any) } companion object FACTORY { fun createNew(): ChangeListener { val time = System.currentTimeMillis() return object : ChangeListener { override fun notifyChanged(element: Any) { println("J2KO" + "time " + time) } } } fun createTheSame(): ChangeListener { return object : ChangeListener { override fun notifyChanged(element: Any) { println("J2KO" + "time " + System.currentTimeMillis()) } } } } fun notifyChanged() } 

另外在IntelliJ IDEA中,我无法编译你的代码。