Introduction
きっかけ
- Scalaのsealedってどうやって実現されてるんだろう?
sealed modifier
- sealedをつけたclass, traitは、同一ファイル内でのみ継承できる
- Javaには同じ概念はない(finalは似てるけど)
- コンパイル済みclassファイルをリンクする場合も、sealedは有効
example
sealed class Foo
class Bar extends Foo
$ scalac Foo.scala
$ ls
Bar.scala Foo.class Foo.scala
$ scalac Bar.scala
Bar.scala:1: error: illegal inheritance from sealed class Foo
class Bar extends Foo
^
one error found
この記事の目的
- Scalaのsealedがどう実現されているのか分かる
環境
Scala compiler version 2.11.8 -- Copyright 2002-2016, LAMP/EPFL
java version "1.8.0_77"
Javaクラスファイルの基礎
レイアウト
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
name | length | description |
---|---|---|
magic | 4 bytes | 固定値 0xCAFEBABE
|
minor_version | 2 bytes | JDKのバージョンを表す |
major_version | 2 bytes | 同上 |
constant_pool_count | 2 bytes | 定数プールのエントリ数 |
constant_pool | - | 定数プール |
access_flags | 2 bytes | クラス/インターフェース/final/publicなどを表すフラグ |
this_class | 2 bytes | このクラスを表す、定数プール内のインデックス |
super_class | 2 bytes | 親クラスを表す、定数プール内のインデックス |
interfaces_count | 2 bytes | このクラスの直接の親インターフェースの数 |
interfaces | - | 各親インターフェースを表す定数プール内のインデックスの配列 |
fields_count | 2 bytes | このクラスが持つフィールドの数 |
fields | - | フィールドを表すfield_info の配列 |
methods_count | 2 bytes | このクラスが持つメソッドの数 |
methods | - | メソッドを表すmethod_info の配列 |
attributes_count | 2 bytes | このclassファイルに格納された属性の数 |
attributes | - | 属性を表すattribute_info の配列 |
example
public class Hello {
public static void main(String[] args) {
System.out.println("hello, world");
}
}
$ javac Hello.java
$ javap -v Hello.class
Classfile /Users/hokada/develop/scala/sealed-example/clean/Hello.class
Last modified Oct 14, 2016; size 416 bytes
MD5 checksum 83526ec8f7ebea872a7a2c3b7c8a4d2b
Compiled from "Hello.java"
public class Hello
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."<init>":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // hello, world
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Hello
#6 = Class #22 // java/lang/Object
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Hello.java
#15 = NameAndType #7:#8 // "<init>":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 hello, world
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Hello
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
{
public Hello();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 1: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String hello, world
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 3: 0
line 4: 8
}
SourceFile: "Hello.java"
attributesに格納できるんじゃ?
- 実際、Scala 2.7以前は
attributes
に格納されてたっぽい
Scala 2.8以降は?
-
ScalaSignature
アノテーションのbytesという値(String)に格納されている
なぜアノテーションか
- JVMはカスタムattributeを無視する
- なのでJavaのreflectionではScala特有の情報は取りようがない
- reflectionの利便性のためと、classファイルをもう一度parseする無駄をなくすためっぽい?
Storage of pickled Scala signatures in class files
より引用
- Why does Scala 2.8 store signatures differently?
The legacy method for storing signatures as attributes is simultaneously more elegant,
more compact (about 15%) and simpler than that using annotations. However, to access
the pickled signature in attributes requires obtaining and parsing the entire class file.
Because annotations are recognized by the JVM, the new method allows retrieving pickled
signature bytes directly from within a running Scala program by using Java reflection.
格納方法
- sealedなど各情報のフラグはバイト列
- 定数プールにバイト列は格納できないので、うまいこと文字列にエンコードしてるっぽい
Java reflectionでScalaSignatureを取得してみる
というわけで、Javaのreflectionを使って、冒頭のFoo.class
のScalaSignaturesが取得できるはず
import java.lang.annotation.Annotation;
public class ShowAnnotation {
public static void main(String[] args) {
Annotation[] annots = Foo.class.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
}
}
$ javac ShowAnnotation.java
$ java -cp ~/.ivy2/cache/org.scala-lang/scala-library/jars/scala-library-2.11.8.jar:. ShowAnnotation
@scala.reflect.ScalaSignature(bytes=E1A!
ai\8
qP3naRLhh1CA
A!"A
\1 M
Ca"=S:LGO+g
AA)
とれる。