1. Overview

In this tutorial, we’re going to look at some basic Scala operators. We’ll go through the rules of operator precedence and associativity.

2. Methods & Operators

In Scala, all operators are methods. Operators themselves are just syntactic sugar or a shorthand to call methods.

For example, let’s look at the arithmetic addition operator (+):

assert(1 + 2 == 3)

Here we use the symbol + as an operator to add two numbers. When we use the + symbol, internally Scala is invoking the method called +. We can also call the method + with the dot notation and it would give the same result:

assert(1.+(2) == 3)

Any method in Scala can be used as an operator. For example, the String.charAt() method, which returns a character at a given index can be invoked using the standard dot notation:

assert("Baeldung".charAt(0) == 'B')

It can also be invoked as an operator:

val char = "Baeldung" charAt 0
assert(char == 'B')

Both arithmetic addition (+) and String.charAt() are examples of infix operators. We can use infix notation to invoke any method that accepts at least one argument.

When a method accepts multiple parameters, we have to put all the parameters between a pair of parentheses to use the method in infix notation. For example, the String.replace() method takes two parameters:

assert("Baeldung".replace('g', 'G') == "BaeldunG")

We replaced all the small case g with the capital G. Using parentheses, we can call it in operator notation:

val str = "Baeldung" replace ('g', 'G')
assert(str == "BaeldunG")

2.1. Postfix and Prefix Operator Notation

So far, we saw examples of infix operator notation, which means the operator (or method) sits between two operands. There are two other types of operator notation – prefix and postfix.

Prefix operators appear before their operands. For example “” in -10. All prefix operators are converted to methods with the name unary_<operator>:

assert(10.unary_- == -10)

Postfix operators appear after their operands. We can apply the String method toUpperCase as a postfix operator:

val strUpperCase = "baeldung" toUpperCase
assert(strUpperCase == "BAELDUNG")

As both prefix and postfix operators take just one argument, they are also called unary operators.

3. Basic Operators

All of Java’s basic operators work the same way in Scala, with some subtle differences. Let’s take a look at all basic types of Scala operators:

3.1. Arithmetic Operators

Scala has following arithmetic binary operators available for numeric types:

  • Addition (+)
  • Subtraction (-)
  • Multiplication (*)
  • Division (/)
  • Remainder (%)

We can use the infix operator notation to invoke them all:

assert(1 + 2 == 3)
assert(3.1 - 1.0 == 2.1)
assert(2 * 6 == 12)
assert(15 / 6 == 2)
assert(15 % 6 == 3)

Additionally, Scala also has two unary operators to indicate whether a number is positive or negative:

  • + (method unary_+)
  • – (method unary_-)

+ makes a numeric literal positive and makes a literal negative:

val num = 10
assert(-num == -10)
assert(10 + -num == 0)

The default value of a number literal is positive if no unary operator is specified.

3.2. Relational Operators

There are five relational operators in Scala:

  • Greater than (>)
  • Less than (<)
  • Greater than or equal to (>=)
  • Less than or equal to (<=)

All of the above relational operators evaluate to a Boolean:

assert(10 < 20 == true) 
assert(10 > 20 == false) 
assert(3.0 >= 2.5 == true) 
assert(3.0 <= 2.5 == false)

Additionally, we also have a unary negate (!) operator to invert a Boolean:

assert(!true == false)

3.3. Logical Operators

Logical operators, logical-or (|| and | ) and logical-and (&& and &) take Boolean operands and evaluate to a Boolean result. Logical-or evaluates to true only when either of the two operands is true. Logical-and evaluates to true only when both operands are true.

assert(true || false == true)
assert(true && false == false)

The && and || are short-circuit operators. Expressions with these operators do not always evaluate both of its operands. The right-hand side of the expression is ignored if the left-hand side determines the result alone.

For example, if the left-hand side of the expression with && is false, the expression does not need to evaluate the right-hand side as it would always result in false. Similarly, if the left-hand side of || is true, the right-hand side is not evaluated as the value would be true regardless.

Let’s see that in example for &&:

def printTrue() : Boolean = {
  println("true");
  true
}

def printFalse() : Boolean = {
  println("false"); 
  false
}
val result1 = printFalse() && printTrue() // only "false" is printed
assert(result1 == false)
val result2 = printTrue() && printFalse() // "true" and "false" are printed
assert(result2 == false)

