[Java] Introducing Scala from the Java perspective to understand by decompiling (Basic)

6 minute read

This article is “Innovation Promotion Community” in the in-house technical community of Fujitsu Systems Web Technology. This is the 14th day of Inobeko Summer Vacation Advent Calendar 2020, planned by “Inobeko” for short. The content of this article is my own opinion and is not representative of the organization to which it belongs. Promise up to here :wink:

Introduction

In this article, a Java programmer who started Scala decompiles the class file generated by compiling Scala, It started when I wanted to understand Scala from a Java perspective.

The basic flow is as follows.

  • Tracing the scala code while watching “TOUR OF SCALA” of docs.scala-lang.org/ja
  • Decompile class files using Java Decompiler called jd-gui
  • Look at the decompiled source code and add a comment if it is not

That’s it.

Now let’s get a little closer to the world of Scala. This time, I would like to touch the world of Scala by using the “Basic” edition from TOUR OF SCALA. (There are some development issues)

class

Classes in scala look almost exactly like Java, but with different constructor parameters. Also, let’s check how to implement overload based on a sample.

Sample code

class Greeter(prefix: String, suffix: String) {
  def greet(name: String): Unit =
    println(prefix + name + suffix)
}

After decompilation

package com.github.fishibashi.scaladecompile;

import scala.Predef$;
import scala.reflect.ScalaSignature;

@ScalaSignature(bytes = "\006\005A2A!\002\004\001\037!Aa\003\001B\001B\003%q\003\003\005#\001\t\005\t\025!\ 003\030\021\025\031\003\001\"\001%\021\025I\003\001\"\001+\005\0359%/Z3uKJT!a\002\005\002\035M\034 \027\r\\1eK\016|W\016]5mK*\021\021BC\001\013M&\034\b.\0332bg\"L'BA\006\r\003\0319\027\016\0365vE *\tQ\"A\002d_6\034\001a\005\002\001!A\021\021\003F\007\002%)\t1#A\003tG\006d\027-\003\002\026% \t1\021I\\=SK\032\fa\001\035:fM&D\bC\001\r \035\tIR\004\005\002\033%5\t1D\003\002\035\035\0051AH ]8pizJ!A\b\n\002\rA\023X\rZ3g\023\t\001\023E\001\004TiJLgn\032\006\003=I\taa];gM&D\030A\002\037j]&$ h\bF\002&O!\002\"A\n\001\016\003\031AQAF\002A\002]AQAI\002A\002]\tQa\032:fKR$\"a\013\030\021\005Ea \023BA\027\023\005\021)f.\033;\t\013=\"\001\031A\f\002\t9\fW.\032")
public class Greeter {
  private final String prefix;
  
  private final String suffix;
  
  public Greeter(String prefix, String suffix) {}
  
  public void greet(String name) {
    Predef$.MODULE$.println((new StringBuilder(0)).append(this.prefix).append(name).append(this.suffix).toString());
  }
}

ScalaSignature

It seems to store the meta information when it was compiled. There was an article on qiita that confirmed how to implement sealed. https://qiita.com/ocadaruma/items/93818bd1a5318e71f0d8

I don’t know if it’s something I want to know, but it’s for that. .. ..

Constructor arguments

The arguments prefix: String, suffix: String are defined as private final fields. *It may be final because the argument of the constructor is val (cannot be changed) by default.

For example, if you try to write a constructor parameter as a var parameter, it will be as follows.

class Greeter(var prefix: String, var suffix: String) {
  def greet(name: String): Unit =
    println(prefix + name + suffix)
}
public class Greeter {
  private String prefix;
  
  private String suffix;
  
  public String prefix() {
    return this.prefix;
  }
  
  public void prefix_$eq(String x$1) {
    this.prefix = x$1;
  }
  
  public String suffix() {
    return this.suffix;
  }
  
  public void suffix_$eq(String x$1) {
    this.suffix = x$1;
  }
  
  public Greeter(String prefix, String suffix) {}
  
  public void greet(String name) {
    Predef$.MODULE$.println((new StringBuilder(0)).append(prefix()).append(name).append(suffix()).toString());
  }
}

You can see that final is gone and getter methods (prefix(), suffix()) and setter methods ($eq) are generated. When calling it from scala code, you can get or set it with prefix normally, but when calling from Java code, it is likely to be field name_$eq. It’s complicated…

greet method

