Scala:具有匿名类型的抽象类

Ali*_*ina 13 scala anonymous-inner-class

我正在阅读"Scala for the Impatient",他们在8.8中说:

[..]你可以使用abstract关键字来表示一个无法实例化的类[..]

abstract class Person { val id: Int ; var name: String }
Run Code Online (Sandbox Code Playgroud)

几行之后:

您始终可以使用匿名类型自定义抽象字段:

val fred = new Person {

  val id = 1729

  var name = "Fred"

}
Run Code Online (Sandbox Code Playgroud)

因此,他们用匿名类型人工实例化Person类.在现实世界的哪种情况下,人们会想要这样做?

And*_*kin 4

在稍微思考一下我自己的答案之后,我得出的结论是,它所说的基本上只是:

“匿名本地类实例是穷人的函数文字”

+150为有助于扩大这一狭隘视野的答案提供悬赏。


长话短说

每当您想要将方法的实现视为对象时,您可以实例化一个扩展抽象基类的匿名本地类,实现方法,然后像基类的任何其他实例一样传递创建的实例。


概述

这篇文章讨论了您可能想要实例化匿名本地类的五种情况。这些示例从非常基础到相当高级。

  1. 简单的例子Runnable
  2. 绘制二维函数的简单示例
  3. 历史上重要的例子Function<X, Y>
  4. 一个高级的现实示例,其中匿名本地类的实例化似乎是不可避免的
  5. 简要讨论您用来介绍问题的代码。

免责声明:有些代码不惯用,因为它“重新发明轮子”并且不会隐藏 lambda 或 SingleAbstractMethod 语法中抽象本地类的实例化。


简单的介绍性示例:Runnable

假设您要编写一个方法,该方法采用某些代码块并多次执行它:

def repeat(numTimes: Int, whatToDo: <someCleverType>): Unit = ???
Run Code Online (Sandbox Code Playgroud)

假设您想从头开始重新发明一切,并且不想使用标准库中的任何名称参数或接口,您会用什么来代替<someCleverType>?您必须提供看起来有点像这样的基类:

abstract class MyRunnable {
  def run(): Unit  // abstract method
}
Run Code Online (Sandbox Code Playgroud)

repeat现在您可以按如下方式实现您的方法:

def repeat(numTimes: Int, r: MyRunnable): Unit = {
  for (i <- 1 to numTimes) {
    r.run()
  }
}
Run Code Online (Sandbox Code Playgroud)

现在假设您想使用此方法来打印“Hello, world!” 十次。如何创造权利MyRunnable?您可以定义一个 HelloWorld扩展MyRunnable并实现该run方法的类,但它只会污染命名空间,因为您只想使用它一次。相反,您可以直接实例化匿名类:

val helloWorld = new MyRunnable {
  def run(): Unit = println("Hello, world!")
}
Run Code Online (Sandbox Code Playgroud)

然后将其传递给repeat

repeat(10, helloWorld)
Run Code Online (Sandbox Code Playgroud)

您甚至可以省略该helloWorld变量:

repeat(10, new MyRunnable {
  def run(): Unit = println("Hello, world!")
})
Run Code Online (Sandbox Code Playgroud)

这是一个典型的示例,说明了为什么您想要实例化匿名本地类。


稍微有趣的例子:RealFunction

在前面的示例中,run不带任何参数,它每次都执行相同的代码。

现在我想稍微修改一下示例,以便实现的方法带有一些参数。

我现在不会提供完整的实现,但假设你有一个函数

plot(f: RealFunction): Unit = ???
Run Code Online (Sandbox Code Playgroud)

绘制实数函数的图形R -> R,其中RealFunction是一个抽象类,定义为

abstract class RealFunction {
  def apply(x: Double): Double
}
Run Code Online (Sandbox Code Playgroud)

要绘制抛物线,您现在可以执行以下操作:

val xSquare = new RealFunction {
  def apply(x: Double): Double = x * x
}

plot(xSquare)
Run Code Online (Sandbox Code Playgroud)

