Decompiling Traits with Scala 2.11 and 2.12

Introduction

When working with Scala, one quickly gets familiar with the notation of Traits
. Traits enable powerful language features such as Subtype Polymorphism
and Mixins
.

Scala 2.12 (currently at preview stage M4) takes advantage of Java 8 Default Methods
to generate optimized JVM byte code for traits. We’ll take a short detour of how Scala 2.11 generates byte code for traits and then look at Scala 2.12 , both compiled with Java 8.

Compiling traits with Scala 2.11

Traits in Scala allow us to provide a default implementation to interface methods:

trait X {
  def helloWorld: String = "hello world"
}

Using Scala on the JVM, the compiler needs to work around the fact that Java <= v7 doesn’t allow default implementations on interfaces, which is the relative cousin of traits. Lets see what the Scala compiler does to work around that. We’ll compile trait X
and then look at the generated byte code:

[[email protected] yuvie]# scalac X.scala
[[email protected] yuvie]# cd yuvie/
[[email protected] yuvie]# ll
total 12
-rw-r--r--. 1 root   root   448 Jun 24 17:58 X.class
-rw-r--r--. 1 root   root   416 Jun 24 17:58 X$class.class

Scala generated two class files for our X
trait. One called X.class
, and one called X$class.class
. Let’s look at what each of these contain:

[root@localhost yuvie]# javap -c -p X
Warning: Binary file X contains yuvie.X
Compiled from "X.scala"
public interface yuvie.X {
  public abstract java.lang.String helloWorld();
}

[root@localhost yuvie]# javap -c -p X$class.class 
Compiled from "X.scala"
public abstract class yuvie.X$class {
  public static java.lang.String helloWorld(yuvie.X);
    Code:
       0: ldc           #9                  // String hello world
       2: areturn

  public static void $init$(yuvie.X);
    Code:
       0: return
}

Looking at the code we can see that Scalas compiler generates:

  1. An interface called X
    , matching the trait declaration. This interface has a single method called helloWorld
    without the implementation
    .
  2. An abstract class called X$class
    with a static
    helloWorld
    method, which provides the implementation.

Lets see what happens when we extend X
with some class M
:

class M extends X

Bytecode:

[root@localhost yuvie]# javap -c -p M.class 
Compiled from "M.scala"
public class yuvie.M implements yuvie.X {
  public java.lang.String helloWorld();
    Code:
       0: aload_0
       1: invokestatic  #17                 // Method yuvie/X$class.helloWorld:(Lyuvie/X;)Ljava/lang/String;
       4: areturn

  public yuvie.M();
    Code:
       0: aload_0
       1: invokespecial #23                 // Method java/lang/Object."":()V
       4: aload_0
       5: invokestatic  #27                 // Method yuvie/X$class.$init$:(Lyuvie/X;)V
       8: return
}

When we extend / mixin X
and want to invoke helloWorld
, a call to the abstract class X$class
is made which provides the implementation for helloWorld
. This duo of interface and abstract class allows Scala to generate valid byte code.

Leveraging Default Methods with Scala 2.12

We just saw how Scala 2.11 deals with compiling traits, let’s see how Scala 2.12 leverages default methods. Taking the same code and compiling it again only now with Scala 2.12-M4 yields the following:

[root@localhost test-project]# cd target/scala-2.12.0-M4/classes/yuvie/
[root@localhost yuvie]# ll
total 16
-rw-rw-r--. 1 yuvali yuvali  680 Jun 24 18:17 X.class

[root@localhost yuvie]# javap -c -p X.class
Compiled from "X.scala"
public interface yuvie.X {
  public java.lang.String helloWorld();
    Code:
       0: ldc           #12                 // String hello world
       2: areturn

  public void $init$();
    Code:
       0: return
}

The compiler generates a single
interface
called X
which has the default implementation of the trait. Let’s see what happens now when we extend M
with X
:

[root@localhost yuvie]# javap -c -p M.class 
Compiled from "M.scala"
public class yuvie.M implements yuvie.X {
  public java.lang.String helloWorld();
    Code:
       0: aload_0
       1: invokespecial #14                 // Method yuvie/X.helloWorld:()Ljava/lang/String;
       4: areturn

  public yuvie.M();
    Code:
       0: aload_0
       1: invokespecial #20                 // Method java/lang/Object."":()V
       4: aload_0
       5: invokespecial #23                 // Method yuvie/X.$init$:()V
       8: return
}

The compiler now generates an invokespecial
instruction for the instance method
created in X
, instead of invokestatic
previously for the static method
created inside X$class.class
.

Conclusion

Thanks to Java 8 and Default Method, Scala 2.12 is now able to emit less byte code for traits. My example was very simplified, and in real case scenarios this should save decent amount of bytecode generation for the Scala compiler and enables Scala to be more aligned with Java without needing to jump the hoops to make traits work.

稿源:Asyncified (源链) | 关于 | 阅读提示

本站遵循[CC BY-NC-SA 4.0]。如您有版权、意见投诉等问题,请通过eMail联系我们处理。
酷辣虫 » 综合编程 » Decompiling Traits with Scala 2.11 and 2.12

喜欢 (0)or分享给?

专业 x 专注 x 聚合 x 分享 CC BY-NC-SA 4.0

使用声明 | 英豪名录