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

标签 scala anonymous-inner-class

我正在阅读“不耐烦的 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]

    前面的例子推广到任意函数,可以有类型 XY作为域和codomain。从历史的角度来看,这可以说是最重要的例子。考虑以下抽象类:
    abstract class Function[X, Y] {
      def apply(x: X): Y // abstract method
    }
    

    它类似于 RealFunction ,但不是固定的 Double ,您现在拥有 XY .

    鉴于此接口(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,并且每次你想传递某种回调或 RunnableFunction ,您必须实现一个扩展抽象类的匿名类。如今,在较新的 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] = ???
    }
    

    在这里,FooBar将类似于在不同抽象级别上运行的嵌入式领域特定语言,以及 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/

    相关文章:

    java - Java : new Stream<Integer>(){ . .. } 中的语法是什么意思?

    Java - 这些是哪些类型的类;哪个是匿名内部类?

    scala - 在 Scala 中,如何以编程方式确定案例类的字段名称?

    scala - Cake Pattern 中如何分离业务类和辅助特征?

    java - 如何在 Scala 中使用 Java 方法

    java - 为什么使用匿名内部类,有哪些替代方案?

    java - 为什么内部类实例变量不能修改外部类实例变量,而内部类局部变量可以

    Java - 如何从匿名内部类访问非最终变量?

    scala - Akka-Http 2.4.9 抛出 java.lang.NoClassDefFoundError : akka/actor/ActorRefFactory exception

    scala - UnFlatten Dataframe 到特定结构