CHAPTER 9

image

Scala and Java Interoperability

One of the main design goals of Scala is running on a JVM and providing interoperability with Java. The need for Scala and Java interoperability arises when you want to use existing Java libraries or frameworks. Scala code is often used in tandem with large Java programs and frameworks. Even though integration with Java is easy for the most part, we encourage you to use pure Scala as much as possible. When you are working with the Java library or framework, first try to find something equivalent in Scala, and use Java if there’s no equivalent Scala library available.

Scala is compiled to Java bytecodes, and you can use tools like Java class file disassembler javap to disassemble bytecodes generated by the Scala compiler. By running on a JVM you can take advantage of all the frameworks and tools built into other JVM languages. In most cases, Scala features are translated into Java features so that Scala can easily integrate with Java. However, some Scala features don’t directly map to Java, and in such case you have to learn how to handle features that are available in Java but not in Scala. The features that are available in Java but not in Scala are static members and checked exceptions, and some of the Scala features that are not available in Java is traits. If you are using Java from Scala, you have to deal with mutability, exceptions, and nulls that are interdicted in the Scalaworld. Because Scala works seamlessly with Java, most of the time you can combine the languages without worrying too much.

This chapter describes how Scala is translated to Java and the use of Java annotations in Scala framework. The goal of this chapter is to show you how easily you can integrate Scala with Java. You’ll also learn how Scala annotations help in integration—for example, generating JavaBean-style getters and setters.

Translating Java Classes to Scala Classes

The interoperability between Scala and Java classes makes it straightforward to replace or extend an existing Java class with a Scala class. Listing 9-1 shows a Class declaration in Java.

Listing 9-1. A Class Declaration in Java

public class Book {}

Contrast Listing 9-1 with Listing 9-2.

Listing 9-2. Scala Equivalent of a Class Declaration

class Book

As you learned in the Chapter 3, you don’t need braces when there’s no content and everything is public in Scala by default.

Now define a Java class with constructor that binds instance variables with accessors as illustrated in Listing 9-3.

Listing 9-3. A Java Class with a Constructor

public class Book {
  private final int isbn;
  private final String title;

  public Book(int isbn, String title) {
    this.isbn = isbn;
    this.title = title;
  }

  public int getIsbn() {
    return isbn;
  }

  public String y() {
    return y;
  }
}

As you can see in Listing 9-3, the constructor binds the instance variables isbn and title with accessors getIsbn and getTitle. Let’s see the Scala equivalent as shown in Listing 9-4.

Listing 9-4. Scala Equivalent of Listing 9-3

class Book(val isbn: Int, val title: String)

As you can see in Listing 9-4, adding val makes the constructor arg bind to a field of the same name. Listing 9-5 shows the Java class NonFiction with a constructor that calls the superclass.

Listing 9-5. Constructor Calling Superclass in Java

public class NonFiction extends Book {
  public NonFiction(String title) {
    super(title)
  }
}

Listing 9-6 shows the Scala equivalent of Listing 9-5.

Listing 9-6. Scala Equivalent of Listing 9-5

class NonFiction (title: String) extends Book(title)

You can see in Listing 9-6 that the primary constructor goes in the declaration. What about the multiple constructors? You will see that in the section that follows. Let’s compare the mutable and immutable instance variables in Java and Scala. Listing 9-7 illustrates the mutable instance variable in Java.

Listing 9-7. Mutable Instance Variable in Java

public class Book {
  private String title = "Beginning Scala";

  public String getTitle() {
    return title;
  }

  public void setTitle(String t) {
title = t;
  }
}

Listing 9-8 illustrates the Scala equivalent of Listing 9-7.

Listing 9-8. Scala Equivalent of Listing 9-7
class Book {
  var  title = "Beginning Scala"
}

As you can see in Listing 9-8, just writing var in the class body defines a mutable field. Now let’s see the immutable instance variables in Java and Scala. Listing 9-9 illustrates an immutable instance variable in Java.

Listing 9-9. Immutable Instance Variable in Java

public class Book {
  private final intisbn = 999;

  public int getIsbn() {
    return isbn;
  }
}

Listing 9-10 illustrates the Scala equivalent of Listing 9-9.

Listing 9-10. Scala Equivalent of Listing 9-9

class Book {
  val isbn = 999
}

As you may have noticed in Listing 9-9, isbn is also the name of an accessor method.

TRANSLATING JAVA IMPORTS TO SCALA IMPORTS

// import in Java
importcom.modA.ClassA;
import com.modB.ClassB1;
import com.modB.ClassB2;
importcom.modC.*;

