首页 文章

Scala中Anonymous,Singleton和Companion对象之间的差异

提问于
浏览
5

我在Google中搜索并了解深入了解Scala中Anonymous,Singleton和Companion Object之间的差异

我发现在scala中,

  • 没有引用名称的对象称为匿名对象 . 当您不想进一步重用它时,最好创建匿名对象 .

  • Singleton对象是一个使用object关键字而不是类声明的对象 . 调用在singleton对象中声明的方法并不需要任何对象,也没有静态概念 . 因此scala创建了一个单例对象,为程序执行提供入口点 .

  • 在scala中,当你有一个与singleton对象同名的类时,它被称为伴随类,而singleton对象被称为companion对象 . 伴随类及其伴随对象都必须在同一源文件中定义 .

那么,如果我们不想重用,为什么Anonymous对象是好的呢? Singleton很容易理解,但Companion Object的真正目的是什么?我的意思是编写伴侣类和伴随对象的规则背后的故事都必须在同一个源文件中定义? Companion Object的唯一原因是我们有一个与singleton对象同名的类吗?

我想Scala中的这些功能应该有一些重要的原因 . 有什么解释或是否有资源可以从中了解有关Scala对象的更多信息?

2 回答

  • 6

    这是一个很长的答案,但我希望它澄清了一些潜在的使用场景 .

    那么,如果我们不想重用,为什么匿名对象是好的?

    我认为,与其他两个不同,“匿名对象”这个术语在Scala世界中没有明确定义 . 我可以想到一些可能被称为的事情:

    • 您未分配给任何命名变量或字段的某些对象 . 在某些情况下会发生这种情况 . 例如,在某些集合中考虑 foldLeft . 您希望在那里传递初始值,但通常不需要给它任何名称,因为这是一次性对象 . 另一种情况是这样的对象包含你想要使用的一些逻辑(一种strategy pattern) . 考虑以下来自标准 ParIterableLike 的文章
    def count(p: T => Boolean): Int = {
        tasksupport.executeAndWaitResult(new Count(p, splitter))
      }
    

    此特定实现使用命名方法 tasksupport ,因为它希望它可以自定义 . 但如果不是这样,它可能就像是

    def count(p: T => Boolean): Int = {
        new ExecutionContextTaskSupport.executeAndWaitResult(new Count(p, splitter))
      }
    

    new ExecutionContextTaskSupport 将是一个匿名对象 .

    • 应该被称为“匿名 type 对象”的东西 .

    • 如果你为一些type-classes实施证据,这种情况经常发生 . 从Play-Json开始考虑这个例子

    case class Resident(name: String, age: Int, role: Option[String])
    
    import play.api.libs.json._ 
    implicit val residentReads = Json.reads[Resident]
    
    // In a request, a JsValue is likely to come from `request.body.asJson`
    // or just `request.body` if using the `Action(parse.json)` body parser
    val jsonString: JsValue = Json.parse(
      """{
        "name" : "Fiver",
        "age" : 4
      }"""
    )
    
    val residentFromJson: JsResult[Resident] = Json.fromJson[Resident](jsonString)
    

    这里 residentReads 的对象和类将由 Json.reads 后面的宏生成,只要它实现了 Reads 特性,你就不关心它的类型 .

    • 或者,如果您的模板方法取决于返回的某些策略 . 即所有调用者需要知道的类型是它匹配某些指定的接口 Contract (即扩展某些 trait )的情况 . 从 ExecutionContextImpl 开始考虑这件作品
    def fromExecutorService(es: ExecutorService, reporter: Throwable => Unit = ExecutionContext.defaultReporter):
        ExecutionContextImpl with ExecutionContextExecutorService = {
        new ExecutionContextImpl(Option(es).getOrElse(createDefaultExecutorService(reporter)), reporter)
          with ExecutionContextExecutorService {
            final def asExecutorService: ExecutorService = executor.asInstanceOf[ExecutorService]
            override def execute(command: Runnable) = executor.execute(command)
            override def shutdown() { asExecutorService.shutdown() }
            override def shutdownNow() = asExecutorService.shutdownNow()
            override def isShutdown = asExecutorService.isShutdown
            override def isTerminated = asExecutorService.isTerminated
            override def awaitTermination(l: Long, timeUnit: TimeUnit) = asExecutorService.awaitTermination(l, timeUnit)
            override def submit[T](callable: Callable[T]) = asExecutorService.submit(callable)
            override def submit[T](runnable: Runnable, t: T) = asExecutorService.submit(runnable, t)
            override def submit(runnable: Runnable) = asExecutorService.submit(runnable)
            override def invokeAll[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAll(callables)
            override def invokeAll[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAll(callables, l, timeUnit)
            override def invokeAny[T](callables: Collection[_ <: Callable[T]]) = asExecutorService.invokeAny(callables)
            override def invokeAny[T](callables: Collection[_ <: Callable[T]], l: Long, timeUnit: TimeUnit) = asExecutorService.invokeAny(callables, l, timeUnit)
          }
        }
    

    只要符合 ExecutionContextExecutorService 的 Contract ,调用者就不会关心特定类型,特别是我们并不关心它是基于 ExecutionContextImpl 而不是任何其他实现 .

    实际情况#1和#2(即“匿名类型的匿名对象”)经常被合并,如果你需要传递一些工作,由于某些原因不适合简单的接口(因为它需要更多)比一个生命周期方法或历史兼容性原因) . 最好的例子是 java.lang.Runnable . 这是 ExecutionContextImpl 的另一个例子:

    // As per ThreadFactory contract newThread should return `null` if cannot create new thread.
    def newThread(runnable: Runnable): Thread =
      if (reserveThread())
        wire(new Thread(new Runnable {
          // We have to decrement the current thread count when the thread exits
          override def run() = try runnable.run() finally deregisterThread()
        })) else null
    

    Thread 类需要 Runnable 作为要执行的工作,我们想要包装 runnable 作为参数与另一个将在最后调用 deregisterThread 的参数,但我们不关心对象的名称或其实际类型 .

    Companion Object的真正目的是什么?

    我可以想到使用Companion Objects的几个主要原因 .

    • Java世界中的某个东西是 static 方法或 static 字段 . 例如,假设您为自己编写了自定义精度算术 BigInt . 你会在哪里放置众所周知的常量,如 zero ,以便从外面访问它们?伴侣对象就是答案 . 这种伴随对象的另一个非常典型的用法是提供一些工厂方法的方法(通常通过 apply ),例如你可以写
    List.empty
     List(1, 2, 3)
    

    没有 new 关键字

    • 你有一些类,你想为它提供一些共享的默认实例 . 就创建该类的更多实例而言,没有必要使用单例 . 例如, scala.util.Random 将类和伴随对象定义为
    object Random extends Random
    

    所以你可以做到

    Random.nextInt
    
    • 可能最值得称道的东西可能被称为"companion object" . 您有一些类的层次结构和一段逻辑应该绑定到层次结构中的每个类,但不是层次结构中类的类型 . 这可能听起来有点复杂,但并不难 . scala.concurrent 包非常采用这个想法 . 考虑例如:
    abstract class GenTraversableFactory[CC[X] <: GenTraversable[X] with GenericTraversableTemplate[X, CC]]
    extends GenericCompanion[CC] {
    
      private[this] val ReusableCBFInstance: GenericCanBuildFrom[Nothing] = new GenericCanBuildFrom[Nothing] {
        override def apply() = newBuilder[Nothing]
      }
      def ReusableCBF: GenericCanBuildFrom[Nothing] = ReusableCBFInstance
    
      // some other stuff  
    
    }
    

    List 伴侣对象实现了几个级别到继承树

    object List extends SeqFactory[List] {
      /** $genericCanBuildFromInfo */
      implicit def canBuildFrom[A]: CanBuildFrom[Coll, A, List[A]] =
        ReusableCBF.asInstanceOf[GenericCanBuildFrom[A]]
    
      def newBuilder[A]: Builder[A, List[A]] = new ListBuffer[A]
    
      // some other stuff 
    
    }
    

    这个 canBuildFrom 实际上被许多集合方法使用,这些集合方法将它们转换为其他集合,例如 ++map . 此外,这个聪明的技巧允许您通过映射到其他集合类型来编写类似的东西:

    val list: List[Int] = Vector(1, 2, 3).map(x => 2 * x)(breakout)
    
  • 1

    那么,如果我们不想重用,为什么匿名对象是好的?

    因为我们不需要关心为其分配名称 . 考虑像 List("a","b","c") 这样的列表的定义 . 在这里,我们使用3个匿名对象创建它 . 我们也可以这样做:

    val a = "a"
    val b = "b"
    val c = "c"
    List(a, b, c)
    

    显然,前一种选择更方便使用 .

    但Companion Object的真正目的是什么?

    基本上它保留了不属于具有相同名称的特定类的实例的静态方法 . 伴随对象通常出现在scala集合中,例如可以轻松创建对象 . 考虑使用 List("a", "b", "c") 的相同示例 . List实际上是一个抽象类,因此它不能简单地以这种方式创建:

    new List("a", "b", "c")
    

    (为什么? - 但这是另一个问题)而且List("a","b","c")无论如何都是较短的形式 . 因此,为了使用该较短的形式,在伴随对象List中引入了方法 override def apply[A](xs: A*): List[A] . 希望现在应该清楚为什么伴侣对象是有用的 .

    这是一个解释伴随对象的好处的资源:http://daily-scala.blogspot.com/2009/09/companion-object.html

相关问题