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

时间:2017-05-31 17:28:42

标签: 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()
}

其中ChangeListener在java文件中定义:

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方法接收相同的实例?

P.S。我知道上面的代码不是惯用语:)

3 个答案:

答案 0 :(得分:6)

这与性能有关。显然创建更少的对象对于性能来说更好,所以这就是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不是。

答案 1 :(得分:4)

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

至于实际问题,你已经谈到了为什么会发生这种情况。 Lambdas(在本例中为SAM构造函数)被编译为匿名类(除非它们被内联)。如果它们捕获任何外部变量,那么对于每次调用,都将创建一个新的匿名类实例。否则,因为它们不必具有任何状态,所以只有一个实例将支持lambda的每次调用。我认为这是出于性能原因,如果没有其他原因。 (请参阅Kotlin in Action本书,了解本段中的信息。)

如果您希望每次都返回一个新实例而不捕获任何变量,则可以使用完整的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的形式向每个侦听器添加一些其他状态。

答案 2 :(得分:-1)

看起来您尝试将SAM conversion与Kotlin界面一起使用。

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

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

为了实现您想要的界面,您需要使用对象表达式。 另请参阅high order functions - 我认为您的解决方案需要它们。

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中,我无法编译您的代码。