您甚至可以单独测试它,而无需plot:例如,p(42)计算1764.0,它是 的平方42


一般功能Function[X, Y]

前面的示例概括为任意函数,这些函数可以具有类型XY作为域和余域。从历史的角度来看,这可以说是最重要的例子。考虑以下抽象类:

abstract class Function[X, Y] {
  def apply(x: X): Y // abstract method
}
Run Code Online (Sandbox Code Playgroud)

它与 the 类似,但现在有and ,RealFunction而不是固定的。DoubleXY

xSquare给定此接口,您可以按如下方式重新创建该函数:

val xSquare = new Function[Double, Double] {
  def apply(x: Double) = x * x
}
Run Code Online (Sandbox Code Playgroud)

事实上,这个例子非常重要,以至于 Scala 的标准库中充满了这样的FunctionN[X1,...,XN, Y]用于不同数量参数的接口N

这些接口有自己的简洁语法,并且在编译器中享有很高的特权。从您的问题的角度来看,这会产生一个“问题”,因为匿名类的实例化通常隐藏在特殊的内置语法糖下。在惯用的 Scala 中,你通常会简单地写

val xSquare = (x: Double) => x * x
Run Code Online (Sandbox Code Playgroud)

代替

val xSquare = new Function[Double, Double] {
  def apply(x: Double) = x * x
}
Run Code Online (Sandbox Code Playgroud)

其他 JVM 语言的情况也类似。例如,甚至 Java 版本 8 也在java.util.function. 几年前,你会写类似的东西

Function<Integer, Integer> f = new Function<Integer, Integer>() {
  public Integer apply(Integer x) {
    return x * x;
  }
};
Run Code Online (Sandbox Code Playgroud)

在 Java 中,因为还没有 lambda,每次你想传递某种回调 或Runnable或时Function,你都必须实现一个扩展抽象类的匿名类。如今,在较新的 Java 版本中,它被 lambda 和 SingleAbstractMethod 语法隐藏,但原理仍然相同:构造实现接口或扩展抽象类的匿名类实例。


一个高级的“几乎真实世界”示例

在今天编写的代码中您不会遇到任何前面的示例,因为匿名本地类的实例化被 lambda 的语法糖隐藏了。我想提供一个现实的例子,其中匿名本地类的实例化实际上是不可避免的。

new AbstractClassName(){ }在没有可用语法糖的地方,-语法仍然会出现。例如,由于 Scala 没有多态 lambda 语法,因此要在 Scalaz 或 Cats 等库中构造自然转换,您通常会编写如下内容:

val nat = new (Foo ~> Bar) {
  def apply[X](x: Foo[X]): Bar[X] = ???
}
Run Code Online (Sandbox Code Playgroud)

在这里,FooBar类似于嵌入式领域特定语言,它们在不同的抽象级别上运行,并且Foo是更高级别的,而Bar是更低级别的。又是同样的原理,这样的例子比比皆是。这是现实世界中几乎“逼真”的用法示例:定义(KVStoreA ~> Id)-interpreter。我希望你能认出new (KVStoreA ~> Id) { def apply(...) ... }里面的部分。不幸的是,这个示例相当高级,但正如我在评论中提到的,在过去的十年中,所有简单且常用的示例大部分都被 lambda 和单一抽象方法语法隐藏了。


回到你的例子

您引用的代码

abstract class Person(val name: String) {
  def id: Int
}

val fred = new Person {
  val id = 1729
  var name = "Fred"
}
Run Code Online (Sandbox Code Playgroud)

似乎无法编译,因为缺少构造函数参数。

我的猜测是作者想证明你可以def通过s 覆盖vals:

trait P {
  def name: String
}

val inst = new P {
  val name = "Fred"
}
Run Code Online (Sandbox Code Playgroud)

虽然很高兴知道这是可能的,但我不认为这是匿名本地类实例化最重要的用例(因为您可以使用普通成员变量并在构造函数中传递值)。考虑到篇幅限制,本书的作者可能只是想快速演示语法,而不对其实际用法进行深入讨论。