In the greet method, strings were concatenated with the + method (in scala, + is also treated as a method!). However, when I look at the result of decompiling, I am using StringBuilder to concatenate the strings. What! Smart! If you are using Java code to combine and process strings, **It is sure to be written in the review table as “Please use StringBuilder :angry: “, but in Scala, that Don’t worry.

The result of rewriting in Java and decompiling is as follows.

JavaGreeeter.java


// precompiled code
//public class JavaGreeter {
// private final String prefix;
//
// private final String suffix;
//
// public JavaGreeter(String prefix, String suffix) {
// this.prefix = prefix;
// this.suffix = suffix;
//}
//
// public String greet(String name) {
// return prefix + name + suffix;
//}
//}

import scala.Predef$;

public class JavaGreeter {
  private final String prefix;
  
  private final String suffix;
  
  public JavaGreeter(String prefix, String suffix) {
    this.prefix = prefix;
    this.suffix = suffix;
  }
  
  public void greet(String name) {
    Predef$.MODULE$.println(this.prefix + this.prefix + name);
  }
}

It seems that it is designed to simply combine strings. Perhaps all operators are treated as functions in Scala. I suspect that the + method of String itself is probably a movement to use StringBuilder (maybe scalac interprets it with StringBuilder…).

In addition, even if you use a literal, it seems to use StringBuilder in the same way.

  def greet(name: String): Unit =
    println(s"${prefix} ${name} ${suffix}")
  /**
    public void greet(String name) {
      Predef$.MODULE$.println((new StringBuilder(2)).append(prefix()).append(" ").append(name).append(" ").append(suffix()).toString() );
    }
   */

object

Next is the object. Not a Java java.lang.Object. Does Scala’s object correspond to the Singleton pattern in Java? The syntax is as follows.

Sample code

object IdFactory {
  private var counter = 0def create(): Int = {
    counter += 1
    counter
  }
}

デコンパイル後

``

IdFactory.class
@ScalaSignature(bytes = "\006\0051:Qa\002\005\t\002E1Qa\005\005\t\002QAQaG\001\005\002qAq!H\001A\002\023%a\004C\004#\003\001\007I\021B\022\t\r%\n\001\025)\003 \021\025Q\023\001\"\001,\003%IEMR1di>\024\030P\003\002\n\025\005q1oY1mC\022,7m\\7qS2,'BA\006\r\003)1\027n\0355jE\006\034\b.\033\006\003\0339\taaZ5uQV\024'\"A\b\002\007\r|Wn\001\001\021\005I\tQ\"\001\005\003\023%#g)Y2u_JL8CA\001\026!\t1\022$D\001\030\025\005A\022!B:dC2\f\027B\001\016\030\005\031\te.\037*fM\0061A(\0338jiz\"\022!E\001\bG>,h\016^3s+\005y\002C\001\f!\023\t\tsCA\002J]R\f1bY8v]R,'o\030\023fcR\021Ae\n\t\003-\025J!AJ\f\003\tUs\027\016\036\005\bQ\021\t\t\0211\001 \003\rAH%M\001\tG>,h\016^3sA\00511M]3bi\026$\022a\b")
public final class IdFactory {
  public static int create() {
    return IdFactory$.MODULE$.create();
  }
}

``

IdFactory$.class
public final class IdFactory$ {
  public static final IdFactory$ MODULE$ = new IdFactory$();
  
  private static int counter = 0;
  
  private int counter() {
    return counter;
  }
  
  private void counter_$eq(int x$1) {
    counter = x$1;
  }
  
  public int create() {
    counter_$eq(counter() + 1);
    return counter();
  }
}

なんと!匿名クラスが出来上がりました。

おそらくですが、SingletonオブジェクトであるIdFactory$と、それを呼び出すIdFactoryクラスという構成です。分離されてしまう理由があるのでしょうか。勉強を進めていくうえで何かあると思って今回はこれで終わりです。 公式ドキュメントにも、後で詳しく取り扱いますとあるので、期待です

trait

traitとは何ぞや。 interfaceのようなものなんですが、フィールドも持ててデフォルト実装もできて、はたまた複数のトレイトを継承(mixin)することができるらしいです。

ここでは、traitの作成と、継承したクラスを作成しどのようなクラスが作成されるのかを見てみましょう。

サンプルコード

trait Greeter {
  def greet(name: String): Unit =
    println("Hello, " + name + "!")
}

