ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 418
Скачиваний: 0
Packages and Imports
Topics in This Chapter A1
7.1Packages — page 76
7.2Scope Rules — page 77
7.3Chained Package Clauses — page 79
7.4Top-of-File Notation — page 79
7.5Package Objects — page 80
7.6Package Visibility — page 80
7.7Imports — page 81
7.8Imports Can Be Anywhere — page 82
7.9Renaming and Hiding Members — page 82
7.10Implicit Imports — page 82
Exercises — page 83
Chapter 7
In this chapter, you will learn how packages and import statements work in Scala. Both packages and imports are more regular than in Java; they are also a bit more flexible.
The key points of this chapter are:
•Packages nest just like inner classes.
•Package paths are not absolute.
•A chain x.y.z in a package clause leaves the intermediate packages x and x.y invisible.
•Package statements without braces at the top of the file extend to the entire file.
•A package object can hold functions and variables.
•Import statements can import packages, classes, and objects.
•Import statements can be anywhere.
•Import statements can rename and hide members.
•java.lang, scala, and Predef are always imported.
75
76 |
Chapter 7 |
Packages and Imports |
|
7.1 Packages
Packages in Scala fulfill the same purpose as packages in Java or namespaces in C++: to manage names in a large program. For example, the name Map can occur in the packages scala.collection.immutable and scala.collection.mutable without conflict. To access either name, you can use the fully qualified scala.collection.immutable.Map or scala.collection.mutable.Map. Alternatively, use an import statement to provide a shorter alias—see Section 7.7, “Imports,” on page 81.
To add items to a package, you 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 the definition of an object or a class, a package can be defined in multiple files. The preceding code might be in a file Employee.scala, and a file Manager.scala might contain
package com { package horstmann {
package impatient { class Manager
...
}
}
}
NOTE: There is no enforced relationship between the directory of the source file and the package.You don’t have to put Employee.scala and Manager.scala into a com/horstmann/impatient directory.
Conversely, you can contribute to more than one package in a single file. The file
Employee.scala can contain
7.2 |
|
Scope Rules |
77 |
|
package com { package horstmann {
package impatient { class Employee
...
}
}
}
package org { package bigjava {
class Counter
...
}
}
7.2 Scope Rules
In Scala, the scope rules for packages are more consistent than those in Java. Scala packages nest just like all other scopes. You can access names from the enclosing scope. For example,
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)
}
}
}
}
}
Note the Utils.percentOf qualifier. The Utils class was defined in the parent package. Everything in the parent package is in scope, and it is not necessary to use com.horstmann.Utils.percentOf. (You could, though, if you prefer—after all, com is also in scope.)
78 |
Chapter 7 |
Packages and Imports |
|
There is a fly in the ointment, however. Consider
package com { package horstmann {
package impatient { class Manager {
val subordinates = new collection.mutable.ArrayBuffer[Employee]
...
}
}
}
}
This code takes advantage of the fact that the scala package is always imported. Therefore, the collection package is actually scala.collection.
And now suppose someone introduces the following package, perhaps in a different file:
package com { package horstmann {
package collection {
...
}
}
}
Now the Manager class no longer compiles. It looks for a mutable member inside the com.horstmann.collection package and doesn’t find it. The intent in the Manager class was the collection package in the top-level scala package, not whatever collection subpackage happened to be in some accessible scope.
In Java, this problem can’t occur because package names are always absolute, starting at the root of the package hierarchy. But in Scala, package names are relative, just like inner class names. With inner classes, one doesn’t usually run into problems because all the code is in one file, under control of whoever is in charge of that file. But packages are open-ended. Anyone can contribute to a package at any time.
One solution is to use absolute package names, starting with _root_, for example:
val subordinates = new _root_.scala.collection.mutable.ArrayBuffer[Employee]
Another approach is to use “chained” package clauses, as detailed in the next section.
7.4 |
|
Top-of-File Notation |
79 |
|
NOTE: Most programmers use complete paths for package names, without the _root_ prefix. This is safe as long as everyone avoids names scala, java, com, org, and so on, for nested packages.
7.3 Chained Package Clauses
A package clause can contain a “chain,” or path segment, for example:
package com.horstmann.impatient {
// Members of com and com.horstmann are not visible here package people {
class Person
...
}
}
Such a clause limits the visible members. Now a com.horstmann.collection package would no longer be accessible as collection.
7.4 Top-of-File Notation
Instead of the nested notation that we have used up to now, you can have package clauses at the top of the file, without braces. For example:
package com.horstmann.impatient package people
class Person
...
This is equivalent to
package com.horstmann.impatient { package people {
class Person
...
// Until the end of the file
}
}
This is the preferred notation if all the code in the file belongs to the same package (which is the usual case).
80 |
Chapter 7 |
Packages and Imports |
|
Note that in the example above, everything in the file belongs to the package com.horstmann.impatient.people, but the package com.horstmann.impatient has also been opened up so you can refer to its contents.
7.5 Package Objects
A package can contain classes, objects, and traits, but not the definitions of functions or variables. That’s an unfortunate limitation of the Java virtual machine. It would make more sense to add utility functions or constants to a package than to some Utils object. Package objects address this limitation.
Every package can have one package object. You define it in the parent package, and it has the same name as the child package. For example,
package com.horstmann.impatient
package |
object people { |
val defaultName = "John Q. Public" |
|
} |
|
package |
people { |
class |
Person { |
var |
name = defaultName // A constant from the package |
} |
|
... |
|
} |
|
Note that the defaultName value didn’t need to be qualified because it was in the same package. Elsewhere, it is accessible as com.horstmann.impatient.people.defaultName.
Behind the scenes, the package object gets compiled into a JVM class with static methods and fields, called package.class, inside the package. In our example, that would be a class com.horstmann.impatient.people.package with a static field defaultName. (In the JVM, you can use package as a class name.)
It is a good idea to use the same naming scheme for source files. Put the package object into a file com/horstmann/impatient/people/package.scala. That way, anyone who wants to add functions or variables to a package can find the package object easily.
7.6 Package Visibility
In Java, a class member that isn’t declared as public, private, or protected is visible in the package containing the class. In Scala, you can achieve the same effect with qualifiers. The following method is visible in its own package:
7.7 |
|
Imports |
81 |
|
package com.horstmann.impatient.people
class Person {
private[people] def description = "A person with name " + name
...
}
You can extend the visibility to an enclosing package:
private[impatient] def description = "A person with name " + name
7.7 Imports
Imports let you use short names instead of long ones. With the clause
import java.awt.Color
you can write Color in your code instead of java.awt.Color.
That is the sole purpose of imports. If you don’t mind long names, you’ll never need them.
You can import all members of a package as
import java.awt._
This is the same as the * wildcard in Java. (In Scala, * is a valid character for an identifier. You could define a package com.horstmann.*.people, but please don’t.)
You can also import all members of a class or object.
import java.awt.Color._ val c1 = RED // Color.RED
val c2 = decode("#ff0000") // Color.decode
This is like import static in Java. Java programmers seem to live in fear of this variant, but in Scala it is commonly used.
Once you import a package, you can access its subpackages with shorter names. For example:
import java.awt._
def handler(evt: event.ActionEvent) { // java.awt.event.ActionEvent
...
}
The event package is a member of java.awt, and the import brings it into scope.
82 |
Chapter 7 |
Packages and Imports |
|
7.8 Imports Can Be Anywhere
In Scala, an import statement can be anywhere, not just at the top of a file. The scope of the import statement extends until the end of the enclosing block. For example,
class Manager {
import scala.collection.mutable._
val subordinates = new ArrayBuffer[Employee]
...
}
This is a very useful feature, particularly with wildcard imports. It is always a bit worrisome to import lots of names from different sources. In fact, some Java programmers dislike wildcard imports so much that they never use them, but let their IDE generate long lists of imported classes.
By putting the imports where they are needed, you can greatly reduce the potential for conflicts.
7.9 Renaming and Hiding Members
If you want to import a few members from a package, use a selector like this:
import java.awt.{Color, Font}
The selector syntax lets you rename members:
import java.util.{HashMap => JavaHashMap} import scala.collection.mutable._
Now JavaHashMap is a java.util.HashMap and plain HashMap is a scala.collection. mutable.HashMap.
The selector HashMap => _ hides a member instead of renaming it. This is only useful if you import others:
import java.util.{HashMap => _, _} import scala.collection.mutable._
Now HashMap unambiguously refers to scala.collection.mutable.HashMap since java.util.HashMap is hidden.
7.10 Implicit Imports
Every Scala program implicitly starts with
import java.lang._ import scala._ import Predef._
Exercises 83
As with Java programs, java.lang is always imported. Next, the scala package is imported, but in a special way. Unlike all other imports, this one is allowed to override the preceding import. For example, scala.StringBuilder overrides java.lang.StringBuilder instead of conflicting with it.
Finally, the Predef object is imported. It contains quite a few useful functions. (These could equally well have been placed into the scala package object, but Predef was introduced before Scala had package objects.)
Since the scala package is imported by default, you never need to write package names that start with scala. For example,
collection.mutable.HashMap
is just as good as
scala.collection.mutable.HashMap
Exercises
1. Write an example program to demonstrate that
package com.horstmann.impatient
is not the same as
package com package horstmann package impatient
2.Write a puzzler that baffles your Scala friends, using a package com that isn’t at the top level.
3.Write a package random with functions nextInt(): Int, nextDouble(): Double, and setSeed(seed: Int): Unit. To generate random numbers, use the linear congruential generator
next = previous × a + b mod 2n,
where a = 1664525, b = 1013904223, and n = 32.
4.Why do you think the Scala language designers provided the package object syntax instead of simply letting you add functions and variables to a package?
5.What is the meaning of private[com] def giveRaise(rate: Double)? Is it useful?
6.Write a program that copies all elements from a Java hash map into a Scala hash map. Use imports to rename both classes.
7.In the preceding exercise, move all imports into the innermost scope possible.