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