Книга: Learn Scala for Java Developers
Назад: Inheritance
Дальше: Generics

Control Structures

This chapter is all about control structures, like if statements, switch blocks, loops and breaks. Specifically, we’ll look at:

Conditionals

Ifs and Ternaries

Conditionals are straightforward.

  // java   if (age > 55) {       retire();   } else {       carryOnWorking();   } 

An if in Java looks exactly the same in Scala.

  // scala   if (age > 55) {     retire()   } else {     carryOnWorking()   } 

You’ll often find Scala developers dropping the braces for simple if blocks. For example:

  if (age > 55)     retire()   else     carryOnWorking() 

or even pulling it all onto one line.

  if (age > 55) retire() else carryOnWorking() 

This style is favoured because if/else is actually an expression in Scala and not a statement, and the more concise syntax makes it look more like an expression. What’s the difference? Well, an expression returns a value whereas a statement carries out an action.

 

Expressions vs. Statements

An expression returns a value whereas a statement carries out an action. Statements by their nature often have side effects whereas expressions are less likely to.

For example, let’s add a creation method to our Customer class that will create either a DiscountedCustomer or a regular Customer based on how long they’ve been a customer.

  // java   public static Customer create(String name, String address,           Integer yearsOfCustom) {       if (yearsOfCustom > 2) {           return new DiscountedCustomer(name, address);       } else {           return new Customer(name, address);       }   } 

In Java, we’re forced to return the new Customer from the method. The conditions are statements, things that execute, not expressions which return values. We could do it longhand and create a variable, set then return it, but the point is the same; the statements here have to cause a side effect.

  public static Customer create(String name, String address,           Integer yearsOfCustom) {       Customer customer = null;       if (yearsOfCustom > 2) {           customer = new DiscountedCustomer(name, address);       } else {           customer = new Customer(name, address);       }       return customer;   } 

Because conditionals in Scala are expressions, you don’t need to jump through these hoops. In the Scala equivalent, we can just create the if and both branches will return a customer. As the entire expression is the last statement in the method, it is what will be returned from the method.

  // scala   object Customer {     def create(name: String, address: String, yearsOfCustom: Int) = {       if (yearsOfCustom > 2)         new DiscountedCustomer(name, address)       else         new Customer(name, address)     }   } 

Longhand, we can assign the result of the if (remember it’s an expression not a statement) to a val and then return the value on the last line.

  object Customer {     def create(name: String, address: String, yearsOfCustom: Int) = {       val customer = if (yearsOfCustom > 2)         new DiscountedCustomer(name, address)       else         new Customer(name, address)       customer     }   } 

Another trivial example might be something like this:

  val tall = if (height > 190) "tall" else "not tall"   // scala 

You may have noticed this behaves like a ternary expression in Java.

  String tall = height > 190 ? "tall" : "not tall";     // java 

So, ternaries are expressions in Java but if statements are not. Scala has no conditional operator (?:) because a regular Scala if is an expression; it’s equivalent to Java’s conditional operator. In fact, the bytecode generated for an if uses a ternary.

You don’t have to use an if in Scala like a ternary and assign it to anything, but it’s important to realise that it is an expression and has a value. In fact, everything in Scala is an expression. Even a simple block (denoted with curly braces) will return something.

Switch Statements

There are no switch statements as such in Scala. Scala uses match expressions instead. These look like they’re switching but differ, in that the whole thing is an expression and not a statement. So as we saw with the if, Scala’s switch-like construct has a value. It also uses something called pattern matching which is a lot more powerful, as it allows you to select on more than just equality.

In Java, you might write a switch to work out which quarter a particular month falls in. So, January, February, March are in the first quarter, April, May, June in the second, and so on.

  // java   public class Switch {       public static void main(String... args) {           String month = "August";           String quarter;           switch (month) {               case "January":               case "February":               case "March":                   quarter = "1st quarter";                   break;               case "April":               case "May":               case "June":                   quarter = "2nd quarter";                   break;               case "July":               case "August":               case "September":                   quarter = "3rd quarter";                   break;               case "October":               case "November":               case "December":                   quarter = "4th quarter";                   break;               default:                   quarter = "unknown quarter";                   break;           }           System.out.println(quarter);       }   } 

The break is required to stop the statement execution falling through. When Java selects a case, it has to have a side effect to be useful. In this case, it assigns a value to a variable.

In Scala, we’d start with something like this:

  // scala   object BrokenSwitch extends App {     val month = "August"     var quarter = "???"     month match {       case "January" =>       case "February" =>       case "March" => quarter = "1st quarter"       case "April" =>       case "May" =>       case "June" => quarter = "2nd quarter"       case "July" =>       case "August" =>       case "September" => quarter = "3rd quarter"       case "October" =>       case "November" =>       case "December" => quarter = "4th quarter"       case _ => quarter = "unknown quarter"     }     println(month + " is " + quarter)   } 

The above is a direct syntactic translation. However, Scala doesn’t support the break keyword so we have to leave that out. Rather than switch we use match and we’re saying “does the month match any of these case clauses?”