class DefaultGreeter extends Greeter

class CustomizableGreeter(prefix: String, postfix: String) extends Greeter {
  override def greet(name: String): Unit = {
    println(prefix + name + postfix)
  }
}

デコンパイル後

今回は全部で3つのクラスファイルが出来上がりました。 Javaの場合は、1つのjavaソースコードに複数のpublicクラスを作ることができませんので、複数に分割されました。

Greeter.java


@ScalaSignature(bytes = "\006\005!2qa\001\003\021\002\007\005Q\002C\003\025\001\021\005Q\003C\003\032\001\021\005!DA\004He\026,G/\032:\013\005\0251\021AD:dC2\fG-Z2p[BLG.\032\006\003\017!\t!BZ5tQ&\024\027m\0355j\025\tI!\"\001\004hSRDWO\031\006\002\027\005\0311m\\7\004\001M\021\001A\004\t\003\037Ii\021\001\005\006\002#\005)1oY1mC&\0211\003\005\002\007\003:L(+\0324\002\r\021Jg.\033;%)\0051\002CA\b\030\023\tA\002C\001\003V]&$\030!B4sK\026$HC\001\f\034\021\025a\"\0011\001\036\003\021q\027-\\3\021\005y)cBA\020$!\t\001\003#D\001\"\025\t\021C\"\001\004=e>|GOP\005\003IA\ta\001\025:fI\0264\027B\001\024(\005\031\031FO]5oO*\021A\005\005")
public interface Greeter {
  static void $init$(Greeter $this) {}
  
  default void greet(String name) {
    Predef$.MODULE$.println((new StringBuilder(8)).append("Hello, ").append(name).append("!").toString());
  }
}

traitはinterfaceになりました

``

DefaultGreeter.java
@ScalaSignature(bytes = "\006\005i1AAA\002\001\031!)q\003\001C\0011\tqA)\0324bk2$xI]3fi\026\024(B\001\003\006\0039\0318-\0317bI\026\034w.\0349jY\026T!AB\004\002\025\031L7\017[5cCND\027N\003\002\t\023\0051q-\033;ik\nT\021AC\001\004G>l7\001A\n\004\0015\031\002C\001\b\022\033\005y!\"\001\t\002\013M\034\027\r\\1\n\005Iy!AB!osJ+g\r\005\002\025+5\t1!\003\002\027\007\t9qI]3fi\026\024\030A\002\037j]&$h\bF\001\032!\t!\002\001")
public class DefaultGreeter implements Greeter {
  public void greet(String name) {
    Greeter.greet$(this, name);
  }
  
  public DefaultGreeter() {
    Greeter.$init$(this);
  }
}

インタフェースのデフォルト実装を呼び出す形になっています。

``


@ScalaSignature(bytes = "\006\005M2A!\002\004\001\037!A!\004\001B\001B\003%1\004\003\005'\001\t\005\t\025!\003\034\021\0259\003\001\"\001)\021\025a\003\001\"\021.\005M\031Uo\035;p[&T\030M\0317f\017J,W\r^3s\025\t9\001\"\001\btG\006d\027\rZ3d_6\004\030\016\\3\013\005%Q\021A\0034jg\"L'-Y:iS*\0211\002D\001\007O&$\b.\0362\013\0035\t1aY8n\007\001\0312\001\001\t\027!\t\tB#D\001\023\025\005\031\022!B:dC2\f\027BA\013\023\005\031\te.\037*fMB\021q\003G\007\002\r%\021\021D\002\002\b\017J,W\r^3s\003\031\001(/\0324jqB\021Ad\t\b\003;\005\002\"A\b\n\016\003}Q!\001\t\b\002\rq\022xn\034;?\023\t\021##\001\004Qe\026$WMZ\005\003I\025\022aa\025;sS:<'B\001\022\023\003\035\001xn\035;gSb\fa\001P5oSRtDcA\025+WA\021q\003\001\005\0065\r\001\ra\007\005\006M\r\001\raG\001\006OJ,W\r\036\013\003]E\002\"!E\030\n\005A\022\"\001B+oSRDQA\r\003A\002m\tAA\\1nK\002")
public class CustomizableGreeter implements Greeter {
  private final String prefix;
  
  private final String postfix;
  
  public CustomizableGreeter(String prefix, String postfix) {
    Greeter.$init$(this);
  }
  
