Your browser doesn't support the features required by impress.js, so you are presented with a simplified version of this presentation.

For the best experience please use the latest Chrome, Safari or Firefox browser.

熱血!Scala入門
自己紹介
shigemk2 でググってください 某shigemk2ブログ 毎日更新 某読書会 主催 某社でシェル芸やってる
発表資料です http://shigemk2.github.io/kernelvm11/ ソースコードです http://shigemk2.github.io/scala_jvm/
今日はScala初心者がScalaのクラスファイルについて話します
各バージョン
クラスファイルはScalaやJavaでコンパイルしたあとに出来る中間ファイルのことです この本は名著 熱血!アセンブラ入門にわりとインスパイヤされたパチモンです
熱血!アセンブラ入門P3より
Scalaのクラスファイルをフィーリングなんとなく読んでみよう
Scalaのクラスファイルを読む前に、まずはJavaのクラスファイルをなんとなく読んでみる
JavaのHello, World
public class HelloWorldJava {
    public static void main(String[] args){
        System.out.println("Hello, world.");
    }
}
コンパイルと逆アセンブルのコマンド

javac -g HelloWorldJava.java
javap -v -p HelloWorldJava
      
コマンドの詳細 デバッグ情報を付与しつつコンパイル

javac -g HelloWorldJava.java
      
クラスファイルをプライベートメソッド、詳細情報つきで逆アセンブル

javap -v -p HelloWorldJava
      
逆アセンブル結果(一部)

Classfile /home/shigemk2/projects/github.com/shigemk2/scala_jvm/HelloWorldJava.class
  Last modified 2015/05/26; size 547 bytes
  MD5 checksum fe3b93e48c1c3ce255c71714786c4dcd
  Compiled from "HelloWorldJava.java"
public class HelloWorldJava
  SourceFile: "HelloWorldJava.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
      
クラスファイルのさっくりとした構成
基本的なところはJavaのクラスファイルもScalaのクラスファイルも変わりません
JavaのHello, Worldプログラムのクラスファイルを逆アセンブルしたものからざっくりとした構成を見ていきましょう
public class HelloWorldJava {
    public static void main(String[] args){
        System.out.println("Hello, world.");
    }
}
コマンド
javac -g HelloWorldJava.java
javap -v -p HelloWorldJava
ざっくりとした構成: 前置き部分

Classfile /home/shigemk2/projects/github.com/shigemk2/scala_jvm/HelloWorldJava.class
  Last modified 2015/05/26; size 547 bytes
  MD5 checksum fe3b93e48c1c3ce255c71714786c4dcd
  Compiled from "HelloWorldJava.java"
public class HelloWorldJava
  SourceFile: "HelloWorldJava.java"
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
      
ざっくりとした構成: 定数プール

Constant pool:
   #1 = Methodref          #6.#20         //  java/lang/Object."":()V
   #2 = Fieldref           #21.#22        //  java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #23            //  Hello, world.
   #4 = Methodref          #24.#25        //  java/io/PrintStream.println:
   #5 = Class              #26            //  HelloWorldJava
   #6 = Class              #27            //  java/lang/Object
   #7 = Utf8               
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LHelloWorldJava;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorldJava.java
  #20 = NameAndType        #7:#8          //  "":()V
  #21 = Class              #28            //  java/lang/System
  #22 = NameAndType        #29:#30        //  out:Ljava/io/PrintStream;
  #23 = Utf8               Hello, world.
  #24 = Class              #31            //  java/io/PrintStream
  #25 = NameAndType        #32:#33        //  println:(Ljava/lang/String;)V
  #26 = Utf8               HelloWorldJava
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
      
