ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 02.01.2026
Просмотров: 3495
Скачиваний: 0
Section 5.3 |
Chapter 5 · Basic Types and Operations |
125 |
5.3Operators are methods
Scala provides a rich set of operators for its basic types. As mentioned in previous chapters, these operators are actually just a nice syntax for ordinary method calls. For example, 1 + 2 really means the same thing as (1).+(2). In other words, class Int contains a method named + that takes an Int and returns an Int result. This + method is invoked when you add two Ints:
scala> val |
sum = 1 + 2 |
// Scala invokes (1).+(2) |
sum: Int = |
3 |
|
To prove this to yourself, you can write the expression explicitly as a method invocation:
scala> val sumMore = (1).+(2) sumMore: Int = 3
In fact, Int contains several overloaded + methods that take different parameter types.3 For example, Int has another method, also named +, that takes and returns a Long. If you add a Long to an Int, this alternate + method will be invoked, as in:
scala> val longSum = 1 + 2L |
// Scala invokes (1).+(2L) |
longSum: Long = 3 |
|
The + symbol is an operator—an infix operator to be specific. Operator notation is not limited to methods like + that look like operators in other languages. You can use any method in operator notation. For example, class String has a method, indexOf, that takes one Char parameter. The indexOf method searches the string for the first occurrence of the specified character, and returns its index or -1 if it doesn’t find the character. You can use indexOf as an operator, like this:
scala> val s = "Hello, world!"
s: java.lang.String = Hello, world!
scala> s indexOf 'o' |
// Scala invokes s.indexOf(’o’) |
res0: Int = 4 |
|
3Overloaded methods have the same name but different argument types. More on method overloading in Section 6.11.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.3 |
Chapter 5 · Basic Types and Operations |
126 |
In addition, String offers an overloaded indexOf method that takes two parameters, the character for which to search and an index at which to start. (The other indexOf method, shown previously, starts at index zero, the beginning of the String.) Even though this indexOf method takes two arguments, you can use it in operator notation. But whenever you call a method that takes multiple arguments using operator notation, you have to place those arguments in parentheses. For example, here’s how you use this other indexOf form as an operator (continuing from the previous example):
scala> s indexOf ('o', 5) // Scala invokes s.indexOf(’o’, 5) res1: Int = 8
Any method can be an operator
In Scala operators are not special language syntax: any method can be an operator. What makes a method an operator is how you use it. When you write “s.indexOf('o')”, indexOf is not an operator. But when you write “s indexOf 'o'”, indexOf is an operator, because you’re using it in operator notation.
So far, you’ve seen examples of infix operator notation, which means the method to invoke sits between the object and the parameter or parameters you wish to pass to the method, as in “7 + 2”. Scala also has two other operator notations: prefix and postfix. In prefix notation, you put the method name before the object on which you are invoking the method, for example, the ‘-’ in -7. In postfix notation, you put the method after the object, for example, the “toLong” in “7 toLong”.
In contrast to the infix operator notation—in which operators take two operands, one to the left and the other to the right—prefix and postfix operators are unary: they take just one operand. In prefix notation, the operand is to the right of the operator. Some examples of prefix operators are -2.0, !found, and ~0xFF. As with the infix operators, these prefix operators are a shorthand way of invoking methods. In this case, however, the name of the method has “unary_” prepended to the operator character. For instance, Scala will transform the expression -2.0 into the method invocation “(2.0).unary_-”. You can demonstrate this to yourself by typing the method call both via operator notation and explicitly:
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.3 |
Chapter 5 · Basic Types and Operations |
127 |
scala> -2.0 |
// Scala invokes (2.0).unary_- |
res2: Double = -2.0 |
|
scala> (2.0).unary_- |
|
res3: Double = -2.0 |
|
The only identifiers that can be used as prefix operators are +, -, !, and ~. Thus, if you define a method named unary_!, you could invoke that method on a value or variable of the appropriate type using prefix operator notation, such as !p. But if you define a method named unary_*, you wouldn’t be able to use prefix operator notation, because * isn’t one of the four identifiers that can be used as prefix operators. You could invoke the method normally, as in p.unary_*, but if you attempted to invoke it via *p, Scala will parse it as if you’d written *.p, which is probably not what you had in mind!4
Postfix operators are methods that take no arguments, when they are invoked without a dot or parentheses. In Scala, you can leave off empty parentheses on method calls. The convention is that you include parentheses if the method has side effects, such as println(), but you can leave them off if the method has no side effects, such as toLowerCase invoked on a String:
scala> val s = "Hello, world!"
s: java.lang.String = Hello, world!
scala> s.toLowerCase
res4: java.lang.String = hello, world!
In this latter case of a method that requires no arguments, you can alternatively leave off the dot and use postfix operator notation:
scala> s toLowerCase
res5: java.lang.String = hello, world!
In this case, toLowerCase is used as a postfix operator on the operand s. To see what operators you can use with Scala’s basic types, therefore, all
you really need to do is look at the methods declared in the type’s classes in the Scala API documentation. Given that this is a Scala tutorial, however, we’ll give you a quick tour of most of these methods in the next few sections.
4All is not necessarily lost, however. There is an extremely slight chance your program with the *p might compile as C++.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.4 |
Chapter 5 · Basic Types and Operations |
128 |
Fast track for Java programmers
Many aspects of Scala described in the remainder of this chapter are the same as in Java. If you’re a Java guru in a rush, you can safely skip to Section 5.7 on page 132, which describes how Scala differs from Java in the area of object equality.
5.4Arithmetic operations
You can invoke arithmetic methods via infix operator notation for addition (+), subtraction (-), multiplication (*), division (/), and remainder (%), on any numeric type. Here are some examples:
scala> 1.2 + 2.3 res6: Double = 3.5
scala> 3 - 1 res7: Int = 2
scala> 'b' - 'a' res8: Int = 1
scala> 2L * 3L res9: Long = 6
scala> 11 / 4 res10: Int = 2
scala> 11 % 4 res11: Int = 3
scala> 11.0f / 4.0f res12: Float = 2.75
scala> 11.0 % 4.0 res13: Double = 3.0
When both the left and right operands are integral types (Int, Long, Byte, Short, or Char), the / operator will tell you the whole number portion of the quotient, excluding any remainder. The % operator indicates the remainder of an implied integer division.
The floating-point remainder you get with % is not the one defined by the IEEE 754 standard. The IEEE 754 remainder uses rounding division, not truncating division, in calculating the remainder, so it is quite different from
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.5 |
Chapter 5 · Basic Types and Operations |
129 |
the integer remainder operation. If you really want an IEEE 754 remainder, you can call IEEEremainder on scala.math, as in:
scala> math.IEEEremainder(11.0, 4.0) res14: Double = -1.0
The numeric types also offer unary prefix operators + (method unary_+) and - (method unary_-), which allow you to indicate a literal number is positive or negative, as in -3 or +4.0. If you don’t specify a unary + or -, a literal number is interpreted as positive. Unary + exists solely for symmetry with unary -, but has no effect. The unary - can also be used to negate a variable. Here are some examples:
scala> val neg = 1 + -3 neg: Int = -2
scala> val y = +3 y: Int = 3
scala> -neg res15: Int = 2
5.5Relational and logical operations
You can compare numeric types with relational methods greater than (>), less than (<), greater than or equal to (>=), and less than or equal to (<=), which yield a Boolean result. In addition, you can use the unary ‘!’ operator (the unary_! method) to invert a Boolean value. Here are a few examples:
scala> 1 > 2
res16: Boolean = false
scala> 1 < 2
res17: Boolean = true
scala> 1.0 <= 1.0 res18: Boolean = true
scala> 3.5f >= 3.6f res19: Boolean = false
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.5 |
Chapter 5 · Basic Types and Operations |
130 |
scala> 'a' >= 'A' res20: Boolean = true
scala> val thisIsBoring = !true thisIsBoring: Boolean = false
scala> !thisIsBoring res21: Boolean = true
The logical methods, logical-and (&&) and logical-or (||), take Boolean operands in infix notation and yield a Boolean result. For example:
scala> val toBe = true toBe: Boolean = true
scala> val question = toBe || !toBe question: Boolean = true
scala> val paradox = toBe && !toBe paradox: Boolean = false
The logical-and and logical-or operations are short-circuited as in Java: expressions built from these operators are only evaluated as far as needed to determine the result. In other words, the right-hand side of logical-and and logical-or expressions won’t be evaluated if the left-hand side determines the result. For example, if the left-hand side of a logical-and expression evaluates to false, the result of the expression will definitely be false, so the right-hand side is not evaluated. Likewise, if the left-hand side of a logical-or expression evaluates to true, the result of the expression will definitely be true, so the right-hand side is not evaluated. For example:
scala> def salt() = { println("salt"); false } salt: ()Boolean
scala> def pepper() = { println("pepper"); true } pepper: ()Boolean
scala> pepper() && salt() pepper
salt
res22: Boolean = false
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.6 |
Chapter 5 · Basic Types and Operations |
131 |
scala> salt() && pepper() salt
res23: Boolean = false
In the first expression, pepper and salt are invoked, but in the second, only salt is invoked. Given salt returns false, there’s no need to call pepper.
Note
You may be wondering how short-circuiting can work given operators are just methods. Normally, all arguments are evaluated before entering a method, so how can a method avoid evaluating its second argument? The answer is that all Scala methods have a facility for delaying the evaluation of their arguments, or even declining to evaluate them at all. The facility is called by-name parameters and is discussed in Section 9.5.
5.6Bitwise operations
Scala enables you to perform operations on individual bits of integer types with several bitwise methods. The bitwise methods are: bitwise-and (&), bitwise-or (|), and bitwise-xor (ˆ).5 The unary bitwise complement operator (~, the method unary_~), inverts each bit in its operand. For example:
scala> 1 & 2 res24: Int = 0
scala> 1 | 2 res25: Int = 3
scala> 1 ˆ 3 res26: Int = 2
scala> ~1 res27: Int = -2
The first expression, 1 & 2, bitwise-ands each bit in 1 (0001) and 2 (0010), which yields 0 (0000). The second expression, 1 | 2, bitwise-ors each bit in
5The bitwise-xor method performs an exclusive or on its operands. Identical bits yield a
0.Different bits yield a 1. Thus 0011 ˆ 0101 yields 0110.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index
Section 5.7 |
Chapter 5 · Basic Types and Operations |
132 |
the same operands, yielding 3 (0011). The third expression, 1 ˆ 3, bitwisexors each bit in 1 (0001) and 3 (0011), yielding 2 (0010). The final expression, ~1, inverts each bit in 1 (0001), yielding -2, which in binary looks like 11111111111111111111111111111110.
Scala integer types also offer three shift methods: shift left (<<), shift right (>>), and unsigned shift right (>>>). The shift methods, when used in infix operator notation, shift the integer value on the left of the operator by the amount specified by the integer value on the right. Shift left and unsigned shift right fill with zeroes as they shift. Shift right fills with the highest bit (the sign bit) of the left-hand value as it shifts. Here are some examples:
scala> -1 >> 31 res28: Int = -1
scala> -1 >>> 31 res29: Int = 1
scala> 1 << 2 res30: Int = 4
-1 in binary is 11111111111111111111111111111111. In the first example, -1 >> 31, -1 is shifted to the right 31 bit positions. Since an Int consists of 32 bits, this operation effectively moves the leftmost bit over until it becomes the rightmost bit.6 Since the >> method fills with ones as it shifts right, because the leftmost bit of -1 is 1, the result is identical to the original left operand, 32 one bits, or -1. In the second example, -1 >>> 31, the leftmost bit is again shifted right until it is in the rightmost position, but this time filling with zeroes along the way. Thus the result this time is binary 00000000000000000000000000000001, or 1. In the final example, 1 << 2, the left operand, 1, is shifted left two positions (filling in with zeroes), resulting in binary 00000000000000000000000000000100, or 4.
5.7Object equality
If you want to compare two objects for equality, you can use either ==, or its inverse !=. Here are a few simple examples:
6The leftmost bit in an integer type is the sign bit. If the leftmost bit is 1, the number is negative. If 0, the number is positive.
Cover · Overview · Contents · Discuss · Suggest · Glossary · Index