我正在阅读“不耐烦的 Scala”,在 8.8 中他们说:
[..] you can use the abstract keyword to denote a class that cannot be instantiated [..]
abstract class Person { val id: Int ; var name: String }
后面几行:
You can always customize an abstract field by using an anonymous type:
val fred = new Person {
val id = 1729
var name = "Fred"
}
因此,他们人为地用匿名类型实例化了 Person 类。在现实世界中的哪些情况下,人们会想要这样做?
最佳答案
在稍微思考了我自己的答案后,我得出结论,它所说的基本上只是:
"Anonymous local class instances are poor man's function literals"
提供了
+150
有助于扩大这种狭隘视野的答案的赏金。TL;博士
每当您想将方法的实现视为对象时,您可以实例化一个匿名本地类,该类扩展抽象基类,实现这些方法,然后像基类的任何其他实例一样传递创建的实例。
概览
这篇文章讨论了您可能想要实例化匿名本地类的五种情况。这些示例从非常基础到相当高级。
Runnable
的简单示例Function<X, Y>
免责声明:某些代码是非惯用的,因为它“重新发明了轮子”并且不会隐藏 lambda 或 SingleAbstractMethod 语法中抽象本地类的实例化。
简单介绍示例:Runnable
假设您要编写一个方法,该方法需要一些代码块并多次执行它:
def repeat(numTimes: Int, whatToDo: <someCleverType>): Unit = ???
假设您想从头开始重新发明一切,并且不想使用标准库中的任何按名称参数或接口(interface),那么您用什么来代替
<someCleverType>
?您必须提供看起来有点像这样的基类:abstract class MyRunnable {
def run(): Unit // abstract method
}
现在您可以实现您的
repeat
方法如下:def repeat(numTimes: Int, r: MyRunnable): Unit = {
for (i <- 1 to numTimes) {
r.run()
}
}
现在假设您要使用此方法打印“Hello, world!”十次。怎么做才对
MyRunnable
?你可以定义一个类HelloWorld
扩展 MyRunnable
并实现 run
方法,但它只会污染命名空间,因为您只想使用它一次。相反,您可以直接实例化一个匿名类:val helloWorld = new MyRunnable {
def run(): Unit = println("Hello, world!")
}
然后将其传递给
repeat
:repeat(10, helloWorld)
你甚至可以省略
helloWorld
多变的:repeat(10, new MyRunnable {
def run(): Unit = println("Hello, world!")
})
这是为什么要实例化匿名本地类的规范示例。
更有趣的例子:RealFunction
在前面的例子中,
run
没有参数,它每次都执行相同的代码。现在我想稍微修改示例,以便实现的方法需要一些参数。
我现在不会提供完整的实现,但假设你有一个函数
plot(f: RealFunction): Unit = ???
绘制实函数图
R -> R
,其中 RealFunction
是一个抽象类,定义为abstract class RealFunction {
def apply(x: Double): Double
}
要绘制抛物线,您现在可以执行以下操作:
val xSquare = new RealFunction {
def apply(x: Double): Double = x * x
}
plot(xSquare)
你甚至可以在没有
plot
的情况下单独测试它:例如,p(42)
计算 1764.0
,它是 42
的平方.一般功能
Function[X, Y]
前面的例子推广到任意函数,可以有类型
X
和 Y
作为域和codomain。从历史的角度来看,这可以说是最重要的例子。考虑以下抽象类:abstract class Function[X, Y] {
def apply(x: X): Y // abstract method
}
它类似于
RealFunction
,但不是固定的 Double
,您现在拥有 X
和 Y
.鉴于此接口(interface),您可以重新创建
xSquare
功能如下:val xSquare = new Function[Double, Double] {
def apply(x: Double) = x * x
}
事实上,这个例子是如此重要以至于Scala 的标准库中充满了这样的接口(interface)
FunctionN[X1,...,XN, Y]
对于不同数量的参数 N
.这些接口(interface)有自己的简洁语法,并且在编译器中具有很高的特权。从您的问题的角度来看,这会产生一个“问题”,因为匿名类的实例化通常隐藏在特殊的内置语法糖下。在惯用的 Scala 中,您通常会简单地编写
val xSquare = (x: Double) => x * x
而不是
val xSquare = new Function[Double, Double] {
def apply(x: Double) = x * x
}
这种情况在其他 JVM 语言中是类似的。例如,即使 Java 版本 8 在
java.util.function
中也引入了一堆非常相似的接口(interface)。 .几年前,你会写这样的东西
Function<Integer, Integer> f = new Function<Integer, Integer>() {
public Integer apply(Integer x) {
return x * x;
}
};
在 Java 中,因为还没有 lambda,并且每次你想传递某种回调或
Runnable
或 Function
,您必须实现一个扩展抽象类的匿名类。如今,在较新的 Java 版本中,它被 lambda 和 SingleAbstractMethod 语法隐藏,但原理仍然相同:构造匿名类的实例以实现接口(interface)或扩展抽象类。一个高级的“几乎真实世界”的例子
您不会在今天编写的代码中遇到任何前面的示例,因为匿名本地类的实例化被 lambda 的语法糖隐藏了。我想提供一个现实的例子,其中匿名本地类的实例化实际上是不可避免的。
new AbstractClassName(){ }
-syntax 仍然出现在没有语法糖可用的地方。例如,因为 Scala 没有多态 lambda 的语法,要在像 Scalaz 或 Cats 这样的库中构造自然转换,您通常会编写如下内容:val nat = new (Foo ~> Bar) {
def apply[X](x: Foo[X]): Bar[X] = ???
}
在这里,
Foo
和 Bar
将类似于在不同抽象级别上运行的嵌入式领域特定语言,以及 Foo
更高级,而Bar
更底层。又是一模一样的道理,这样的例子随处可见。这是一个几乎“逼真”的真实世界使用示例:defining an (KVStoreA ~> Id)
-interpreter .希望你能认出new (KVStoreA ~> Id) { def apply(...) ... }
参与其中。不幸的是,这个例子相当先进,但正如我在评论中提到的,在过去的十年里,所有简单和常用的例子都被 lambda 和 Single-Abstract-Method 语法所掩盖。回到你的例子
你引用的代码
abstract class Person(val name: String) {
def id: Int
}
val fred = new Person {
val id = 1729
var name = "Fred"
}
似乎无法编译,因为缺少构造函数参数。
我的猜测是作者想证明你可以覆盖
def
来自 val
s:trait P {
def name: String
}
val inst = new P {
val name = "Fred"
}
虽然很高兴知道这是可能的,但我不认为这是匿名本地类实例化的最重要用例(因为您可以使用普通成员变量并在构造函数中传递值)。鉴于篇幅限制,本书作者可能只是想快速演示语法,而没有深入讨论它的实际用法。
关于Scala:具有匿名类型的抽象类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49598751/