ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3391
Скачиваний: 0
Section 13.5 |
Chapter 13 · Packages and Imports |
290 |
This technique is quite useful in large projects that span several packages. It allows you to define things that are visible in several sub-packages of your project but that remain hidden from clients external to your project. The same technique is not possible in Java. There, once a definition escapes its immediate package boundary, it is visible to the world at large.
Of course, the qualifier of a private may also be the directly enclosing package. An example is the access modifier of guide in object Vehicle in Listing 13.12. Such an access modifier is equivalent to Java’s packageprivate access.
Table 13.1 · Effects of private qualifiers on LegOfJourney.distance
no access modifier |
public access |
private[bobsrockets] |
access within outer package |
private[navigation] |
same as package visibility in Java |
private[Navigator] |
same as private in Java |
private[LegOfJourney] |
same as private in Scala |
private[this] |
access only from same object |
All qualifiers can also be applied to protected, with the same meaning as private. That is, a modifier protected[X] in a class C allows access to the labeled definition in all subclasses of C and also within the enclosing package, class, or object X. For instance, the useStarChart method in Listing 13.12 is accessible in all subclasses of Navigator and also in all code contained in the enclosing package navigation. It thus corresponds exactly to the meaning of protected in Java.
The qualifiers of private can also refer to an enclosing class or object. For instance the distance variable in class LegOfJourney in Listing 13.12 is labeled private[Navigator], so it is visible from everywhere in class Navigator. This gives the same access capabilities as for private members of inner classes in Java. A private[C] where C is the outermost enclosing class is the same as just private in Java.
Finally, Scala also has an access modifier that is even more restrictive than private. A definition labeled private[this] is accessible only from within the same object that contains the definition. Such a definition is called object-private. For instance, the definition of speed in class Navigator in Listing 13.12 is object-private. This means that any access must not only be within class Navigator, but it must also be made from the very same in-
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 13.5 |
Chapter 13 · Packages and Imports |
291 |
stance of Navigator. Thus the accesses “speed” and “this.speed” would be legal from within Navigator. The following access, though, would not be allowed, even if it appeared inside class Navigator:
val other = new Navigator
other.speed // this line would not compile
Marking a member private[this] is a guarantee that it will not be seen from other objects of the same class. This can be useful for documentation. It also sometimes lets you write more general variance annotations (see Section 19.7 for details).
To summarize, Table 13.1 on page 290 lists the effects of private qualifiers. Each line shows a qualified private modifier and what it would mean if such a modifier were attached to the distance variable declared in class LegOfJourney in Listing 13.12.
Visibility and companion objects
In Java, static members and instance members belong to the same class, so access modifiers apply uniformly to them. You have already seen that in Scala there are no static members; instead you can have a companion object that contains members that exist only once. For instance, in Listing 13.13 object Rocket is a companion of class Rocket.
Scala’s access rules privilege companion objects and classes when it comes to private or protected accesses. A class shares all its access rights with its companion object and vice versa. In particular, an object can access all private members of its companion class, just as a class can access all private members of its companion object.
For instance, the Rocket class above can access method fuel, which is declared private in object Rocket. Analogously, the Rocket object can access the private method canGoHomeAgain in class Rocket.
One exception where the similarity between Scala and Java breaks down concerns protected static members. A protected static member of a Java class C can be accessed in all subclasses of C. By contrast, a protected member in a companion object makes no sense, as singleton objects don’t have any subclasses.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 13.6 |
Chapter 13 · Packages and Imports |
292 |
class Rocket { import Rocket.fuel
private def canGoHomeAgain = fuel > 20
}
object Rocket {
private def fuel = 10
def chooseStrategy(rocket: Rocket) { if (rocket.canGoHomeAgain)
goHome() else
pickAStar()
}
def goHome() {} def pickAStar() {}
}
Listing 13.13: Accessing private members of companion classes and objects.
13.6 Package objects
So far, the only code you have seen added to packages are classes, traits, and standalone objects. These are by far the most common definitions that are placed at the top level of a package, but Scala doesn’t limit you to just those. Any kind of definition that you can put inside a class, you can also put at the top level of a package. If you have some helper method you’d like to be in scope for an entire package, go ahead and put it right at the top level of the package.
To do so, put the definitions in a package object. Each package is allowed to have one package object. Any definitions placed in a package object are considered members of the package itself.
An example is shown in Listing 13.14. File package.scala holds a package object for package bobsdelights. Syntactically, a package object looks much like one of the curly-braces packagings shown earlier in the chapter. The only difference is that it includes the object keyword. It’s a package object, not a package. The contents of the curly braces can include any definitions you like. In this case, the package object includes the showFruit utility method from Listing 13.8.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 13.6 |
Chapter 13 · Packages and Imports |
293 |
Given that definition, any other code in any package can import the method just like it would import a class. For example, Listing 13.14 also shows the standalone object PrintMenu, which is located in a different package. PrintMenu can import the utility method showFruit in the same way it would import the class Fruit.
//In file bobsdelights/package.scala package object bobsdelights {
def showFruit(fruit: Fruit) { import fruit._
println(name +"s are "+ color)
}
}
//In file PrintMenu.scala
package printmenu
import bobsdelights.Fruits import bobsdelights.showFruit
object PrintMenu {
def main(args: Array[String]) { for (fruit <- Fruits.menu) {
showFruit(fruit)
}
}
}
Listing 13.14 · A package object.
Looking ahead, there are other uses of package objects for kinds of definitions you haven’t seen yet. Package objects are frequently used to hold package-wide type aliases (Chapter 20) and implicit conversions (Chapter 21). The top-level scala package has a package object, and its definitions are available to all Scala code.
Package objects are compiled to class files named package.class that are the located in the directory of the package that they augment. It’s useful to keep the same convention for source files. So you would typically put the source file of the package object bobsdelights of Listing 13.14 into a file named package.scala that resides in the bobsdelights directory.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 13.7 |
Chapter 13 · Packages and Imports |
294 |
13.7 Conclusion
In this chapter, you saw the basic constructs for dividing a program into packages. This gives you a simple and useful kind of modularity, so that you can work with very large bodies of code without different parts of the code trampling on each other. This system is the same in spirit as Java’s packages, but there are some differences where Scala chooses to be more consistent or more general.
Looking ahead, Chapter 29 describes a more flexible module system than division into packages. In addition to letting you separate code into several namespaces, that approach allows modules to be parameterized and to inherit from each other. In the next chapter, we’ll turn our attention to assertions and unit testing.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Chapter 14
Assertions and Unit Testing
Two important ways to check that the behavior of the software you write is as you expect are assertions and unit tests. In this chapter, we’ll show you several options you have in Scala to write and run them.
14.1 Assertions
Assertions in Scala are written as calls of a predefined method assert.1 The expression assert(condition) throws an AssertionError if condition does not hold. There’s also a two-argument version of assert. The expression assert(condition, explanation) tests condition, and, if it does not hold, throws an AssertionError that contains the given explanation. The type of explanation is Any, so you can pass any object as the explanation. The assert method will call toString on it to get a string explanation to place inside the AssertionError.
For example, in the method named “above” of class Element, shown in Listing 10.13 on page 247, you might place an assert after the calls to widen to make sure that the widened elements have equal widths. This is shown in Listing 14.1.
Another way you might choose to do this is to check the widths at the end of the widen method, right before you return the value. You can accomplish this by storing the result in a val, performing an assertion on the result, then mentioning the val last so the result is returned if the assertion succeeds. You
1The assert method is defined in the Predef singleton object, whose members are automatically imported into every Scala source file.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 14.1 |
Chapter 14 · Assertions and Unit Testing |
296 |
def above(that: Element): Element = { val this1 = this widen that.width val that1 = that widen this.width assert(this1.width == that1.width)
elem(this1.contents ++ that1.contents)
}
Listing 14.1 · Using an assertion.
can do this more concisely, however, with a convenience method in Predef named ensuring, as shown in Listing 14.2.
The ensuring method can be used with any result type because of an implicit conversion. Although it looks in this code as if we’re invoking ensuring on widen’s result, which is type Element, we’re actually invoking ensuring on a type to which Element is implicitly converted. The ensuring method takes one argument, a predicate function that takes a result type and returns Boolean. ensuring will pass the result to the predicate. If the predicate returns true, ensuring will return the result. Otherwise, ensuring will throw an AssertionError.
In this example, the predicate is “w <= _.width”. The underscore is a placeholder for the one argument passed to the predicate, the Element result of the widen method. If the width passed as w to widen is less than or equal to the width of the result Element, the predicate will result in true, and ensuring will result in the Element on which it was invoked. Because this is the last expression of the widen method, widen itself will then result in the Element.
private def widen(w: Int): Element = if (w <= width)
this else {
val left = elem(' ', (w - width) / 2, height)
var right = elem(' ', w - width - left.width, height) left beside this beside right
} ensuring (w <= _.width)
Listing 14.2 · Using ensuring to assert a function’s result.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index