Java, SAM and Scala

on waitingforcode.com

Java, SAM and Scala

You're still doing Java/C#/JavaScript/Python/PHP... and need a wind of change? I was like that 4 years ago. I changed then to the data engineering field and it solved my existential problems :) If you want to follow my path, I prepared a course that will help you with that! Join the class!
During the years Java was much more verbose than its JVM-based colleagues (Clojure, Groovy, Scala, ...). But it changed with Java 8 and its Lambda expressions. However the years of the verbosity brought some patterns into Java-based applications. One of them is known as SAM and fortunately it can be easily used with Scala.

This post focuses on the interoperability between Java SAM and Scala. The first part gives an example of what we're talking about. The second part explains how to handle it in Scala.

Single Abstract Methods in Java

Simple speaking, SAM is the acronym for Single Abstract Method. It represents an abstract class or interface having one and exactly one not implemented (abstract) method. If you've done Java for a long time you've certainly found some examples of SAMs in the API. All kind of single-method swing listeners are the examples of SAMS, as for instance:

public interface TextListener extends EventListener {

    /**
     * Invoked when the value of the text has changed.
     * The code written for this method performs the operations
     * that need to occur when text changes.
     */
    public void textValueChanged(TextEvent e);

}

Another and maybe more speaking use cases are the most often used nowadays: Runnable, Callable or the ones added in Java 8: Predicate<T> or Function<T, R>. All such SAM classes are annotated with @FunctionalInterface annotation. It prevents against incorrect implementation because the compiler is able to detect at compile time whether the annotated interface defines exactly one abstract method. Below you can find an example of custom SAM interface:

@FunctionalInterface
public interface SamIllustration {

    String getSomeText();

}

// It can be used later as:
// SamIllustration sam = () -> "test";
// System.out.println(sam.getSomeText());

SAM and Scala

As you already could see through all previous posts, especially after the collections conversion in Scala, Scala works with Java data structures pretty well. However, the use of SAM interfaces prior to Scala 2.12 was a little bit verbose. In order to use Java SAM types we had to define it in Java-anonymous class style:

object SamIllustrationUse {

  def main(args: Array[String]): Unit = {
    val samIllustration: SamIllustration = new SamIllustration {
      override def getSomeText: String = "test"
    }

    print(samIllustration.getSomeText)
  }

}

Fortunately it changed with Scala 2.12 where we can use Java's SAM types more in Scalaistic manner:

object SamIllustrationUse {

  def main(args: Array[String]): Unit = {
    val samIllustration: SamIllustration = () => "test"
    println(samIllustration.getSomeText)
  }

}

Note however that an arbitrary lambda expression where the compiler deduces the type won't work as expected:

def main(args: Array[String]): Unit = {
  val samIllustration = () => "test"
  println(samIllustration.getSomeText)
}
//  Error:(7, 29) value getSomeText is not a member of () => String
//    println(samIllustration.getSomeText)

As you can see, the compiler considers the expression as an instance of a function and not SamIllustration (btw, how could it deduce it's a SamIllustration?). Internally the generated bytecode is the same as if the lambda expression would be written natively in Java. Below you can find the bytecode for Java's implementation:

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
         0: invokedynamic #2,  0              // InvokeDynamic #0:getSomeText:()Ltest/SamIllustration;
         5: astore_1
         6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
         9: aload_1
        10: invokeinterface #4,  1            // InterfaceMethod test/SamIllustration.getSomeText:()Ljava/lang/String;
        15: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        18: return
      LineNumberTable:
        line 6: 0
        line 7: 6
        line 8: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  args   [Ljava/lang/String;
            6      13     1   sam   Ltest/SamIllustration;
}

# The code was generated for:
public class SamIllustrationMain {

    public static void main(String[] args) {
        SamIllustration sam = () -> "test";
        System.out.println(sam.getSomeText());
    }

}

The bytecode for Scala gives that:

  public void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=2
         0: invokedynamic #39,  0             // InvokeDynamic #0:getSomeText:()Ltest/SamIllustration;
         5: astore_2
         6: getstatic     #44                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
         9: aload_2
        10: invokeinterface #48,  1           // InterfaceMethod test/SamIllustration.getSomeText:()Ljava/lang/String;
        15: invokevirtual #52                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
        18: return
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            5      13     2 samIllustration   Ltest/SamIllustration;
            0      19     0  this   LSamIllustrationUse$;
            0      19     1  args   [Ljava/lang/String;
      LineNumberTable:
        line 6: 0
        line 7: 6
    MethodParameters:
      Name                           Flags
      args                           final

# For code
object SamIllustrationUse {

  def main(args: Array[String]): Unit = {
    val samIllustration: SamIllustration = () => "test"
    println(samIllustration.getSomeText)
  }

} 

In both cases the compiler behaves the same and it creates a lightweight class for the closure directly from LambdaMetaFactory:

# Scala version
  #26 = Methodref          #22.#25        // java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
  #27 = MethodHandle       #6:#26         // invokestatic java/lang/invoke/LambdaMetafactory.altMetafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;
# Java version
  #28 = MethodHandle       #6:#41         // invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
  #41 = Methodref          #52.#53        // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

This short post is another proof, after the one about collections conversion, for Scala - Java interoperability. As we could see in the first section, the SAM improved with Java 8 that proposed @FunctionalInterface annotation enforcing SAM constraints about the single one abstract method in the body. Java 8 also brought a writing facility because thanks to the Lambda expressions SAMs can be pretty easily inlined. Scala, prior to its 2.12 version, required a verbose syntax to define Java's SAM. Only the 2.12 release lets us write them with the same inlined syntax as in Java. Moreover, both languages generate the same bytecode. So we shouldn't fear about a potential performance decrease when using Java classes in Scala applications.

Share on:

Share, like or comment this post on Twitter: