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

j2k*_*2ko 4 android kotlin

我和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()
}
Run Code Online (Sandbox Code Playgroud)

ChangeListener在java文件中定义的位置:

interface ChangeListener {
    void notifyChange(Object element);
}
Run Code Online (Sandbox Code Playgroud)

然后我尝试从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));
Run Code Online (Sandbox Code Playgroud)

结果是:

createNew: a == b -> false
createTheSame: c == d -> true
Run Code Online (Sandbox Code Playgroud)

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

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

nha*_*man 6

这与性能有关.显然创建更少的对象对于性能来说更好,所以这就是Kotlin尝试做的事情.

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

fun create() : () -> Unit {
  return { println("Hello, World!") }
}
Run Code Online (Sandbox Code Playgroud)

对应于以下内容:

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!");
  }
} 
Run Code Online (Sandbox Code Playgroud)

您可以在此处看到始终返回相同的实例.


但是,如果引用lamdba范围之外的变量,则不起作用:单例实例无法访问该变量.

fun create(text: String) : () -> Unit {
  return { println(text) }
}
Run Code Online (Sandbox Code Playgroud)

相反,对于每次调用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);
  }
} 
Run Code Online (Sandbox Code Playgroud)

这就是为什么你ab实例都是一样的,但cd没有.


zsm*_*b13 4

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

至于实际的问题,您已经谈到了为什么会发生这种行为。Lambda(在本例中为 SAM 构造函数)被编译为匿名类(除非它们是内联的)。如果它们捕获任何外部变量,则对于每次调用,都将创建匿名类的新实例。否则,由于它们不必具有任何状态,因此只有一个实例将支持 lambda 的每次调用。我想这是出于性能原因,如果没有别的原因。(本段中的信息归功于《Kotlin in Action》一书。)

如果你想每次返回一个新实例而不捕获任何变量,你可以使用完整的object表示法:

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

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

fun createNotQUiteTheSame(): ChangeListener {
    return ChangeListener { println("time " + System.currentTimeMillis()) }
}
Run Code Online (Sandbox Code Playgroud)

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

我认为提供这种转换是因为比较这些无状态实例是否相等是非常边缘的情况。如果您需要能够在返回的实例之间进行比较,那么您最好使用完整的object表示法。id然后,您甚至可以以示例的形式向每个侦听器添加一些附加状态。