07 Apr 2019 | 4mins
This is my small contribution to Gympass’ Scala study group. Scala for the impatient, chapter 7: Packages and Imports.
Packages are used to reference specific classes, objects, traits, etc. Imports are used to access packages from other files and use definitions by its simple name.
In this chapter, the author presents how packages and import statements work in Scala. Key Points are:
To add items to a package, we can include them in package statements, such as:
package com {
package horstmann {
package impatient {
class Employee
...
}
}
}
Then the class name Employee can be accessed anywhere as com.horstmann.impatient. Employee.
Unlike Classes or objects definitions, packages can be defined in multiple files.(Ex: Manager class on package com.horstmann.impatient.manager)
OBS: there is no enforced relationship between the source file and the package. employee.scala doesn’t have to be on com/horstmann/impatient directory.
we can contribute to more than one package in a single file.
Everything defined inside the parent package can be used without the full package reference. Ex:
package com {
package horstmann {
object Utils {
def percentOf(value: Double, rate: Double) = value * rate / 100
...
}
package impatient {
class Employee {
...
def giveRaise(rate: scala.Double) {
salary += Utils.percentOf(salary, rate)
}
}
}
}
}
We can use Utils.percentOf instead of com com.horstmann.Utils.percentOf because it is used inside the parent scope.
If a package is defined with a conflicting name (Ex: collection vs scala.collection), one solution is to use absolute package names. Another approach is to use chained package clauses.
package com {
package horstmann {
package collection {
...
}
}
}
Instead of using the above example, we can use the following way, so com.horstmann.collection package would no longer be accessible as ‘collection’:
package com.horstmann.collection {
// Members of com and com.horstmann are not visible here
}
}
Instead of the nested notations presented until now, we can use a cleaned notation without braces:
package com.horstmann.impatient
package people
class Person
...
The package definition will extend to the entire file and is equivalent to:
package com.horstmann.impatient {
package people {
class Person
...
// Until the end of the file
}
}
Packages can contain classes objects and traits, but not methods or variables. To address this issue we can use package objects.
Every package can have its package object(only 1 per package), and it should be defined inside the parent.
package com.horstmann.impatient
package object people {
val defaultName = "John Q. Public"
}
package people {
class Person {
var name = defaultName // A constant from the package
}
...
}
We can use qualifiers to explicit the visibility of a method.
package com.horstmann.impatient.people
class Person {
private[people] def description = "A person with name " + name
...
}
We can extend the visibility to an enclosing package:
private[impatient] def description = "A person with name " + name
If we import packages, we can use shorter names instead of longer ones by bringing them into scope. (Ex: Color instead of java.awt.Color after importing it)
To import all members of a package use the wildcard ._ (Ex: import java.awt._)
Imports can be anywhere inside a Scala file, not only on top. The scope extends until the end of the enclosing block.
It is helpful to reduce potential namespace conflicts. It also makes our code more clear and organized. Ex:
package foo
// available to all classes defined below
import java.io.File
import java.io.PrintWriter
class Foo {
// only available inside this class
import javax.swing.JFrame
// ...
}
class Bar {
// only available inside this class
import scala.util.Random
// ...
}
However, import statements are read in the order of the file, so the code below won’t compile:
// this doesn't work because the import is after the attempted reference
class ImportTests {
def printRandom {
val r = new Random // fails
}
}
import scala.util.Random
To import a few members from a package, we can use a selector:
import java.awt.{Color, Font}
On selectors, we can rename members using =>
import java.util.{HashMap => JavaHashMap}
Renaming is important when you use classes with the same name from different packages, or to abbreviate a name. (Ex: javal.util.Date & java.sql.Date)
The alternative to renaming is to use fully qualified names (package.Class) inside the code, but it’s verbose.
Every Scala program starts with:
import java.lang._ // String, Object, Exception
import scala._ // Int, Nothing, Function
import Predef._ // println, ???
OBS: some scala package members override the java correspondents. (Ex: StringBuilder)