ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3486
Скачиваний: 0
Section 11.1 |
Chapter 11 · Scala’s Hierarchy |
253 |
scala> 42.toString
res1: java.lang.String = 42
scala> 42.hashCode res2: Int = 42
scala> 42 equals 42 res3: Boolean = true
Note that the value class space is flat; all value classes are subtypes of scala.AnyVal, but they do not subclass each other. Instead there are implicit conversions between different value class types. For example, an instance of class scala.Int is automatically widened (by an implicit conversion) to an instance of class scala.Long when required.
As mentioned in Section 5.9, implicit conversions are also used to add more functionality to value types. For instance, the type Int supports all of the operations below:
scala> 42 max 43 res4: Int = 43
scala> 42 min 43 res5: Int = 42
scala> 1 until 5
res6: Range = Range(1, 2, 3, 4)
scala> 1 to 5
res7: Range.Inclusive = Range(1, 2, 3, 4, 5)
scala> 3.abs res8: Int = 3
scala> (-3).abs res9: Int = 3
Here’s how this works: The methods min, max, until, to, and abs are all defined in a class scala.runtime.RichInt, and there is an implicit conversion from class Int to RichInt. The conversion is applied whenever a method is invoked on an Int that is undefined in Int but defined in RichInt. Similar “booster classes” and implicit conversions exist for the other value classes. Implicit conversions will be discussed in detail in Chapter 21.
The other subclass of the root class Any is class AnyRef. This is the base class of all reference classes in Scala. As mentioned previously, on the
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.2 |
Chapter 11 · Scala’s Hierarchy |
254 |
Java platform AnyRef is in fact just an alias for class java.lang.Object. So classes written in Java as well as classes written in Scala all inherit from AnyRef.2 One way to think of java.lang.Object, therefore, is as the way AnyRef is implemented on the Java platform. Thus, although you can use Object and AnyRef interchangeably in Scala programs on the Java platform, the recommended style is to use AnyRef everywhere.
Scala classes are different from Java classes in that they also inherit from a special marker trait called ScalaObject.
11.2 How primitives are implemented
How is all this implemented? In fact, Scala stores integers in the same way as Java: as 32-bit words. This is important for efficiency on the JVM and also for interoperability with Java libraries. Standard operations like addition or multiplication are implemented as primitive operations. However, Scala uses the “backup” class java.lang.Integer whenever an integer needs to be seen as a (Java) object. This happens for instance when invoking the toString method on an integer number or when assigning an integer to a variable of type Any. Integers of type Int are converted transparently to “boxed integers” of type java.lang.Integer whenever necessary.
All this sounds a lot like auto-boxing in Java 5 and it is indeed quite similar. There’s one crucial difference, though, in that boxing in Scala is much less visible than boxing in Java. Try the following in Java:
// This is Java
boolean isEqual(int x, int y) { return x == y;
}
System.out.println(isEqual(421, 421));
You will surely get true. Now, change the argument types of isEqual to java.lang.Integer (or Object, the result will be the same):
2The reason the AnyRef alias exists, instead of just using the name java.lang.Object, is because Scala was designed to work on both the Java and .NET platforms. On .NET,
AnyRef is an alias for System.Object.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.2 |
Chapter 11 · Scala’s Hierarchy |
255 |
// This is Java
boolean isEqual(Integer x, Integer y) { return x == y;
}
System.out.println(isEqual(421, 421));
You will find that you get false! What happens is that the number 421 gets boxed twice, so that the arguments for x and y are two different objects. Because == means reference equality on reference types, and Integer is a reference type, the result is false. This is one aspect where it shows that Java is not a pure object-oriented language. There is a difference between primitive types and reference types that can be clearly observed.
Now try the same experiment in Scala:
scala> def isEqual(x: Int, y: Int) = x == y isEqual: (Int,Int)Boolean
scala> isEqual(421, 421) res10: Boolean = true
scala> def isEqual(x: Any, y: Any) = x == y isEqual: (Any,Any)Boolean
scala> isEqual(421, 421) res11: Boolean = true
In fact, the equality operation == in Scala is designed to be transparent with respect to the type’s representation. For value types, it is the natural (numeric or boolean) equality. For reference types other than Java’s boxed numeric types, == is treated as an alias of the equals method inherited from Object. That method is originally defined as reference equality, but is overridden by many subclasses to implement their natural notion of equality. This also means that in Scala you never fall into Java’s well-known trap concerning string comparisons. In Scala, string comparison works as it should:
scala> val x = "abcd".substring(2) x: java.lang.String = cd
scala> val y = "abcd".substring(2) y: java.lang.String = cd
scala> x == y
res12: Boolean = true
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.3 |
Chapter 11 · Scala’s Hierarchy |
256 |
In Java, the result of comparing x with y would be false. The programmer should have used equals in this case, but it is easy to forget.
However, there are situations where you need reference equality instead of user-defined equality. For example, in some situations where efficiency is paramount, you would like to hash cons with some classes and compare their instances with reference equality.3 For these cases, class AnyRef defines an additional eq method, which cannot be overridden and is implemented as reference equality (i.e., it behaves like == in Java for reference types). There’s also the negation of eq, which is called ne. For example:
scala> val x = new String("abc") x: java.lang.String = abc
scala> val y = new String("abc") y: java.lang.String = abc
scala> x == y
res13: Boolean = true
scala> x eq y
res14: Boolean = false
scala> x ne y
res15: Boolean = true
Equality in Scala is discussed further in Chapter 30.
11.3 Bottom types
At the bottom of the type hierarchy in Figure 11.1 you see the two classes scala.Null and scala.Nothing. These are special types that handle some “corner cases” of Scala’s object-oriented type system in a uniform way.
Class Null is the type of the null reference; it is a subclass of every reference class (i.e., every class that itself inherits from AnyRef). Null is not compatible with value types. You cannot, for example, assign a null value to an integer variable:
3You hash cons instances of a class by caching all instances you have created in a weak collection. Then, any time you want a new instance of the class, you first check the cache. If the cache already has an element equal to the one you are about to create, you can reuse the existing instance. As a result of this arrangement, any two instances that are equal with equals() are also equal with reference equality.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 11.4 |
Chapter 11 · Scala’s Hierarchy |
257 |
scala> val i: Int = null <console>:4: error: type mismatch;
found : Null(null) required: Int
Type Nothing is at the very bottom of Scala’s class hierarchy; it is a subtype of every other type. However, there exist no values of this type whatsoever. Why does it make sense to have a type without values? As discussed in Section 7.4, one use of Nothing is that it signals abnormal termination. For instance there’s the error method in the Predef object of Scala’s standard library, which is defined like this:
def error(message: String): Nothing = throw new RuntimeException(message)
The return type of error is Nothing, which tells users that the method will not return normally (it throws an exception instead). Because Nothing is a subtype of every other type, you can use methods like error in very flexible ways. For instance:
def divide(x: Int, y: Int): Int = if (y != 0) x / y
else error("can't divide by zero")
The “then” branch of the conditional, x / y, has type Int, whereas the else branch, the call to error, has type Nothing. Because Nothing is a subtype of Int, the type of the whole conditional is Int, as required.
11.4Conclusion
In this chapter we showed you the classes at the top and bottom of Scala’s class hierarchy. Now that you’ve gotten a good foundation on class inheritance in Scala, you’re ready to understand mixin composition. In the next chapter, you’ll learn about traits.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Chapter 12
Traits
Traits are a fundamental unit of code reuse in Scala. A trait encapsulates method and field definitions, which can then be reused by mixing them into classes. Unlike class inheritance, in which each class must inherit from just one superclass, a class can mix in any number of traits. This chapter shows you how traits work and shows two of the most common ways they are useful: widening thin interfaces to rich ones, and defining stackable modifications. It also shows how to use the Ordered trait and compares traits to the multiple inheritance of other languages.
12.1 How traits work
A trait definition looks just like a class definition except that it uses the keyword trait. An example is shown in Listing 12.1:
trait Philosophical { def philosophize() {
println("I consume memory, therefore I am!")
}
}
Listing 12.1 · The definition of trait Philosophical.
This trait is named Philosophical. It does not declare a superclass, so like a class, it has the default superclass of AnyRef. It defines one method, named philosophize, which is concrete. It’s a simple trait, just enough to show how traits work.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 12.1 |
Chapter 12 · Traits |
259 |
Once a trait is defined, it can be mixed in to a class using either the extends or with keywords. Scala programmers “mix in” traits rather than inherit from them, because mixing in a trait has important differences from the multiple inheritance found in many other languages. This issue is discussed in Section 12.6. For example, Listing 12.2 shows a class that mixes in the Philosophical trait using extends:
class Frog extends Philosophical { override def toString = "green"
}
Listing 12.2 · Mixing in a trait using extends.
You can use the extends keyword to mix in a trait; in that case you implicitly inherit the trait’s superclass. For instance, in Listing 12.2, class Frog subclasses AnyRef (the superclass of Philosophical) and mixes in Philosophical. Methods inherited from a trait can be used just like methods inherited from a superclass. Here’s an example:
scala> val frog = new Frog frog: Frog = green
scala> frog.philosophize()
I consume memory, therefore I am!
A trait also defines a type. Here’s an example in which Philosophical is used as a type:
scala> val phil: Philosophical = frog phil: Philosophical = green
scala> phil.philosophize()
I consume memory, therefore I am!
The type of phil is Philosophical, a trait. Thus, variable phil could have been initialized with any object whose class mixes in Philosophical.
If you wish to mix a trait into a class that explicitly extends a superclass, you use extends to indicate the superclass and with to mix in the trait. Listing 12.3 shows an example. If you want to mix in multiple traits, you add more with clauses. For example, given a trait HasLegs, you could mix both Philosophical and HasLegs into Frog as shown in Listing 12.4.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index