  public void greet(String name) {
    Predef$.MODULE$.println((new StringBuilder(0)).append(this.prefix).append(name).append(this.postfix).toString());
  }
}

implementsした実装をoverrideしてるだけのようです。まぁまぁ想定内ですね。

mixin

Scalaではmixinを用いて複数のクラスを合成することができます。Java cannot inherit multiple classes or interfaces, it can inherit a single parent class.

Sample code using mixin

Mixin.scala


abstract class BaseMixinSample(val prefix: String, val suffix: String) {
  def greet(name: String): Unit = println(prefix + name + suffix)
}

trait TraitA {
  val a: String
  def A(): Unit = println("A")
}

trait TraitB {
  val b: String
  def B(): Unit = println("B")
}

class DefaultMixinSample() extends BaseMixinSample("Hello", "!") with TraitA with TraitB {
  override def greet(name: String): Unit = super.greet(name)

  override val a: String = "Value is A"
  override val b: String = "Value is B"
}

After decompilation


import scala.Predef$;
import scala.reflect.ScalaSignature;

@ScalaSignature(bytes = "\006\005U2Qa\002\005\002\002EA\001\002\007\001\003\006\004%\t!\007\005\tK\001\021\t\021 )A\0055!Aa\005\001BC\002\023\005\021\004\003\005(\001\t\005\t\025!\003\033\021\025A\003\001\" \001*\021\025q\003\001\"\0010\005=\021\025m]3NSbLgnU1na2,'BA\005\013\0039\0318-\0317bI\026\034w.\0349jY\026T!a\ 003\007\002\025\031L7\017[5cCND\027N\003\002\016\035\0051q-\033;ik\nT\021aD\001\004G>l7\001A\n\003\001I\001 \"a\005\f\016\003QQ\021!F\001\006g\016\fG.Y\005\003/Q\021a!\0218z%\0264\027A\0029sK\032L\0070F\001\ 033!\tY\"E\004\002\035AA\021Q\004F\007\002=)\021q\004E\001\007yI|w\016\036 \n\005\005\"\022A\002) sK\022,g-\003\002$I\t11\013\036:j]\036T!!\t\013\002\017A\024XMZ5yA\00511/\0364gSb\fqa];gM&D\b%\001 \004=S:LGO\020\013\004U1j\003CA\026\001\033\005A\001\"\002\r\006\001\004Q\002\"\002\024\006\001\004Q \022!B4sK\026$HC\001\0314!\t\031\022'\003\0023)\t!QK\\5u\021\025!d\0011\001\033\003\021q\027 -\\3")
public abstract class BaseMixinSample {
  private final String prefix;
  
  private final String suffix;
  
  public String prefix() {
    return this.prefix;
  }
  
  public String suffix() {
    return this.suffix;
  }
  
  public BaseMixinSample(String prefix, String suffix) {}
  
  public void greet(String name) {
    Predef$.MODULE$.println((new StringBuilder(0)).append(prefix()).append(name).append(suffix()).toString());
  }
}

public class DefaultMixinSample extends BaseMixinSample implements TraitA, TraitB {
  public void B() {
    TraitB.B$(this);
  }
  
  public void A() {
    TraitA.A$(this);
  }
  
  public DefaultMixinSample() {
    super("Hello", "!");
    TraitA.$init$(this);
    TraitB.$init$(this);
  }
  
  public void greet(String name) {
    super.greet(name);
  }
  
  public static void test() {
    DefaultMixinSample$.MODULE$.test();
  }
}

public interface TraitA {
  static void $init$(TraitA $this) {}
  
  default void A() {
    Predef$.MODULE$.println("A");
  }
  
  String a();
}

public interface TraitB {
  static void $init$(TraitB $this) {}
  
  default void B() {
    Predef$.MODULE$.println("B");
  }
  
  String b();
}

Apparently, defining a field in a trait implicitly creates a method in the interface that is equivalent to the field name. Then, it seems that the trait is realized by defining the actual condition of the method to get the field in the composition destination class.

Finally

The purpose of this article was to understand how Scala works, but Java programmers may find Scala a bit nicer! I thought that it would be a good opportunity for me to think. It’s a language with a long history, but I think it’s a very attractive language because it has many new features and features that Java programmers want to see.

In the future as well, I would like to create an opportunity to revisit Scala from the Java perspective as we continue learning.