逆コンパイルして理解する、Java視点からのScala入門（Class～Tuple編）

この記事は[株式会社富士通システムズウェブテクノロジー](https://www.fujitsu.com/jp/group/fsweb/)の社内技術コミュニティで、「イノベーション推進コミュニティ」
略して「いのべこ」が企画する、[いのべこ Advent Calendar 2020](https://qiita.com/advent-calendar/2020/fsweb)の16日目の記事です。
本記事の掲載内容は私自身の見解であり、所属する組織を代表するものではありません。

ここまでお約束 :wink:

# はじめに

この記事は、Scalaを始めたJavaプログラマが、Scalaをコンパイルして生成されたclassファイルを逆コンパイルし、
Java的な観点からScalaを理解したいなと思いついて[始めたもの](https://qiita.com/ishibashi-futoshi/items/ee4eb55e1163a8f15356)です。

今回は、Class～Tuple編の内容を主に振り返っていきたいと思います。

# Class

<details><summary>何の面白みもない基本的なクラス</summary><div>

```scala:User.scala
class User
```

```java:User.java

import scala.reflect.ScalaSignature;

@ScalaSignature(bytes = "\006\005]1AAA\002\001\031!)1\003\001C\001)\t!Qk]3s\025\t!Q!\001\btG\006d\027\rZ3d_6\004\030\016\\3\013\005\0319\021A\0034jg\"L'-Y:iS*\021\001\"C\001\007O&$\b.\0362\013\003)\t1aY8n\007\001\031\"\001A\007\021\0059\tR\"A\b\013\003A\tQa]2bY\006L!AE\b\003\r\005s\027PU3g\003\031a\024N\\5u}Q\tQ\003\005\002\027\0015\t1\001")
public class User {}
```

特にいうことはない・・・・

</div></details>

<details><summary>何の面白みもない基本クラス２</summary><div>

```scala:Point.scala
class Point(var x: Int, var y: Int) {

  def move(dx: Int, dy: Int): Unit = {
    x = x + dx
    y = y + dy
  }

  override def toString: String =
    s"($x, $y)"
}
```

```java:Point.java
@ScalaSignature(bytes = "\006\005\0313AAC\006\001)!A1\004\001BA\002\023\005A\004\003\005!\001\t\005\r\021\"\001\"\021!9\003A!A!B\023i\002\002\003\025\001\005\003\007I\021\001\017\t\021%\002!\0211A\005\002)B\001\002\f\001\003\002\003\006K!\b\005\006[\001!\tA\f\005\006g\001!\t\001\016\005\006s\001!\tE\017\002\006!>Lg\016\036\006\003\0315\tab]2bY\006$WmY8na&dWM\003\002\017\037\005Qa-[:iS\n\f7\017[5\013\005A\t\022AB4ji\",(MC\001\023\003\r\031w.\\\002\001'\t\001Q\003\005\002\02735\tqCC\001\031\003\025\0318-\0317b\023\tQrC\001\004B]f\024VMZ\001\002qV\tQ\004\005\002\027=%\021qd\006\002\004\023:$\030!\002=`I\025\fHC\001\022&!\t12%\003\002%/\t!QK\\5u\021\0351#!!AA\002u\t1\001\037\0232\003\tA\b%A\001z\003\025Ix\fJ3r)\t\0213\006C\004'\013\005\005\t\031A\017\002\005e\004\023A\002\037j]&$h\bF\0020cI\002\"\001\r\001\016\003-AQaG\004A\002uAQ\001K\004A\002u\tA!\\8wKR\031!%N\034\t\013YB\001\031A\017\002\005\021D\b\"\002\035\t\001\004i\022A\0013z\003!!xn\025;sS:<G#A\036\021\005q\032eBA\037B!\tqt#D\001@\025\t\0015#\001\004=e>|GOP\005\003\005^\ta\001\025:fI\0264\027B\001#F\005\031\031FO]5oO*\021!i\006")
public class Point {
  private int x;
  
  private int y;
  
  public int x() {
    return this.x;
  }
  
  public void x_$eq(int x$1) {
    this.x = x$1;
  }
  
  public int y() {
    return this.y;
  }
  
  public void y_$eq(int x$1) {
    this.y = x$1;
  }
  
  public Point(int x, int y) {}
  
  public void move(int dx, int dy) {
    x_$eq(x() + dx);
    y_$eq(y() + dy);
  }
  
  public String toString() {
    return (new StringBuilder(4)).append("(").append(x()).append(", ").append(y()).append(")").toString();
  }
}
```

、getter/setter、各プロパティに対する_$eqメソッドが生えてます。
toStringメソッドのoverrideキーワードは消え去ってますね。

</div></details>

## デフォルト引数を持つコンストラクタ

```scala:Point.scala
class Point(var x: Int = 0, var y: Int = 0)
```

```java:Point.java
@ScalaSignature(bytes = "\006\005\0353A!\004\b\001/!Aa\004\001BA\002\023\005q\004\003\005$\001\t\005\r\021\"\001%\021!Q\003A!A!B\023\001\003\002C\026\001\005\003\007I\021A\020\t\0211\002!\0211A\005\0025B\001b\f\001\003\002\003\006K\001\t\005\006a\001!\t!M\004\bm9\t\t\021#\0018\r\035ia\"!A\t\002aBQ\001M\005\005\002eBqAO\005\022\002\023\0051\bC\004G\023E\005I\021A\036\003\013A{\027N\034;\013\005=\001\022AD:dC2\fG-Z2p[BLG.\032\006\003#I\t!BZ5tQ&\024\027m\0355j\025\t\031B#\001\004hSRDWO\031\006\002+\005\0311m\\7\004\001M\021\001\001\007\t\0033qi\021A\007\006\0027\005)1oY1mC&\021QD\007\002\007\003:L(+\0324\002\003a,\022\001\t\t\0033\005J!A\t\016\003\007%sG/A\003y?\022*\027\017\006\002&QA\021\021DJ\005\003Oi\021A!\0268ji\"9\021FAA\001\002\004\001\023a\001=%c\005\021\001\020I\001\002s\006)\021p\030\023fcR\021QE\f\005\bS\025\t\t\0211\001!\003\tI\b%\001\004=S:LGO\020\013\004eQ*\004CA\032\001\033\005q\001b\002\020\b!\003\005\r\001\t\005\bW\035\001\n\0211\001!\003\025\001v.\0338u!\t\031\024b\005\002\n1Q\tq'A\016%Y\026\0348/\0338ji\022:'/Z1uKJ$C-\0324bk2$H%M\013\002y)\022\001%P\026\002}A\021q\bR\007\002\001*\021\021IQ\001\nk:\034\007.Z2lK\022T!a\021\016\002\025\005tgn\034;bi&|g.\003\002F\001\n\tRO\\2iK\016\\W\r\032,be&\fgnY3\0027\021bWm]:j]&$He\032:fCR,'\017\n3fM\006,H\016\036\0233\001")
public class Point {
  private int x;
  
  private int y;
  
  public static int $lessinit$greater$default$2() {
    return Point$.MODULE$.$lessinit$greater$default$2();
  }
  
  public static int $lessinit$greater$default$1() {
    return Point$.MODULE$.$lessinit$greater$default$1();
  }
  
  public int x() {
    return this.x;
  }
  
  public void x_$eq(int x$1) {
    this.x = x$1;
  }
  
  public int y() {
    return this.y;
  }
  
  public void y_$eq(int x$1) {
    this.y = x$1;
  }
  
  public Point(int x, int y) {}
}
```

```java:Point$.java

public final class Point$ {
  public static final Point$ MODULE$ = new Point$();
  
  public int $lessinit$greater$default$1() {
    return 0;
  }
  
  public int $lessinit$greater$default$2() {
    return 0;
  }
}
```

なんと、PointクラスとPoint\$クラスに分離してしまいました。
デフォルト値のほうは、Point\$クラスのstaticメソッドとして実装されています（\$lessinit\$greater\$default\$?から呼ばないのはなぜなのだろうか・・・）

Javaにはデフォルト引数の概念はなく、オーバーロードやBuilderパターンなどを用いて実装されることが多いです。

にしても、staticな定数を呼び出しているメソッドと値を分離するメリットもよくわからないし、
\$lessinit\$greater\$default\$?はいつ呼び出されているのだろうか・・・

試しにjshellに上記のjavaコードを張り付けて実行してみたところ、以下のようなエラーになったのでScalaの[Runtime Reflection](https://docs.scala-lang.org/ja/overviews/reflection/overview.html)が何らかの解決をしてくれているのだろうな・・・となった。

```
var point = new Point()
|  エラー:
|  クラス Pointのコンストラクタ Pointは指定された型に適用できません。
|    期待値: int,int
|    検出値: 引数がありません
|    理由: 実引数リストと仮引数リストの長さが異なります
|  var point = new Point();
|              ^---------^
```

ちょうど次の章でデフォルト引数に触れる予定だったらしく、「Scalaで定義したデフォルトパラメータはJavaのコードから呼び出される時はオプショナルではありません。」と書かれていた。

# 名前付き引数

メソッドを呼び出すとき、引数に名前を付けることができます。

```scala
  def printName(first: String, last: String): Unit = {
    println(first + " " + last)
  }

  // 呼び出し側
  printName("John", "Smith")  // ① Prints "John Smith" 
  printName(first = "John", last = "Smith")  // ② Prints "John Smith"
  printName(last = "Smith", first = "John")  // ③ Prints "John Smith"
```

```scala
  public void printName(String first, String last) {
    Predef$.MODULE$.println((new StringBuilder(1)).append(first).append(" ").append(last).toString());
  }

  // 呼び出し側
  printName("John", "Smith"); // ①
  printName("John", "Smith"); // ②
  String x$1 = "Smith", x$2 = "John";
  printName("John", "Smith"); // ③
```

なんだかモヤっとする結果になりましたね・・・

Javaには名前付き引数がないので、①, ②の結果については想定通りです。
しかしながらなぜx$1, x$2の引数を用意しておきながら、printNameに代入するときには使用していないのか不可解です。
おそらくJVMの最適化の影響なのかなと思ってますが。。。

とりあえず、一度変数化⇒代入のようにコンパイルされるのかな？という知見が得られたことにします。。。

# Tuple

```scala:TupleSample.java
class TupleSample {
  def getIngredient: (String, Int) = {
    ("Sugar", 25)
  }

  def useIngredient(): Unit = {
    val ingredient = getIngredient;
    println(ingredient._1)
    println(ingredient._2)
  }
}
```

```java:TupleSample.java
import scala.Predef$;
import scala.Tuple2;
import scala.reflect.ScalaSignature;
import scala.runtime.BoxesRunTime;

@ScalaSignature(bytes = "\006\005E2A\001B\003\001\035!)Q\003\001C\001-!)\021\004\001C\0015!)A\006\001C\001[\tYA+\0369mKN\013W\016\0357f\025\t1q!\001\btG\006d\027\rZ3d_6\004\030\016\\3\013\005!I\021A\0034jg\"L'-Y:iS*\021!bC\001\007O&$\b.\0362\013\0031\t1aY8n\007\001\031\"\001A\b\021\005A\031R\"A\t\013\003I\tQa]2bY\006L!\001F\t\003\r\005s\027PU3g\003\031a\024N\\5u}Q\tq\003\005\002\031\0015\tQ!A\007hKRLen\032:fI&,g\016^\013\0027A!\001\003\b\020*\023\ti\022C\001\004UkBdWM\r\t\003?\031r!\001\t\023\021\005\005\nR\"\001\022\013\005\rj\021A\002\037s_>$h(\003\002&#\0051\001K]3eK\032L!a\n\025\003\rM#(/\0338h\025\t)\023\003\005\002\021U%\0211&\005\002\004\023:$\030!D;tK&swM]3eS\026tG\017F\001/!\t\001r&\003\0021#\t!QK\\5u\001")
public class TupleSample {
  public Tuple2<String, Object> getIngredient() {
    return new Tuple2("Sugar", BoxesRunTime.boxToInteger(25));
  }
  
  public void useIngredient() {
    Tuple2<String, Object> ingredient = getIngredient();
    Predef$.MODULE$.println(ingredient._1());
    Predef$.MODULE$.println(BoxesRunTime.boxToInteger(ingredient._2$mcI$sp()));
  }
}
```

ここで注目すべきは、`scala.TupleN`と`scala.runtime.BoxesRunTime`でしょうか。。。

## scala.TupleN

scala.TupleNはN = 2~22まで定義されたScalaの組み込みTuple型です。

## scala.runtime.BoxesRunTime

scalaのコードでTupleの2番目の要素に指定されている`Int`はScalaの組み込み型である`scala.Int`です。
Stringは`scala.Predef`にaliasとして`java.lang.String`がStringとして定義されているため、実質ただのStringです。

そこで、JVMの世界で利用するために、オートボクシング（？）と言っていいのかはわかりませんが、
Java側の型に戻してあげる処理をBoxexRunTimeが行っているように見えます。

```BoxesRunTime.java
    public static java.lang.Integer boxToInteger(int i) {
        return java.lang.Integer.valueOf(i);
    }
```

## タプルでのパターンマッチング

パターンマッチングで要素を分解できます。

```scala
    val (name, quantity) = getIngredient
    println(name)
    println(quantity)
```

```java
    Tuple2 tuple21;
    Tuple2<String, Object> tuple2 = getIngredient();
    if (tuple2 != null) {
      String str = (String)tuple2._1();
      int i = tuple2._2$mcI$sp();
      tuple21 = new Tuple2(str, BoxesRunTime.boxToInteger(i));
    } else {
      throw new MatchError(tuple2);
    } 
    Tuple2 tuple22 = tuple21;
    String name = (String)tuple22._1();
    int quantity = tuple22._2$mcI$sp();
    Predef$.MODULE$.println(name);
    Predef$.MODULE$.println(BoxesRunTime.boxToInteger(quantity));
```

なんだかたった3行だったコードが超面倒なことになりましたね。。。
ポイントはこの辺ですよね

```java
    if (tuple2 != null) {                                         // tupleがnullかどうかチェック
      String str = (String)tuple2._1();                           
      int i = tuple2._2$mcI$sp();
      tuple21 = new Tuple2(str, BoxesRunTime.boxToInteger(i));    // なぜかここで再度Tupleを作成
    } else {
      throw new MatchError(tuple2);                               // まぁnullだったらエラーをthrowするのはわかる
    } 
    Tuple2 tuple22 = tuple21;                                     // なぜかここで新しいTupleに代入
    String name = (String)tuple22._1();
    int quantity = tuple22._2$mcI$sp();
```

黙ってこんなコードではダメなのか・・・

```java
try {
  String name = (String)tuple2._1();
  int quantity = tuple2._2$mcI$sp();
} catch (XXXException e) { // ClassCastExceptionとか
  throw new MatchError(tuple2);
}
```

上記のデコンパイル結果のコードのほうが良い理由が知りたい。

## for内包表記

for内包表記はscalaではよく使う思います

```scala
val numPairs = List((2, 5), (3, -7), (20, 56))
for ((a, b) <- numPairs) {
  println(a * b)
}
```

```java
    List numPairs = (List)new .colon.colon(new Tuple2.mcII.sp(2, 5), (List)new .colon.colon(new Tuple2.mcII.sp(3, -7), (List)new .colon.colon(new Tuple2.mcII.sp(20, 56), (List)Nil$.MODULE$)));
    numPairs.withFilter(TupleSample::$anonfun$forInclusion$1$adapted).foreach(TupleSample::$anonfun$forInclusion$2$adapted);
```

これまたすごいコードになってしまいましたね。。。
調べてみたところこんな記事が。。。
https://qtamaki.hatenablog.com/entry/20120125/1327500931

見たところ、内部クラスとかにforの中身を展開して云々のようだけど、gradleのビルド結果を見る限りそのような内部クラスは見つからなかったし、jarにも含まれていなかった。。。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280383/2cd886c5-1cfa-f408-5d55-6b64c628cd3b.png)

まず、scalaの`for`はmap/flatMap/withFilterのシンタックスシュガーであるというのは有名な話ですが、
numPairs.withFilterで処理している部分、まずここいらなくね？と思うのだが、numPairsに`(List)Nil$.MODULE$`が含まれていたりするし、変数a,bの作成などをしているのではないかと思うので、ここでおそらくfilterをかましている？
で、`$anonfun$forInclusion$2$adapted`がおそらくforの中に書いていたprintlnの部分か？

なんにせよここまで見ただけではscalaのことがわかったような気になるだけで何もわかってないので、よく調査する必要がある気がする。

# 最後に

今回はClassとTupleについての記事でしたが、デコンパイルしただけではわからないことが多く、
もう少し深堀が必要な内容となってしまいました。

次回の記事（いつになるかは未定）では、以降のTOUR OF SCALAをやっていくのもいいですが、
Scalaのコンパイラ・ランタイムが何をしてくれているのかについて学んでいこうかなと思っています。

~~ちょっと中身が薄い気がするけど。~~