ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3492
Скачиваний: 0
Section 5.7 |
Chapter 5 · Basic Types and Operations |
133 |
scala> 1 == 2
res31: Boolean = false
scala> 1 != 2
res32: Boolean = true
scala> 2 == 2
res33: Boolean = true
These operations actually apply to all objects, not just basic types. For example, you can use == to compare lists:
scala> List(1, 2, 3) == List(1, 2, 3) res34: Boolean = true
scala> List(1, 2, 3) == List(4, 5, 6) res35: Boolean = false
Going further, you can compare two objects that have different types:
scala> 1 == 1.0 res36: Boolean = true
scala> List(1, 2, 3) == "hello" res37: Boolean = false
You can even compare against null, or against things that might be null. No exception will be thrown:
scala> List(1, 2, 3) == null res38: Boolean = false
scala> null == List(1, 2, 3) res39: Boolean = false
As you see, == has been carefully crafted so that you get just the equality comparison you want in most cases. This is accomplished with a very simple rule: first check the left side for null, and if it is not null, call the equals method. Since equals is a method, the precise comparison you get depends on the type of the left-hand argument. Since there is an automatic null check, you do not have to do the check yourself.7
7The automatic check does not look at the right-hand side, but any reasonable equals method should return false if its argument is null.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.8 |
Chapter 5 · Basic Types and Operations |
134 |
This kind of comparison will yield true on different objects, so long as their contents are the same and their equals method is written to be based on contents. For example, here is a comparison between two strings that happen to have the same five letters in them:
scala> ("he"+"llo") == "hello" res40: Boolean = true
How Scala’s == differs from Java’s
In Java, you can use == to compare both primitive and reference types. On primitive types, Java’s == compares value equality, as in Scala. On reference types, however, Java’s == compares reference equality, which means the two variables point to the same object on the JVM’s heap.
Scala provides a facility for comparing reference equality, as well, under the name eq. However, eq and its opposite, ne, only apply to objects that directly map to Java objects. The full details about eq and ne are given in Sections 11.1 and 11.2. Also, see Chapter 30 on how to write a good equals method.
5.8Operator precedence and associativity
Operator precedence determines which parts of an expression are evaluated before the other parts. For example, the expression 2 + 2 * 7 evaluates to 16, not 28, because the * operator has a higher precedence than the + operator. Thus the multiplication part of the expression is evaluated before the addition part. You can of course use parentheses in expressions to clarify evaluation order or to override precedence. For example, if you really wanted the result of the expression above to be 28, you could write the expression like this:
(2 + 2) * 7
Given that Scala doesn’t have operators, per se, just a way to use methods in operator notation, you may be wondering how operator precedence works. Scala decides precedence based on the first character of the methods used in operator notation (there’s one exception to this rule, which will be discussed below). If the method name starts with a *, for example, it will
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.8 |
Chapter 5 · Basic Types and Operations |
135 |
have a higher precedence than a method that starts with a +. Thus 2 + 2 * 7 will be evaluated as 2 + (2 * 7), and a +++ b *** c (in which a, b, and c are variables, and +++ and *** are methods) will be evaluated a +++ (b *** c), because the *** method has a higher precedence than the +++ method.
Table 5.3 · Operator precedence
(all other special characters)
* / % + -
:
= ! < >
&
ˆ
|
(all letters)
(all assignment operators)
Table 5.3 shows the precedence given to the first character of a method in decreasing order of precedence, with characters on the same line having the same precedence. The higher a character is in this table, the higher the precedence of methods that start with that character. Here’s an example that illustrates the influence of precedence:
scala> 2 << 2 + 2 res41: Int = 32
The << method starts with the character <, which appears lower in Table 5.3 than the character +, which is the first and only character of the + method. Thus << will have lower precedence than +, and the expression will be evaluated by first invoking the + method, then the << method, as in 2 << (2 + 2). 2 + 2 is 4, by our math, and 2 << 4 yields 32. Here’s another example:
scala> 2 + 2 << 2 res42: Int = 16
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.8 |
Chapter 5 · Basic Types and Operations |
136 |
Since the first characters are the same as in the previous example, the methods will be invoked in the same order. First the + method will be invoked, then the << method. So 2 + 2 will again yield 4, and 4 << 2 is 16.
The one exception to the precedence rule, alluded to above, concerns assignment operators, which end in an equals character. If an operator ends in an equals character (=), and the operator is not one of the comparison operators <=, >=, ==, or !=, then the precedence of the operator is the same as that of simple assignment (=). That is, it is lower than the precedence of any other operator. For instance:
x *= y + 1
means the same as:
x *= (y + 1)
because *= is classified as an assignment operator whose precedence is lower than +, even though the operator’s first character is *, which would suggest a precedence higher than +.
When multiple operators of the same precedence appear side by side in an expression, the associativity of the operators determines the way operators are grouped. The associativity of an operator in Scala is determined by its last character. As mentioned on page 87 of Chapter 3, any method that ends in a ‘:’ character is invoked on its right operand, passing in the left operand. Methods that end in any other character are the other way around. They are invoked on their left operand, passing in the right operand. So a * b yields a.*(b), but a ::: b yields b.:::(a).
No matter what associativity an operator has, however, its operands are always evaluated left to right. So if a is an expression that is not just a simple reference to an immutable value, then a ::: b is more precisely treated as the following block:
{ val x = a; b.:::(x) }
In this block a is still evaluated before b, and then the result of this evaluation is passed as an operand to b’s ::: method.
This associativity rule also plays a role when multiple operators of the same precedence appear side by side. If the methods end in ‘:’, they are grouped right to left; otherwise, they are grouped left to right. For example,
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.9 |
Chapter 5 · Basic Types and Operations |
137 |
a ::: b ::: c is treated as a ::: (b ::: c). But a * b * c, by contrast, is treated as (a * b) * c.
Operator precedence is part of the Scala language. You needn’t be afraid to use it. Nevertheless, it is good style to use parentheses to clarify what operators are operating upon what expressions. Perhaps the only precedence you can truly count on other programmers knowing without looking up is that multiplicative operators, *, /, and %, have a higher precedence than the additive ones + and -. Thus even if a + b << c yields the result you want without parentheses, the extra clarity you get by writing (a + b) << c may reduce the frequency with which your peers utter your name in operator notation, for example, by shouting in disgust, “bills !*&ˆ%~ code!”.8
5.9Rich wrappers
You can invoke many more methods on Scala’s basic types than were described in the previous sections. A few examples are shown in Table 5.4. These methods are available via implicit conversions, a technique that will be described in detail in Chapter 21. All you need to know for now is that for each basic type described in this chapter, there is also a “rich wrapper” that provides several additional methods. To see all the available methods on the basic types, therefore, you should look at the API documentation on the rich wrapper for each basic type. Those classes are listed in Table 5.5.
5.10 Conclusion
The main take-aways from this chapter are that operators in Scala are method calls, and that implicit conversions to rich variants exist for Scala’s basic types that add even more useful methods. In the next chapter, we’ll show you what it means to design objects in a functional style that gives new implementations of some of the operators that you have seen in this chapter.
8By now you should be able to figure out that given this code, the Scala compiler would invoke (bills.!*&ˆ%~(code)).!().
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.10 |
Chapter 5 · Basic Types and Operations |
138 |
Table 5.4 · Some rich operations
Code |
Result |
0 max 5 |
5 |
0 min 5 |
0 |
-2.7 abs |
2.7 |
-2.7 round |
-3L |
1.5 isInfinity |
false |
(1.0 / 0) isInfinity |
true |
4 to 6 |
Range(4, 5, 6) |
"bob" capitalize |
"Bob" |
"robert" drop 2 |
"bert" |
|
|
Table 5.5 · Rich wrapper classes
Basic type Rich wrapper
Byte scala.runtime.RichByte Short scala.runtime.RichShort Int scala.runtime.RichInt Char scala.runtime.RichChar Float scala.runtime.RichFloat Double scala.runtime.RichDouble Boolean scala.runtime.RichBoolean
String scala.collection.immutable.StringOps
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Chapter 6
Functional Objects
With the understanding of Scala basics you gained in previous chapters, you’re ready to see how to design more full-featured classes in Scala. The emphasis in this chapter is on classes that define functional objects, that is, objects that do not have any mutable state. As a running example, we’ll create several variants of a class that models rational numbers as immutable objects. Along the way, we’ll show you more aspects of object-oriented programming in Scala: class parameters and constructors, methods and operators, private members, overriding, checking preconditions, overloading, and self references.
6.1A specification for class Rational
A rational number is a number that can be expressed as a ratio dn , where n and d are integers, except that d cannot be zero. n is called the numerator
and d the denominator. Examples of rational numbers are 12 , 23 , 112239 , and 21 . Compared to floating-point numbers, rational numbers have the advantage
that fractions are represented exactly, without rounding or approximation. The class we’ll design in this chapter must model the behavior of rational
numbers, including allowing them to be added, subtracted, multiplied, and divided. To add two rationals, you must first obtain a common denominator, then add the two numerators. For example, to add 12 + 23 , you multiply both parts of the left operand by 3 and both parts of the right operand by 2, which gives you 36 + 46 . Adding the two numerators yields the result, 76 . To multiply two rational numbers, you can simply multiply their numerators and multiply their denominators. Thus, 12 25 gives 102 , which can be represented
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 6.2 |
Chapter 6 · Functional Objects |
140 |
more compactly in its “normalized” form as 15 . You divide by swapping the numerator and denominator of the right operand and then multiplying. For instance 12 = 35 is the same as 12 53 , or 56 .
One, maybe rather trivial, observation is that in mathematics, rational numbers do not have mutable state. You can add one rational number to another, but the result will be a new rational number. The original numbers will not have “changed.” The immutable Rational class we’ll design in this chapter will have the same property. Each rational number will be represented by one Rational object. When you add two Rational objects, you’ll create a new Rational object to hold the sum.
This chapter will give you a glimpse of some of the ways Scala enables you to write libraries that feel like native language support. For example, at the end of this chapter you’ll be able to do this with class Rational:
scala> val oneHalf = new Rational(1, 2) oneHalf: Rational = 1/2
scala> val twoThirds = new Rational(2, 3) twoThirds: Rational = 2/3
scala> (oneHalf / 7) + (1 - twoThirds) res0: Rational = 17/42
6.2Constructing a Rational
A good place to start designing class Rational is to consider how client programmers will create a new Rational object. Given we’ve decided to make Rational objects immutable, we’ll require that clients provide all data needed by an instance (in this case, a numerator and a denominator) when they construct the instance. Thus, we will start the design with this:
class Rational(n: Int, d: Int)
One of the first things to note about this line of code is that if a class doesn’t have a body, you don’t need to specify empty curly braces (though you could, of course, if you wanted to). The identifiers n and d in the parentheses after the class name, Rational, are called class parameters. The Scala compiler will gather up these two class parameters and create a primary constructor that takes the same two parameters.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index