LoginSignup
11
7

More than 5 years have passed since last update.

JavaクラスファイルとScalaSignature

Last updated at Posted at 2016-10-14

Introduction

きっかけ

  • Scalaのsealedってどうやって実現されてるんだろう?

sealed modifier

  • sealedをつけたclass, traitは、同一ファイル内でのみ継承できる
  • Javaには同じ概念はない(finalは似てるけど)
  • コンパイル済みclassファイルをリンクする場合も、sealedは有効

example

Foo.scala
sealed class Foo
Bar.scala
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

Hello.java
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 より引用

  1. 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.

格納方法

Java reflectionでScalaSignatureを取得してみる

というわけで、Javaのreflectionを使って、冒頭のFoo.classのScalaSignaturesが取得できるはず

ShowAnnotation.scala
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)

とれる。

参考資料

11
7
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
7