Rather than the colon, we use => and the underscore at the bottom is the catch-all, the same as default in Java. Underscore is often used in Scala to mean an unknown value.

So although this is a direct translation, when we run it, something has gone wrong. The result hasn’t been set.

The output says:

  August is ??? 

Unlike Java, if a case matches, the break is implicit — there is no fall-through to the next case. So we’ll have to add some code to the empty blocks.

  // scala   object Switch extends App {     val month = "August"     var quarter = "???"     month match {       case "January" => quarter = "1st quarter"       case "February" => quarter = "1st quarter"       case "March" => quarter = "1st quarter"       case "April" => quarter = "2nd quarter"       case "May" => quarter = "2nd quarter"       case "June" => quarter = "2nd quarter"       case "July" => quarter = "3nd quarter"       case "August" => quarter = "3rd quarter"       case "September" => quarter = "3rd quarter"       case "October" => quarter = "4th quarter"       case "November" => quarter = "4th quarter"       case "December" => quarter = "4th quarter"       case _ => quarter = "unknown quarter"     }     println(month + " is " + quarter)   } 

This time it works but we’ve duplicated a fair bit.

To remove some of the duplication, we can combine January, February, and March onto one line, separating them with an or. This means that the month can match either January, February, or March. In all of these cases, what follows the => will be executed.

  case "January" | "February" | "March"  => quarter = "1st quarter" 

Doing this for the rest of the cases would give us the following:

  // scala   object SwitchWithLessDuplication extends App {     val month = "August"     var quarter = "???"     month match {       case "January" | "February" | "March"  => quarter = "1st quarter"       case "April" | "May" | "June" => quarter = "2nd quarter"       case "July" | "August" | "September" => quarter = "3rd quarter"       case "October" | "November" | "December" => quarter = "4th quarter"       case _ => quarter = "unknown quarter"     }     println(month + " is " + quarter)   } 

We’ve condensed the code above by writing expressions within the case clauses themselves. This becomes more powerful when we think of these case clauses as patterns that we can use to build up more and more expressive conditions for the match.

Java can only switch on primitives, enums and from Java 7, string values. Thanks to pattern matching, Scala can match on almost anything, including objects. We’ll look more at pattern matching in Part III.

The other thing to note is that Scala’s version of the switch is an expression. We’re not forced to work with side effects and can drop the temporary variable and return a String to represent the quarter the month falls into. We can then change the quarter variable from being a var to a val.

  // scala   object SwitchExpression extends App {     val month = "August"     val quarter = month match {       case "January" | "February" | "March"  => "1st quarter"       case "April" | "May" | "June" => "2nd quarter"       case "July" | "August" | "September" => "3rd quarter"       case "October" | "November" | "December" => "4th quarter"       case _ => "unknown quarter"     }     println(month + " is " + quarter)   } 

We could even do it in-line. We just need to add some parentheses around the match, like this:

  // scala   object SwitchExpression extends App {     val month = "August"     println(month + " is " + (month match {       case "January" | "February" | "March" => "1st quarter"       case "April" | "May" | "June" => "2nd quarter"       case "July" | "August" | "September" => "3rd quarter"       case "October" | "November" | "December" => "4th quarter"       case _ => "unknown quarter"     }))   } 

Looping Structures; do, while and for

Scala and Java share the same syntax for do and while loops. For example, this code uses a do and a while to print the numbers zero to nine.

  // java   int i = 0;   do {       System.out.println(i);       i++;   } while (i < 10); 

The Scala version would look like this. (There is no ++ incrementer so we use += instead.)

  // scala   var i: Int = 0   do {     println(i)     i += 1   } while (i < 10) 

And while loops are the same.

  // java   int i = 0;   while (i < 10) {       System.out.println(i);       i++;   } 
  // scala   var i: Int = 0   while (i < 10) {     println(i)     i += 1   } 

Things get more interesting when we look at for loops. Scala doesn’t have for loops like Java does; it has what’s referred to as the “generator-based for loop” and the related “for comprehension” instead. To all intents and purposes, these can be used like Java’s for loop construct, so for the most part you won’t have to worry about the technical differences.

Java’s for loop controls the iteration in three stages: initialise, check and update.

Fig. 2.7. The typical for loop iteration stages.

Fig. 2.7. The typical for loop iteration stages.

There is no direct analog in Scala. You’ve seen an alternative — using the while loop to initialise a variable, check a condition, then update the variable — but you can also use a generator-based for loop in Scala. So the following for in Java:

  // java   for (int i = 0; i < 10; i++) {       System.out.println(i);   } 

…would look like this using a generator-based for loop in Scala:

  // scala   for (i <- 0 to 9) {     println(i)   } 

The i variable has been created for us and is assigned a value on each iteration. The arrow indicates that what follows is a generator. A generator is something that can feed values into the loop. The whole thing is a lot like Java’s enhanced for loops where anything that is Iterable can be used. In the same way, anything that can generate a iteration in Scala can be used as a generator.

In this case, 0 to 9 is the generator. Zero is an Int literal and the class Int has a method called to that takes an Int and returns a range of numbers which can be enumerated. The example uses the infix shorthand, but we could have written it longhand like this:

  for (i <- 0.to(9)) {     println(i)   } 