ざっくりとした構成: 本体
{
  public HelloWorldJava();
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1
         4: return
      
前置きとか定数プールとかは無視して、本体のところだけ読んでいきます
まずは
Hello, World
Hello, World JavaとScalaを比べてみよう
Hello,World本体(Java)

public class HelloWorldJava {
    public static void main(String[] args){
        System.out.println("Hello, world.");
    }
}
      
逆アセンブル結果

0: getstatic     #2 // クラスフィールド読み込み
3: ldc           #3 // 文字列読み込み
5: invokevirtual #4 // printlnメソッド呼び出し
8: return           // return void
      
Hello,World本体(Scala)

object HelloWorldScala {
  def main(args: Array[String]) :Unit = {
    println("hello, world")
  }
}
      
逆アセンブル結果

0: getstatic     #19 // クラスフィールド読み込み
3: ldc           #21 // 文字列読み込み
5: invokevirtual #25 // printlnメソッド呼び出し
8: return            // return Unit
      
おんなじだ!
FYI: このクラスファイルをJD-GUIでデコンパイルすると

object HelloWorldScala {
  def main(args: Array[String]) :Unit = {
    println("hello, world")
  }
}
      
逆アセンブル結果

0: getstatic     #19 // クラスフィールド読み込み
3: ldc           #21 // 文字列読み込み
5: invokevirtual #25 // printlnメソッド呼び出し
8: return            // return Unit
      
こうなる(コンパイル不可能)

import scala.Predef.;
public final class HelloWorldScala$ {
  public static final  MODULE$;
  static {
    new ();
  }
  public void main(String[] args) {
    Predef..MODULE$.println("hello, world");
  }
  private HelloWorldScala$() {
    MODULE$ = this;
  }
}
      
たぶんJD-GUIがうまくない
続きまして
加算プログラム
加算プログラム

object PlusScala {
  def main(args: Array[String]) :Unit = {
    var a = 1
    a = a + 2
  }
}
      
加算プログラムのソースコード
object PlusScala {
  def main(args: Array[String]) :Unit = {
    var a = 1
    a = a + 2
  }
}
逆アセンブル結果(var a = 1 →a = a + 2)
0: iconst_1 // 0: 定数1→スタック
1: istore_2 // 1: スタックの値→ローカル変数2
2: iload_2  // 2: ローカル変数2→スタック
3: iconst_2 // 3: 定数2→スタック
4: iadd     // 4: スタックの値を加算
5: istore_2 // 5: 加算したスタックの値→ローカル変数2
6: return   // 6: return unit
加算プログラムのソースコード
object PlusScala {
  def main(args: Array[String]) :Unit = {
    var a = 1
    a = a + 2
  }
}
このバイトコードは何をしているのだろうか。
0: iconst_1 // 0: 定数1→スタック
1: istore_2 // 1: スタックの値→ローカル変数2
2: iload_2  // 2: ローカル変数2→スタック
3: iconst_2 // 3: 定数2→スタック
4: iadd     // 4: スタックの値を加算
5: istore_2 // 5: 加算したスタックの値→ローカル変数2
6: return   // 6: return unit
加算プログラムの流れ
0: iconst_1 // 0: 定数1→スタック
1: istore_2 // 1: スタックの値→ローカル変数2
2: iload_2  // 2: ローカル変数2→スタック
3: iconst_2 // 3: 定数2→スタック
4: iadd     // 4: スタックの値を加算
5: istore_2 // 5: 加算したスタックの値→ローカル変数2
6: return   // 6: return unit
なおJavaでは
public class PlusJava {
    public static void main(String[] args){
        int a = 1;
        a = a + 2;
    }
}
逆アセンブル結果
0: iconst_1 // 0: 定数1→スタック
1: istore_1 // 1: スタックの値→ローカル変数1
2: iload_1  // 2: ローカル変数1→スタック
3: iconst_2 // 3: 定数2→スタック
4: iadd     // 4: スタックの値を加算
5: istore_1 // 5: 加算したスタックの値→ローカル変数1
6: return   // 6: return void
加算プログラムの流れ Java
0: iconst_1 // 0: 定数1→スタック
1: istore_1 // 1: スタックの値→ローカル変数1
2: iload_1  // 2: ローカル変数1→スタック
3: iconst_2 // 3: 定数2→スタック
4: iadd     // 4: スタックの値を加算
5: istore_1 // 5: 加算したスタックの値→ローカル変数1
6: return   // 6: return void
加算プログラム比較 Scala vs Java Scala Java
ポイント
続きまして
関数呼び出し
関数呼び出し
object MethodScala {
  def method(a: Int): Int = {
    a
  }
  def main(args: Array[String]) :Unit = {
    val a = 1
    method(a)
  }
}
関数呼び出し
object MethodScala {
  def method(a: Int): Int = {
    a
  }
  def main(args: Array[String]) :Unit = {
    val a = 1
    method(a)
  }
}
逆アセンブル method
0: iload_1 // スタック1に積む
1: ireturn // return Int
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0       2     0  this   LMethodScala$;
         0       2     1     a   I
逆アセンブル main
0: iconst_1 // val a = 1
1: istore_2 // ローカル変数2に積む
2: aload_0  // thisをスタック0に積む
3: iload_2  // スタック2に積む
4: invokevirtual #21                 // Method method:(I)I
7: pop // スタック除去
8: return // return Unit
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0       9     0  this   LMethodScala$;
         0       9     1  args   [Ljava/lang/String;
         2       6     2     a   I
ポイント
続きまして
関数呼び出しその2: 2つの引数
関数呼び出し
object Method2Scala {
  def method(a: Int, b: Int): Int = {
    a + b
  }
  def main(args: Array[String]) :Unit = {
    val a = 100
    val b = 200
    method(a, b)
  }
}
関数呼び出し
object Method2Scala {
  def method(a: Int, b: Int): Int = {
    a + b
  }
  def main(args: Array[String]) :Unit = {
    val a = 100
    val b = 200
    method(a, b)
  }
}
逆アセンブル method
0: iload_1 // val aをスタックに積む
1: iload_2 // val bをスタックに積む
2: iadd    // a + bしてローカル変数にうつす
3: ireturn // return
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0       4     0  this   LMethod2Scala$;
         0       4     1     a   I
         0       4     2     b   I
逆アセンブル main
0: bipush        100  // val a = 100
2: istore_2           // val a をローカル変数に積む
3: sipush        200  // val b = 200
6: istore_3           // val b をローカル変数に積む
7: aload_0            // this
8: iload_2            // val aをスタックに積む
9: iload_3            // val bをスタックに積む
10: invokevirtual #22 // Method method:(II)I
13: pop               // スタック除去
14: return
LocalVariableTable:
  Start  Length  Slot  Name   Signature
         0      15     0  this   LMethod2Scala$;
         0      15     1  args   [Ljava/lang/String;
         3      11     2     a   I
         7       7     3     b   I
ポイント
続きまして
ArrayとList
Array
object ArrayScala {
  def main(args: Array[String]) :Unit = {
    Array(1, 2, 3)
  }
}
List
object ListScala {
  def main(args: Array[String]) :Unit = {
    List(1, 2, 3)
  }
}
Arrayのコード
object ArrayScala {
  def main(args: Array[String]) :Unit = {
    Array(1, 2, 3)
  }
}
Arrayの逆アセンブル結果
0: iconst_3
1: newarray       int // 配列作成
3: dup
4: iconst_0
5: iconst_1
6: iastore            // 配列要素0格納
7: dup
8: iconst_1
9: iconst_2
10: iastore           // 配列要素1格納
11: dup
12: iconst_2
13: iconst_3
14: iastore           // 配列要素2格納
15: pop
16: return
Listのコード
object ListScala {
  def main(args: Array[String]) :Unit = {
    List(1, 2, 3)
  }
}
Listの逆アセンブル結果
0: getstatic     #19   // Field scala/collection/immutable/List(略)
3: getstatic     #24   // Field scala/Predef$.MODULE$:Lscala/Predef$;
6: iconst_3
7: newarray       int  // 配列作成
9: dup
10: iconst_0
11: iconst_1
12: iastore            // 配列要素0格納
13: dup
14: iconst_1
15: iconst_2
16: iastore            // 配列要素1格納
17: dup
18: iconst_2
19: iconst_3
20: iastore            // 配列要素2格納
21: invokevirtual #28  // Method scala/Predef$.wrapIntArray(略)
24: invokevirtual #32  // Method scala/collection/immutable/List$.apply(略)
27: pop
28: return
ポイント
続きまして
Interface vs Trait
Interface
interface Interface {
    String method(String a, String b);
}
public class InterfaceJava implements Interface {
    public String method(String a, String b) {
        return a + b;
    }
    public static void main(String[] args){
        InterfaceJava a = new InterfaceJava();
    }
}
Trait
trait Trait {
  def method(a:String, b:String): String
}
class TraitScala extends Trait {
  def method(a:String, b:String): String = {
    a + b
  }
}
object TraitScala {
  def main(args: Array[String]) :Unit = {
    val a = new TraitScala()
  }
}
どっちも2つのクラスファイルが出てくるけど、InterfaceとTraitのほうをみます。
interface Interface {
    String method(String a, String b);
}
public class InterfaceJava implements Interface {
    public String method(String a, String b) {
        return a + b;
    }
    public static void main(String[] args){
        InterfaceJava a = new InterfaceJava();
    }
}
trait Trait {
  def method(a:String, b:String): String
}
class TraitScala extends Trait {
  def method(a:String, b:String): String = {
    a + b
  }
}
object TraitScala {
  def main(args: Array[String]) :Unit = {
    val a = new TraitScala()
  }
}
Interface
interface Interface {
    String method(String a, String b);
}
Interface逆アセンブル
{
  public abstract java.lang.String method(java.lang.String, java.lang.String);
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
Trait
trait Trait {
  def method(a:String, b:String): String
}
Trait逆アセンブル
{
  public abstract java.lang.String method(java.lang.String, java.lang.String);
    flags: ACC_PUBLIC, ACC_ABSTRACT
}
ポイント
続きまして
このあたりからコードが長くなって読みづらくなっております
再帰
再帰
object FactorialScala {
  def fact(n: Int): Int = n match {
    case 0 => 1
    case n if n > 0 => n * fact(n - 1)
  }

  def main(args: Array[String]) :Unit = {
    fact(5)
  }
}
コード
def fact(n: Int): Int = n match {
    case 0 => 1
    case n if n > 0 => n * fact(n - 1)
}
逆アセンブル
0: iload_1
1: istore_2
2: iload_2
3: tableswitch   { // 0 to 0          // パターンマッチのキモ
               0: 49                  // n = 0 なら49に飛んでreturn 1
         default: 20                  // それ以外なら次に進む
    }
20: iload_2
21: iconst_0
22: if_icmple     37                  // n < 0 ならエラー
25: iload_2                           // n * fact(n - 1) 部分の用意
26: aload_0
27: iload_2
28: iconst_1
29: isub                              // n * fact(n - 1) 部分の用意
30: invokevirtual #16                 // Method fact:(I)I
33: imul
34: goto          50
37: new           #18                 // class scala/MatchError
40: dup
41: iload_2
42: invokestatic  #24 // Method scala/runtime/BoxesRunTime.(略)
45: invokespecial #27 // Method scala/MatchError."":(略)
48: athrow
49: iconst_1
50: ireturn
ポイント
続きまして
末尾再帰
末尾再帰
object FactorialTAScala {
  def fact(n: Int): Int = {
    def go(n: Int, acc: Int): Int =
      if (n <= 0) acc
      else go(n - 1, n * acc)
    go(n, 1)
  }

  def main(args: Array[String]) :Unit = {
    fact(5)
  }
}
コード
object FactorialTAScala {
  def fact(n: Int): Int = {
    def go(n: Int, acc: Int): Int =
      if (n <= 0) acc
      else go(n - 1, n * acc)
    go(n, 1)
  }

  def main(args: Array[String]) :Unit = {
    fact(5)
  }
}
逆アセンブル
0: aload_0
1: iload_1
2: iconst_1                          // go(n, 1)の用意
3: invokespecial #18                 // Method go$1:(II)I
6: ireturn
逆アセンブル
0: iload_1
1: iconst_0
2: if_icmpgt     7
5: iload_2
6: ireturn
7: iload_1
8: iconst_1
9: isub
10: iload_1
11: iload_2
12: imul
13: istore_2
14: istore_1
15: goto          0
コード
object FactorialTAScala {
  def fact(n: Int): Int = {
    def go(n: Int, acc: Int): Int =
      if (n <= 0) acc
      else go(n - 1, n * acc)
    go(n, 1)
  }

  def main(args: Array[String]) :Unit = {
    fact(5)
  }
}
逆アセンブル
0: aload_0
1: iload_1
2: iconst_1
3: invokespecial #18                 // Method go$1:(II)I
6: ireturn
逆アセンブル
0: iload_1
1: iconst_0
2: if_icmpgt     7  // n <= 0 だと終了
5: iload_2
6: ireturn
7: iload_1          // go(n - 1, n * acc)の用意
8: iconst_1
9: isub
10: iload_1
11: iload_2
12: imul
13: istore_2        // オペランドスタックをローカル変数に引っ込めている
14: istore_1        // go(n - 1, n * acc)の用意
15: goto          0 // もとに戻る
ポイント
続きまして
まとめ
まとめ
最後に
なんかマサカリ下さい
おしまい

Use a spacebar or arrow keys to navigate