Scala中的匿名,单例和伴随对象之间的区别

我在谷歌搜索了解并深入了解Scala中匿名,单身和伴侣对象之间的差异

我发现在scala中,

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

  • Singleton对象是通过使用object关键字而不是class来声明的对象。 不需要对象来调用在单例对象中声明的方法,并且没有静态的概念。 所以scala创建了一个单例对象来为我们的程序执行提供入口点。

  • 在scala中,当你有一个与singleton对象具有相同名称的类时,它被称为伴随类,而单例对象称为伴随对象。 伴随类和伴随对象都必须在同一个源文件中定义。

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

    我想在Scala中这些功能应该有一些重要的原因。 什么是解释或有资源来了解更多关于Scala对象?


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

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

    我认为,与其他两个不同,术语“匿名对象”在Scala世界中没有很好的定义。 我可以想到几个可能会这样调用的东西:

  • 一些对象,你不分配给任何指定的变量或字段。 这可能发生在几种情况下。 例如在某些集合上考虑foldLeft 。 你想在那里传递初始值,但你通常不需要给它任何名字,因为这是一个可丢弃的对象。 另一种情况是当这样的对象包含你想要使用的某种逻辑(一种策略模式)时。 考虑从标准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将是一个匿名对象。

  • 应该被称为“匿名类型对象”的东西。

  • 如果您为某些类型类实现证据,则会经常发生这种情况。 考虑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特性,您就不在乎它具有哪种类型。

  • 或者如果你有一个模板方法取决于返回的一些策略。 即这些都是当所有的调用者需要知道该类型是它匹配某个指定的接口契约(即扩展某些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的合同,调用者也不会在意特定类型,特别是我们不关心它是基于ExecutionContextImpl而不是任何其他实现。

    实际上,如果您需要将某个工作由于某些原因不适合简单的Function接口(因为它需要更多的工作)而需要传递给某个地方,实际情况#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 Objects的几个主要原因。

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

    没有new关键字

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

    所以你可以做

    Random.nextInt
    
  • 某种可能最值得称道的东西可能被称为“伴侣对象”。 您有一些层次的类和一些逻辑,应该绑定到层次结构中的每个类,但不是层次结构中类的类型。 这听起来有点复杂,但并不难。 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)
    

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

    因为我们不需要关心给它分配一个名字。 考虑像List("a","b","c")这样的List("a","b","c") 。 在这里我们用3个匿名对象来创建它。 我们可以这样做:

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

    很明显,前一种选择更合适。

    但Companion对象的真正目的是什么?

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

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

    (为什么? - 但那是另一个问题)而且列表(“a”,“b”,“c”)总是较短。 所以为了使用这个简短形式的方法override def apply[A](xs: A*): List[A]被引入了一个伴随对象List。 希望现在应该清楚为什么伴侣对象是有用的。

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

    链接地址: http://www.djcxy.com/p/76535.html

    上一篇: Differences between Anonymous, Singleton & Companion Object in Scala

    下一篇: Access private field in Companion object