In the first example, only the left-hand side is evaluated as it was enough to determine the result of the expression. As the printFalse method returns false, there’s no need to evaluate the right-hand side.

In the second example, however, both sides of the expression are evaluated since the printTrue method returns true, and to determine the final result we have to know the value of the right-hand side.

If we want to evaluate both sides of the expression, we can use & and | instead.

val result3 = printFalse() & printTrue()  // "false" and "true" are printed
assert(result3 == false)

As we can see, & evaluates both sides of the expressions and does not short-circuit.

3.4. Bitwise Operators

Bitwise operators perform operations on individual bits of integer data types. Scala has the following bitwise operators:

  • Bitwise-or (|)
  • Bitwise-and (&)
  • Bitwise-xor (^)
  • Unary-bitwise-complement (~)

Each performs the operation on the individual bits:

val bitwiseAndResult = 2 & 6
assert(bitwiseAndResult == 2)

& performs bitwise-and for each bit in 2 (0010) and 6 (0110), which evaluates to 2 (0010). Similarly, | and ^ perform bitwise-or and bitwise-xor respectively.

~ inverts every bit, from true to false and vice-versa:

assert(~2 == -3)

Scala also has three shift methods on integer data types:

  • shift-left (<<)
  • shift-right (>>)
  • unsigned-shift-right (>>>)

All shift operators shift the integer value of the left operand by the amount specified by the value of the right operand. Shift-left shifts bits to the left filling to the right with zeros:

assert(2 << 2 == 8)

Here we are shifting 2 (0010) twice to the left which yields, 8 (1000).

Both shift-right and unsigned-shift-right shifts bits to the right. Shift-right fills with the highest bit of the left-hand value (signed-bit) as it shifts while unsigned-shift-right fills with zeros:

assert(-8 >> 2 == -2)

-8 in binary is 11111111111111111111111111111000. Here we are shifting bits to the right 2 times, filling up positions with the signed bit, which it being negative, is 1. Resulting in -2 (11111111111111111111111111111110).

assert(-8 >>> 2 == 1073741822)

Unsigned-shift-right, unlike shift-right, fills up bits with 0 as it shifts. Shifting left -8 (11111111111111111111111111111000) twice, yields 1073741822 (00111111111111111111111111111110).

3.5. Equality Operators

In Scala we use == to check for equality of two objects. != checks for inequality. Unlike Java, in Scala, you can use == to check equality for all objects, not just basic types.

4. Operator Precedence

When there are multiple operators present in an expression, they are evaluated based on operator precedence.

For example, 2+3*6 evaluates to 20 and not 30 because multiplication (*) operator has higher precedence than addition (+), so multiplication is carried out first.

Since Scala’s operators are just methods, the precedence is worked out by the priority of the first character of the method used in operator notation:

(all other special characters)
* / %
+ -
:
=!
< >
&
^
|
(all letters)

The higher the first character of a method in the above table the higher is the priority. If the first characters are at the same level then the order of evaluation is left to right:

assert(2 + 3 * 4 == 14)

Here the order of evaluation is 2 + (3 * 4), * is higher in precedence, resulting in 14.

assert(4 - 2 + 1 == 3)

Because + and – are at the same level in precedence, the order of evaluation is from left to right, that is (4 – 2) + 1.

There is one exception to the precedence rule. If the operator ends with the equals character (=), and its not one of the comparison operators (<=, =>, ==, !=), then the precedence is the same as of a simple assignment (=).

var num = 10
num += 2 * 10
assert(num == 30)

The order of evaluation is num += (2 * 10) as += has same precedence as = and is below multiplication (*).

5. Operator Associativity

When multiple operators with the same precedence appear in an expression the associativity determines how operators are grouped. The last character of a method determines associativity.

Methods that end with the “:” character are invoked by passing left operand to the one in right. Methods that end in any other character are opposite, the right operand is passed to the left operand:

assert(2 * 3 == 6)

Here the associativity is 2.*(3). Let’s see associativity for the List method :::,

assert(List(1,2) ::: List(3) == List(1, 2, 3))

In this case, the associativity changes and its evaluated as, List(3).:::(List(1,2)):

assert(List(3).:::(List(1,2)) == List(1, 2, 3))

6. Conclusion

In this article, we looked at how all operators in Scala are just methods. We went through all basic operators in Scala and learned about their precedence and associativity rules.

As always, all code examples can be found over on GitHub.

guest
0 Comments
Inline Feedbacks
View all comments