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

Bha*_*Das 5 scala

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

我发现在scala中,

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

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

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

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

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

Ser*_*gGr 7

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

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

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

  1. 您没有分配给任何命名变量或字段的某些对象.在某些情况下会发生这种情况.例如,考虑foldLeft一些集合.您想在那里传递初始值,但通常不需要给它任何名称,因为这是一次性对象.另一种情况是当这样的对象包含你想要使用的一些逻辑时(一种策略模式).考虑遵循标准的部分ParIterableLike
  def count(p: T => Boolean): Int = {
    tasksupport.executeAndWaitResult(new Count(p, splitter))
  }
Run Code Online (Sandbox Code Playgroud)

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

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

并且new ExecutionContextTaskSupport将是一个匿名的对象.

  1. 应该被称为" 匿名类型对象 "的东西.

    • 如果您为某些类型类实现证据,则会经常发生这种情况.考虑一下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)
Run Code Online (Sandbox Code Playgroud)

这里的对象和类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)
      }
    }
Run Code Online (Sandbox Code Playgroud)

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

实际情况#1和#2(即" 匿名类型的匿名对象 ")经常被组合在一起,如果你需要通过一些工作,由于某些原因不适合简单的Function界面(因为它需要超过一种生命周期方法或出于历史兼容性原因).最好的例子是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
Run Code Online (Sandbox Code Playgroud)

Thread类需要Runnable作为一个作品来执行的,我们希望来包装runnable我们已经有了与另外一个,将调用的参数deregisterThread在最后,但我们不关心对象的名称或其实际类型.

Companion Object的真正目的是什么?

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

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

没有new关键字

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

所以你可以做到

Random.nextInt
Run Code Online (Sandbox Code Playgroud)
  1. 可能最值得的东西可能被称为"伴侣对象".您有一些类的层次结构和一段逻辑应该绑定到层次结构中的每个类,但不是层次结构中类的类型.这可能听起来有点复杂,但并不难.这个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  

}
Run Code Online (Sandbox Code Playgroud)

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 

}
Run Code Online (Sandbox Code Playgroud)

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

val list: List[Int] = Vector(1, 2, 3).map(x => 2 * x)(breakout)
Run Code Online (Sandbox Code Playgroud)