Scala - Package Objects



You use packages to create namespaces in Scala. It prevents naming conflicts. Package objects are used to define functions, variables, and types that are accessible to all members of a package. You can share common definitions across the entire package. So, it improves code organization and maintainability.

What is a Package Object?

Package object is a singleton object named package. It is used to share methods, variables and type aliases across the entire package. Package objects are convenient containers for code and data. You can access these containers across multiple classes within the same package.

Creating a Package Object

You can create a package object in Scala easily. You can define object named package within a file named package.scala. For example -

// package.scala
package com.example

package object utils {
   def exampleMethod(): Unit = {
      println("This is an example method in the package object.")
   }
}

In the code snippet above, we have created a package object named utils under the package com.example. The package object contains a method named exampleMethod().

Using a Package Object

Once a package object is defined, you can access its members directly from any other class in the same package. Following is the example which shows you how to use the exampleMethod() from another class -

// ExampleClass.scala
package com.example.utils

class ExampleClass {
  def printMessage(): Unit = {
    exampleMethod()
  }
}

In this example, the ExampleClass can directly use exampleMethod() defined in the package object utils.

Example with Case Classes and Objects

Create a class, Person. It includes various attributes like, name, age, and designation. Now, the name of the project is company. So, the package and package object for its class would be as follows -

// in file company/person/Person.scala
package company.person

case class Person(name: String, age: Int, desig: String)
object Male extends Person("Ahmad", 27, "Technical lead")
object Female extends Person("Nida", 24, "Intern")

// in file company/person/package.scala
package company
package object person {
  val employees = List(Male, Female)
  def showEmployee(person: Person): Unit = {
    println(s"Name: ${person.name}, Age: ${person.age}, Designation: ${person.desig}")
  }
}

In the code above, the package object person is named after the case class Person. The package object includes a method definition showEmployee(). It prints the details of the employees.

Using Package Objects Across Files

Following is the example which shows you how to use the showEmployee() method from another class within the same package -

// in file company/person/Main.scala
package company.person

object Main {
  def main(args: Array[String]): Unit = {
    employees.foreach(showEmployee)
  }
}

In this example, the Main object directly uses the employees list. The showEmployee method is defined in the package object person.

Usage of Package Objects

You can use Package objects in Scala to hold various definitions, including type aliases, implicit conversions, and utility methods. These support mixed inheritance. So, a single package object can extend more than one class (or trait).

Type Aliases and Implicit Conversions

Following is the example which shows you how to use type aliases and implicit conversions within a package object -

// in file foo/bar/package.scala
package foo

package object bar {
  type StringMap[+T] = Map[String, T]
  implicit def strToInt(s: String): Int = s.toInt
}

In this example, the package object bar defines a type alias StringMap. Implicit conversion from String to Int.

Mixing Traits

You can extend Package objects to traits to include more functionality. For example -

// in file foo/bar/package.scala
package foo

trait Versioning {
  val version: String
}

trait Utility {
  def printVersion(): Unit = println(s"Version: $version")
}

package object bar extends Versioning with Utility {
  override val version: String = "1.0"
}

In this example, the package object bar extends two traits, Versioning and Utility. These provide more methods and properties.

Package Objects for Namespace Management

Package objects are used for managing namespaces and utilities available throughout a package. For example, the library dealing with mathematical operations can define a package object with common constants and helper methods -

// in file math/package.scala
package math

package object constants {
  val Pi = 3.14159
  val E = 2.71828

  def sqrt(x: Double): Double = math.sqrt(x)
}

In this example, the constants package object in the math package defines Pi, E, and a sqrt method. These are then used in the Operations object.

Restrictions

There are some restrictions of package objects. These are -

  • Single Package Object - Only one package object is allowed per package.
  • No Overloaded Methods - Overloading methods in package objects is not supported.
  • File Naming - The source file for a package object should be named package.scala and placed in the corresponding directory of the package.

Package Objects Summary

  • Package objects are used to define methods, variables, and type aliases that are accessible to all members of the package.
  • Each package can have only one package object, which should be named package.scala and placed in the corresponding directory.
  • Package objects support mixed inheritance. So, you can extend these classes and traits.
  • You can use package objects for managing namespaces. These are also used in utilities that are accessible throughout the package.
  • There is no support of overloading methods in package objects. Members cannot share names with top-level classes (or objects) in the same package.
Advertisements