包对象

什么是包对象,与其说是概念,不如说是它们的用法?

我试图找到一个可行的例子,我得到的唯一形式如下:

package object investigations {
val PackageObjectVal = "A package object val"
}


package investigations {


object PackageObjectTest {
def main(args: Array[String]) {
println("Referencing a package object val: " + PackageObjectVal)
}
}
}

我到目前为止的观察结果是:

package object _root_ { ... }

不被允许(这是合理的) ,

package object x.y { ... }

也是不允许的。

似乎包对象必须在直接的父包中声明,如果按照上面的方式编写,则需要使用大括号分隔的包声明表单。

它们是否普遍使用? 如果是,如何使用?

36687 次浏览

Normally you would put your package object in a separate file called package.scala in the package that it corresponds to. You can also use the nested package syntax but that is quite unusual.

The main use case for package objects is when you need definitions in various places inside your package as well as outside the package when you use the API defined by the package. Here is an example:

// file: foo/bar/package.scala


package foo


package object bar {


// package wide constants:
def BarVersionString = "1.0"


// or type aliases
type StringMap[+T] = Map[String,T]


// can be used to emulate a package wide import
// especially useful when wrapping a Java API
type DateTime = org.joda.time.DateTime


type JList[T] = java.util.List[T]


// Define implicits needed to effectively use your API:
implicit def a2b(a: A): B = // ...


}

Now the definitions inside that package object are available inside the whole package foo.bar. Furthermore the definitions get imported when someone outside of that package imports foo.bar._.

This way you can prevent to require the API client to issue additional imports to use your library effectively - e.g. in scala-swing you need to write

import swing._
import Swing._

to have all the goodness like onEDT and implicit conversions from Tuple2 to Dimension.

While Moritz's answer is spot on, one additional thing to note is that package objects are objects. Among other things, this means you can build them up from traits, using mix-in inheritance. Moritz's example could be written as

package object bar extends Versioning
with JodaAliases
with JavaAliases {


// package wide constants:
override val version = "1.0"


// or type aliases
type StringMap[+T] = Map[String,T]


// Define implicits needed to effectively use your API:
implicit def a2b(a: A): B = // ...


}

Here Versioning is an abstract trait, which says that the package object must have a "version" method, while JodaAliases and JavaAliases are concrete traits containing handy type aliases. All of these traits can be reused by many different package objects.

The main use case for package objects is when you need definitions in various places inside your package as well as outside the package when you use the API defined by the package.

Not so with Scala 3, scheduled to be released mid-2020, based on Dotty, as in here:

Toplevel Definitions

All kinds of definitions can be written on the toplevel.
Package objects are no longer needed, will be phased out.

package p


type Labelled[T] = (String, T)
val a: Labelled[Int] = ("count", 1)
def b = a._2
def hello(name: String) = println(i"hello, $name)