// import in Scala
importcom.modA.ClassA;
importcom.modB.{ClassB1, ClassB2} // You can stack multiple imports from the same package in braces.
importcom.modC._ // Underscore in Scala imports is equivalent of * in Java imports.

Fortified with these reflections, let’s now see how to translate multiple constructors while refactoring a Java class to a Scala class. For instance, let’s take the Book Java class shown in Listing 9-11:

Listing 9-11. Java class with Multiple Constructors

public class Book {
private Integer isbn;
private String title;
public Book(Integer isbn) {
this.isbn = isbn;
}
public Book(Integer isbn, String title) {
this.isbn = isbn;
this.title = title;
}
public Integer getIsbn() {
return isbn;
}
public void setIsbn(Integer isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}

As you can see in the Listing 9-11, the Book Java class has two constructors, one that takes isbn as the parameter and the other that takes isbn and title as the parameters. The Book Java class also has getters and setters for title and isbn. Now let’s refactor the Book Java class into a Scala class with class parameters and create an instance as shown in Listing 9-12.

Listing 9-12. Refactoring Listing 9-11

class Book ( var isbn: Int, var title: String)

You can try Listing 9-12 on the REPL:

scala> class Book ( var isbn: Int, var title: String)
defined class Book
scala> val book = new Book(999, "Beginning Scala")
book: Book = Book@10ddb0e

However, a constructor that takes only a single parameter title does not exist in the Listing 9-12 definition. For example if you create the Book instance with a constructor that takes a single title parameter, you will get an error, as following on the REPL:

<console>:8: error: not enough arguments for constructor Book: (isbn: Int, title: String)Book.
Unspecified value parameter title.
       val book2 = new Book("Beginning Java")
                      ^

To complete our refactoring of the Java class, we need an extra constructor as shown in Listing 9-13:

Listing 9-13. Scala Equivalent of Listing 9-11

class Book ( var isbn: Int, var title: String) {
def this( title: String) = this(0,title)
}

Is Listing 9-13 comprehensible to you? You learned about auxiliary constructors in Chapter 3. As you may recall, any auxiliary constructor must immediately call another this(...) constructor and call the primary constructor to make sure all the parameters are initialized.

Now you can try Listing 9-13 on the REPL as shown:

scala> class Book ( var isbn: Int, var title: String) {
     | def this( title: String) = this(0,title)
     | }
defined class Book
scala> val book1 = new Book(999, "Beginning Scala")
book1: Book = Book@132c5fd
scala> val book2 = new Book("Beginning Java")
book2: Book = Book@3e4e8a

This time, we created an instance with the auxiliary constructor.

Now access title and isbn on REPL as shown:

scala> book1.title
res38: String = Beginning Scala
scala> book2.title
res39: String = Beginning Java
scala> book1.isbn
res40: Int = 9999

You can also set title as following:

scala> book2.title = "Beginning Groovy"
book2.title: String = Beginning Groovy
scala> book2.title
res42: String = Beginning Groovy

So we can get and set title and isbn. But if you compare Listing 9-13 with Listing 9-11, you will see that we did not add the getters and setters to Listing 9-13 corresponding to Listing 9-11. But you can still get and set isbn and title because of the generated getters and setters that follow the Scala convention. When you compile Listing 9-13 with scalac and then disassemble it with javap, you’ll see that no getter or setter methods corresponding to Listing 9-11 are generated and the only getters and setters that are generated are the ones that follow the Scala convention as shown in Listing 9-14:

>scalac Book.scala
>javap Book

Listing 9-14. Compiled from “Book.scala” in Listing 9-11

public class Book {
  public int isbn();
  public void isbn_$eq(int);
  public java.lang.String title();
  public void title_$eq(java.lang.String);
  public Book(int, java.lang.String);
  public Book(java.lang.String);
}

So the class definition in Listing 9-13 generates getter and setters that follow scala convention but not that follow Java convention as shown in the Listing 9-11. Generating getters and setters that follow Java convention becomes important when you need to interact with a Java class or a library that accepts only classes that conform to the JavaBean specification. We will see this in the next section.

JavaBeans Specification Compliant Scala Classes

To ensure compatibility with Java frameworks, you may need Java-style getters and setters on the fields of your class to interact with a Java class or library that accepts only classes that conform to the JavaBean specification. To have Java-style getters and setters is to annotate the field with scala.beans.BeanProperty as shown in the following lines of command in Listing 9-15:

Listing 9-15. Annotating Constructor Parameter with @BeanProperty

import scala.beans.BeanProperty
class Book(@BeanProperty var isbn:Int, @BeanProperty var title:String)

You can run the Listing 9-15 in REPL as following:

scala> import scala.beans.BeanProperty
import scala.beans.BeanProperty
scala> class Book(@BeanProperty var isbn:Int, @BeanProperty var title:String)
defined class Book

You can see how the @BeanProperty annotation works by compiling the Book class and then disassembling it. First, save these contents to a file named Book.scala:

Then compile the class:

$ scalac Book.scala

After it’s compiled, disassemble it with the javap command:

$ javap Book

Listing 9-16 shows the compiled Book class from Book.scala.

Listing 9-16. Compiled from “Book.scala” in Listing 9-15

public class Book {
  public int isbn();
  public void isbn_$eq(int);
  public void setIsbn(int);
  public java.lang.String title();
  public void title_$eq(java.lang.String);
  public void setTitle(java.lang.String);
  public int getIsbn();
  public java.lang.String getTitle();
  public Book(int, java.lang.String);
}

As you can see from the disassembled code, the methods getTitle, setTitle, getIsbn, and setIsbn have all been generated because of the @BeanProperty annotation. Note that without these methods, your class will not follow the JavaBean specification.

Image Note  Use the @BeanProperty annotation on your fields, also making sure you declare each field as a var. if you declare your fields as type val, the setter methods (setTitle, setIsbn) won’t be generated.

You saw how to use the @BeanProperty annotation on class constructor parameters. In the same manner, you can also use the @BeanProperty annotation on the fields in a Scala class.

Next you will learn to use a Scala feature not available in Java, such as traits.

Java Interfaces and Scala Traits

A Java class can’t extend a Scala trait that has implemented methods. To understand the problem let’s first look at a regular Java interface as shown in Listing 9-17.

Listing 9-17. A Regular Java Interface Declaration

public interface Book {
  public abstract boolean isBestSeller();
}

The Scala equivalent of Listing 9-17 is shown as following:

Listing 9-18. Scala Equivalent of Listing 9-17

trait Book {
  def isBestSeller: Boolean
}

As you can see in the Listing 9-17, isBestSeller is an abstract method. In Scala, methods are denoted with a def keyword. But isBestSeller in Listing 9-17 is an abstract method. How do we represent abstract method in Scala? In Scala, if there is no = assignment, then the methods denoted with a def keyword or the functions denoted with a val keyword are abstract. For example, let’s look at the Java method that returns some value in Listing 9-19.

Listing 9-19. A Concrete Java Method

public String someMethod(int arg1, boolean arg2) {
  return "voila";
}

The Scala equivalent of Listing 9-19 is shown in Listing 9-20.

Listing 9-20. Scala Equivalent of Listing 9-19

def someMethod(arg1: Int, arg2: Boolean): String = "voila"

As you can see in Listing 9-20, =denotes the implementation.

Now let’s look at an abstract Java method as illustrated in Listing 9-21.

Listing 9-21. An Abstract Java Method

abstract int doTheMath(int i);

The Scala equivalent of an abstract Java method doStuff is shown in Listing 9-22.

Listing 9-22. Scala Equivalent of Listing 9-21

def doTheMath(i: Int): Int

Image Note  If there’s no definition provided with =, then it’s automatically abstract.

Now let’s go back to the original problem. A Java class can’t extend a Scala trait that has implemented methods. So if you are trying to use Scala a trait (that has implemented methods in it) from Java you will run into problems. To demonstrate the problem, first create a trait with a simple implemented method named add (see Listing 9-23).

Listing 9-23. A Scala Trait

trait Computation {
def add(a: Int, b: Int) = a + b
}

You’ve written a Scala trait with implemented methods and need to be able to use an add method from a Java application (see Listing 9-24).

Listing 9-24. A Java Application

public class DoTheMath{
public static void main(String[] args) {
DoTheMath d = new DoTheMath();
// do the math here
}
}

The type Computation cannot be the superclass of Java class DoTheMath simply because a superclass in Java must be a class; that is, Java class DoTheMath cannot use the extend keyword to extend Computation. Moreover, the Java class DoTheMath cannot implement the trait Computation because in Java you implement interfaces, and the trait Computation has an implemented behavior, so Computation is not a like a regular Java interface. To be able to use the implemented method add of a Scala trait Computation from Java class DoTheMath, you must wrap the trait Computation in a Scala class. Listing 9-25 shows a Scala class that wraps the trait Computation.

Listing 9-25. Scala Class that Wraps the Trait Computation

class JavaInteroperableComputation extends Computation

Now the Java class DoTheMath can extend the Scala Java Interoperable Computation class, and access the add method as shown in the Listing 9-26.

Listing 9-26. Accessing the add Method of the Scala Trait from Java Class

public class DoTheMath extends JavaInteroperableComputation{
public static void main(String[] args) {
DoTheMath d = new DoTheMath();
d.add(3,1);
}
}

Image Note  Wrap your Scala traits with implemented behavior in the Scala class for its Java callers.

Java Static Members and Scala Objects

Java code often refers to the static keyword to implement a singleton object as shown in Listing 9-27.

Listing 9-27. A Singleton in Scala

public class Book{
    private static Book book;
    private Book() {}
    public static synchronized Book getInstance() {
        if (book == null) {
book = new Book();
        }
        return book;
    }
}

There is no such thing as static in Scala. In Java, the static does not belong to an object, can’t be inherited, and doesn’t participate in polymorphism, thus statics aren’t object-oriented. Scala, on the other hand, is purely object-oriented. Scala does not support the static, but instead provides the notion of an object in place of the class declaration. If you need to refactor Java code in Listing 9-27 into Scala, simply use the object declaration instead of class as shown in Listing 9-28.

Listing 9-28. Scala Equivalent of Listing 9-27

object Book{
}

Scala objects give you extra advantage in that Scala objects can also extend interfaces and traits. So Scala provides a special syntax that gives you a singleton for free, without all the syntax involved in declaring (see in Listing 9-27). But what if you want to mix static and instance members? In Java, you can do this as shown in Listing 9-29.

Listing 9-29. Java class with Instance and Static Methods

public class Book {
    public String getCategory() {
        return "Non-Fiction";
    }