It’s very similar to the following enhanced for loop in Java, using a list of numbers:

  // java   List<Integer> numbers = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);   for (Integer i : numbers) {       System.out.println(i);   } 

…which itself could be rewritten in Java as the following:

  numbers.forEach(i -> System.out.println(i));      // java 

or as a method reference.

  numbers.forEach(System.out::println);             // java 

Unsurprisingly, Scala has a foreach method of its own.

  (0 to 9).foreach(i => println(i))                 // scala 

We use the to method again to create a sequence of numbers. This sequence has the foreach method, which we call, passing in a lambda. The lambda function takes an Int and returns Unit.

We can even use Scala’s shorthand like we did with Java’s method reference:

  (0 to 10).foreach(println(_))                     // scala 

For-Loop vs For Comprehension

What’s the difference between the generator-based for loop and the for comprehension?

The generator-based for loop will be converted by the compiler into a call to foreach against the collection. A for comprehension will be converted to a call to map on the collection. The for comprehension adds the keyword yield to the syntax:

for (i <- 0 to 5) yield i * 2      // results in (0, 2, 4, 6, 8, 10) 

See the chapter for more details.

Breaking Control Flow (break and continue)

Scala has no break or continue statements, and generally discourages you from breaking out of loops. However, you can use a library method to achieve the same thing. In Java, you might write something like this to break out of a loop early:

  // java   for (int i = 0; i < 100; i++) {       System.out.println(i);       if (i == 10)           break;   } 

In Scala, you need to import a Scala library class called Breaks. You can then enclose the code to break out from in a “breakable” block and call the break method to break out. It’s implemented by throwing an exception and catching it.

  // scala   import scala.util.control.Breaks._   breakable {                           // breakable block     for (i <- 0 to 100) {       println(i)       if (i == 10)         break()                         // break out of the loop     }   } 

Exceptions

Exceptions in Scala are handled in the same way as Java. They have all the same schematics in terms of interrupting control flow and aborting the program if not dealt with.

Exceptions in Scala extend java.lang.Throwable like their Java counterparts but Scala has no concept of checked exceptions. All checked exceptions thrown from existing Java libraries get converted to RuntimeExceptions. Any exceptions you throw don’t need to be dealt with to keep the compiler happy; all exceptions in Scala are runtime exceptions.

Fig. 2.8. The Java exception hierarchy. Scala doesn't use checked exceptions.

Fig. 2.8. The Java exception hierarchy. Scala doesn’t use checked exceptions.

Catching exceptions uses pattern matching like we saw earlier with match expressions. In Java you might do something like this to get the contents of a web page:

  // java   try {       URL url = new URL("http://baddotrobot.com");       BufferedReader reader = new BufferedReader(           new InputStreamReader(url.openStream()));       try {           String line;           while ((line = reader.readLine()) != null)               System.out.println(line);       } finally {           reader.close();       }   } catch (MalformedURLException e) {       System.out.println("Bad URL");   } catch (IOException e) {       System.out.println("Problem reading data: " + e.getMessage());   } 

We start with a URL to curl. This can throw a MalformedURLException. As it’s a checked exception, we’re forced to deal with it. We then create a Reader and open a stream from the URL ready for reading. This can throw another exception, so we’re forced to deal with that too. When we start reading, the readLine method can also throw an exception but that’s handled by the existing catch. To make sure we clean up properly in the event of an exception here, we close the reader in a finally block.

If we want to use Java 7’s try-with-resources construct, we can avoid the finally clause. The try-with-resources syntax will automatically call close on the reader.

  // java   try {       URL url = new URL("http://baddotrobot.com");       try (BufferedReader reader = new BufferedReader(             new InputStreamReader(url.openStream()))) {           String line;           while ((line = reader.readLine()) != null)               System.out.println(line);       }   } catch (MalformedURLException e) {       System.out.println("Bad URL");   } catch (IOException e) {       System.out.println("Problem reading data: " + e.getMessage());   } 

In Scala things look pretty much the same.

  // scala   try {     val url = new URL("http://baddotrobot.com")     val reader = new BufferedReader(new InputStreamReader(url.openStream))     try {       var line = reader.readLine       while (line != null) {         line = reader.readLine         println(line)       }     } finally {       reader.close()     }   } catch {     case e: MalformedURLException => println("Bad URL")     case e: IOException => println(e.getMessage)   } 

We create the URL as before. Although it can throw an exception, we’re not forced to catch it. It’s a Java checked exception but Scala is converting it to a runtime exception.

Although we’re not forced to, we do actually want to deal with the exceptions. So we use the familiar try and catch statements. In the catch, the exceptions are dealt with using match expressions. We can tweak the pattern if we don’t actually need the exception in the code block by replacing the variable name with an underscore. That means we don’t care about the variable, only the class.

  case _: MalformedURLException => println("Bad URL") 

We just need to add the finally block back in. finally is just as it is in Java. There is no try-with-resources equivalent in Scala, although you can write your own method to achieve the same thing. (Hint: With something like the Loan pattern.)

Назад: Inheritance
Дальше: Generics