    public static Book createBook() {
        return new Book();
    }
}

In addition to the notion of object, Scala provides the notion of companion object, which consists of an object that cohabits with a class of the same name in the same package and file. The companion object enables storing of static methods and from this, you have full access to the classes’ members, including private ones. Scala allows you to declare both an object and a class of the same name, placing the static members in the object and the instance members in the class. The Scala equivalent of Listing 9-29 is shown in Listing 9-30.

Listing 9-30. Scala Equivalent of Listing 9-29

class Book {
  defgetCategory() = " Non-Fiction"
}
object Book {
  def createBook() = new Book()
}

Handling Exceptions

You can define the Scala method as shown in Listing 9-31, without declaring that it throws an exception.

Listing 9-31. A Scala Method that Throws an Exception

class SomeClass {
def aScalaMethod {
throw new Exception("Exception")
}
}

This method can then be called from Java as shown in Listing 9-32.

Listing 9-32. Calling a Scala Method from a Java Class

public static void main(String[] args) {
SomeClass s = new SomeClass();
s.aScalaMethod();
}

However, when the Java developer calls scalaMethod, the uncaught exception causes the Java method to fail:

[error] (run-main) java.lang.Exception: Exception!
java.lang.Exception: Exception!
at SomeClass.aScalaMethod

If you don’t mark the aScalaMethod method with the @throws annotation, a Java developer can call it without using a try/catch block in her method, or declaring that her method throws an exception.

For the Java callers of your Scala methods, add the @throws annotation to your Scala methods so they will know which methods can throw exceptions and what exceptions they throw. For example, Listing 9-33 shows how to add an @throws annotation to let callers know that the aScalaMethod method can throw an Exception.

Listing 9-33. Annotating Scala method with @throws

class SomeClass {
@throws(classOf[Exception])
def aScalaMethod {
throw new Exception("Exception")
}
}

Your annotated Scala method works just like a Java method that throws an exception. If you attempt to call aScalaMethod from a Java class without wrapping it in a try/catch block, or declaring that your Java method throws an exception, the compiler (or your IDE) will throw an error.

In your Java code, you’ll write a try/catch block as usual to handle the exception See Listing 9-34).

Listing 9-34. Calling Annotated ScalaMethod from Java

SomeClasss = new SomeClass();
try {
s.aScalaMethod();
} catch (Exception e) {
System.err.println("Caught the exception.");
e.printStackTrace();
}

Summary

This chapter showed how Scala is translated to Java, which is especially important if you call Scala code from Java. You learned that in most cases, Scala features are translated to Java features so that Scala can easily integrate with Java. You learned how to handle features that are available in Java but not in Scala, such as static members and checked exceptions, and how to use Scala features such as traits in Java code. You also learned how Scala annotations help in integration—for example, generating JavaBean-style get and set. Scala code is often used in tandem with large Java programs and frameworks. In the next chapter you will learn about some best-practice Scala idioms.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset