kagamihogeの日記

kagamihogeの日記です。

ImmutablesのGuidesの一部をテキトーに訳した

immutables.github.io

というわけでGuidesの一部(Get started!, Inception, Immutable objects, Using annotation processor in IDE)をテキトーに訳した。

http://immutables.github.io/immutable.html

Get started!

Prerequisites

Immutablesアノテーションプロセッサ(Immutables annotation processor)の動作にはJava 7以上が必要です。

基本的なイミュータブルオブジェクトの生成に必要な依存性を追加します。

maven依存性のスニペットは以下の通りです。

<dependency>
  <groupId>org.immutables</groupId>
  <artifactId>value</artifactId>
  <version>2.0.19</version>
  <scope>provided</scope>
</dependency>

mavenでは"provided"スコープか、実行時にこのアーティファクトが伝播するのを防ぐのに"optional"にして、実質的にコンパイル時のみの依存性にしておきます。

Immutablesアノテーションプロセッサはコンパイラjavacを裏側で使う(ビルドツールの設定でアノテーション処理を無効化しない)任意のビルドツール下で動作します。Eclipse JDT compiler (ECJ)もこのアノテーションプロセッサをサポートしています。Using annotation processor in IDEを参照してください。

Note: いくつかの問題がJavacインクリメンタルコンパイルアノテーション処理で発生することが分かっています。Mavenなどのビルドツールがこのバグの影響を受けます。mvn clean compileなどのコマンドでフルビルドを強制することでそうした問題を解消できます。インクリメンタルコンパイルを無効化することも選択肢の一つです。

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-compiler-plugin</artifactId>
  <version>3.3</version>
  <configuration>
    <compilerVersion>1.8</compilerVersion>
    <source>1.8</source>
    <target>1.8</target>
    <!-- prevents endPosTable exception for maven compile -->
    <useIncrementalCompilation>false</useIncrementalCompilation>
  </configuration>
</plugin>

詳細は右記のフレーズで検索してください。"java.lang.IllegalStateException: endPosTable already set"

Create immutable object

必要な依存性を追加したら、抽象アクセサメソッドを持つ抽象クラスを作ってみます。同等なことはインタフェースにアノテーションを付与したり、@interfaceでも可能です。

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}

次に、コンパイルをすると生成されたイミュータブル実装が使えるようになります。

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}

できました。プロセッサが生成したコード例についてはgenerated codeを参照してください。

生成された基本的なイミュータブルクラスには様々なメソッドがあります。guideを参照してください。

Configuration for Android

(以下Androidの話なので略)

Inception

以下がimmutablesのバックグラウンドになります。

イミュータブ(Immutables)は最古のプロトタイプから数えると2012の前半頃には開発されていました。現在では、ツールキットがアプリケーション開発で便利に使われています。インメモリストレージとJVM上のコンカレント計算を活用する、航空会社の在庫システムと旅行契約の集約(travel deal aggregators)やその他のアプリケーションで使われています。

クラス生成のユーティリティライブラリとしてGuavaが使われていますが、"Effective Java, Second Edition"とGuavaが一般化したAPIスタイルを強いられています。属性値としてのnullの使用は厳密に禁止されています(Using and avoiding null explained)。

これの解決策についてはAST変換やコンパニオン言語でコンパイラをハックする必要はありません。アノテーションプロセッサは標準Javaコンパイラの一部です。クラスはコンパイル時に生成されてソースとしては保存されませんが、生成されるソースは必要に応じて簡単に調査可能です。生成されるコードはユーザ作成のコードを拡張しますが、単一ファイルにまとめられる事はありません。

Immutablesを使うとドメインモデル貧血症(anemic domain model)に陥っているように見えるかもしれませんが、その印象は誤りであり、適切なドメインモデルの構築に大いに役立つと考えています。ただし個々の構築要素(solid building blocks)を必要とし、値オブジェクト(value objects), スマートデータオブジェクト(smart data objects), イミュータブル値(immutable values)のそれぞれが持つメソッドは別の値を算出し、サービスとエンティティの複雑さを削減します。モデリングの側面の一つは、システム状態とイベント(events)とスナップショット(snapshots)のシーケンスとしてのドメインエンティティを表現することです。event sourcingなどのテクニックはイミュータブルオブジェクトのグラフを活用でき、スナップショット間のサブグラフ共有や、システム状態の変換やある時点の完全なシステム状態の再構築が可能です。その種のシステムにおいて、イミュータブルオブジェクトすべての記述を我々は容易にしました*1

Immutablesアノテーションプロセッサは、内部データ構造はもとよりアプリケーション境界で一貫性や安全性を向上させたい場合に有用で、表現豊かなデータオブジェクトを生成します。

Immutable objects

Overview

Javaでイミュータブルの恩恵を得るために、我々はシンプルで一貫性のある値オブジェクトを簡単に生成するためのアノテーションプロセッサを構築しました。Guava's Immutable Collectionsを想像するかもしれませんが、これは普通のオブジェクトで使うためのものです。

Immutablesのコアはモデリングです。良いモデリングとは良いアプリケーションとサービスの作成が中核にあります。良いモデリングとは良い設計の一部です。我々はJavaプログラミング言語におけるモデリングのギャップを埋めることに関心があり、その点では伝統的なJavaBeansだけでは不十分です。

  • 一旦生成されたイミュータブルオブジェクトは、一貫性のある状態で安全に共有可能です。
    • 必須属性が無い場合には失敗する。
    • 他のコードに渡された場合に勝手に更新が行われない。
  • イミュータブルオブジェクトは元からスレッドセーフなのでスレッド間で安全に共有可能です。
    • 過剰なコピーをしない。
    • 過剰な同期化をしない。
  • オブジェクト定義は書くのも読むのも容易。
    • ボイラープレートなセッターとゲッターを書かない。
    • ソースに含まれてしまうIDEが生成するhashCode, equals, toStringを書かない。

Get started!では抽象値クラス(abstract value class)を作成したあと、イミュータブルな実装クラス(immutable implementation class)を生成するためのアノテーションを付与しました。

プロセッサが生成するコード例についてはsample generated codeを参照してください。

もしくは飛ばしてfeaturesに直接進んでください。

Concepts

Abstract value type

抽象値型(Abstract value type) -- 非final(通常はabstract)クラスもしくはインタフェース(もしくは同等のアノテーション型)で値型を定義し、org.immutable.value.Value.Immutableアノテーションを付与したもの。属性(attribute)と別のメタデータと、javaメソッド(と必要であればフィールド)を持つことが出来る。強く推奨するのは、抽象値クラスには外から見える変更可能な状態を持たないようにしてください。抽象値型は生成されるコードのソースモデルとして使わかれます。Get started!

Attributes

属性(attribute)はオブジェクト生成後は変更不可能な値を保持します。フィールド("field")とJavaBeanのプロパティ("property")と区別し、アノテーションの属性との類似性を描くために、意図的に属性("attribute")というネーミングをしています。属性はアクセサメソッドにより定義され、ゼロ個の引数、非voidjavaメソッドです。属性にするためには抽象アクセサメソッドへのアノテーション付与は必要ありませんが、非abstractメソッドのボディ部で値を計算するデフォルト値を持つ属性などの場合、そのアクセサは通常のメソッドと区別するための専用アノテーションを必要とします。

Immutable implementation class

生成されるfinal classはユーザが作成した抽象値型を継承してすべてのアクセサメソッドを実装し、フィールド・メソッドコンストラクタ・ビルダークラス(builder class)も持ちます。イミュータブルな実装クラスは、スカラなプリミティブおよびオブジェクト参照型、コレクション属性向けの専用サポート、に対する抽象属性アクセサを実装します。Objectメソッドequals, hashCode, toStringはオーバーライドされ、そのオブジェクトの一意性(object identity)ではなく属性値に完全に依存します。イミュータブル実装クラスは主要なブツですが、Immutablesアノテーションプロセッサが生成するのはソースコードアーティファクトだけというわけではありません。

Features

Value

アノテーションプロセッサは、イミュータブル実装を生成するためのモデルとしてアノテーションを付与した抽象値型に対して動作します。イミュータブル実装は抽象値型を継承あるいは実装します。抽象アクセサメソッドを定義しないのであればクラスは抽象でなくても構いません。

@Value.Immutable
interface ValueInterface {}

@Value.Immutable
class ValueClass {}

@Value.Immutable
@interface ValueAnnotation {}

...

ValueInterface valueInterface = ImmutableValueInterface.builder().build();

ValueClass valueClass = ImmutableValueClass.builder().build();

ValueAnnotation valueAnnotation = ImmutableValueAnnotation.builder().build();

生成されるクラス名をカスタマイズしてImmutable*以外のプレフィクスにしたり、プレフィクスを無くしたり出来ます。stylesを参照してください。

抽象値型は別の型の中にネスト可能で、内部クラスとして宣言する場合にはstaticにすべきです(もしネストの場合インタフェースとアノテーション暗黙的にstaticです)。

制御するのはクラスに限りません。別パッケージ内の抽象型からイミュータブル実装クラスを生成可能です。@Value.Includeを型やパッケージで使用可能です。これが最も役に立つのはGuiceなどのDIライブラリで使うためのアノテーション実装を生成したい場合です。インクルードは@Value.Enclosingと組み合わせて使用可能です。

package my.package;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import org.immutables.value.Value;

@Value.Include({ Retention.class })
interface IncludedAnnotations {}
...

ImmutableRetention.build()
    .value(RetentionPolicy.CLASS);
    .build();

Builder

デフォルトで、ビルダー(builders)がイミュータブル実装クラスごとに生成されます。ビルダーは名前付きの属性初期化でオブジェクト生成を表現するもので、一般的に、ビルダーは名前の欠如を補います*2

  • ビルダーを作成するには生成されたイミュータブル実装クラスでstatic builder()メソッドを呼び出します。
  • 属性をセットするには属性初期化メソッド(attribute initializer methods)を呼び出します。
  • 属性初期化後にイミュータブルインスタンスを構築するにはbuild()メソッドを呼び出します。

buildメソッドは必須属性が無い場合には失敗します。属性が初期化されたことの追跡には効率的なビットマスクが使われます。

// builder methods illustrated
ImmutableValue.builder()
    .foo(1442)
    .bar(true)
    .addBuz("a")
    .addBuz("1", "2")
    .addAllBuz(iterable)
    .putQux(key, value)
    .build();

初期化メソッドのプレフィクスをsetもしくは withにしたり、コンストラクタでビルダー生成するようにしたり、createメソッドを持つように、カスタマイズ可能です。stylesを参照してください。

デフォルトではビルダーはfromメソッドを持ち、これによりイミュータブル値でcopy-edit操作が可能です。copy-methodsで構造の共有を防止したり、複数の値の属性を要素とするコレクションの作成などに使用可能です。

ImmutableValue.builder()
    .from(otherValue) // merges attrbiute value into builder
    .addBuz("b")
    .build();

ビルダーのfromメソッドはイミュータブル値のtoBuilder()の代替となる、より強力で理にかなったものです。Immutablesの旧バージョン(0.16より前)ではこのメソッドcopyと呼ばれていました。(現状でもこのメソッド名はカスタマイズ可能です)。fromメソッドstrict builderモードでは生成されません。

もしビルダーがコンストラクタと重複する場合、アノテーションパラメータ@Value.Immutable(builder = false)でビルダーの生成を無効化できます。

ビルダのより進んだ使用法としては、異なるオブジェクト型を作るビルダーを渡す場合があります。オリジナルのBuilder patternでは、インタフェースを一致させています。これを作るには、"Builder"という名前のstaticネストクラスを宣言します。生成されるビルダがこのクラスを拡張します。

interface Vehicle {
}

interface VehicleBuilder {
  // Generated builders will implement this method
  // It is compatible with signature of generated builder methods where
  // return type is narrowed to Scooter or Automobile
  Vehicle build();
}

@Value.Immutable
public abstract class Scooter implements Vehicle {
  public abstract static class Builder implements VehicleBuilder {}
}

@Value.Immutable
public abstract class Automobile implements Vehicle {
  public abstract static class Builder implements VehicleBuilder {}
}

class Builders {
  void buildIt(VehicleBuilder builder) {
    Vehicle vehicle = builder.build();
  }

  void use() {
    buildIt(ImmutableScooter.builder());
    buildIt(ImmutableAutomobile.builder());
  }
}

明示的に宣言するabstract "Builder"は常にイミュータブルオブジェクトのビルダーでextendsもしくはimplementsが行われ、fromやbuildなどのメソッドも持ちます。ただし、ビルダーのスーパータイプと生成されるビルダの構造的な互換性を維持するのに特別な配慮が必要なので、それが壊れる場合には、生成されるコードでコンパイルエラーが発生します。

"forwarding"ファクトリメソッドと抽象ビルダーを使うことで、API経由のビルダーと生成される実装型を隠すことが可能です。exampleを参照してください。

ビルダーの別のカスタマイズとしては、ビルダーの内部にprivateでイミュータブル実装クラスを持つ方法があり、この場合は同パッケージにビルダーがトップレベルのスタンドアローンなクラスとして生成されます。

@Value.Immutable
@Value.Style(visibility = ImplementationVisibility.PRIVATE)
public interface Person {
  String getName();
  String getAddress();
}

Person person = new PersonBuilder()
  .name("Jim Boe")
  .address("P.O. box 0000, Lexington, KY")
  .build();

バージョン2.0.17以降では以下のようなstyleで生成される(未定義の(yet-to-be))ビルダーを拡張可能です。

@Value.Immutable
@Value.Style(visibility = ImplementationVisibility.PACKAGE)
public interface Person {
  String name();
  String address();
  // static inner class Builder extends generated or yet to be generated Builder
  class Builder extends ImmutablePerson.Builder {}
}

Person person = new Person.Builder()
  .name("Jim Boe")
  .address("P.O. box 0000, Lexington, KY")
  .build();

より美しくクラス名を簡潔にするには実装を隠すだけでは不十分ですが、バイトコード呼び出しで実際に参照されるクラスとメソッドに興味が出るかもしれません*3

他の構造とネーミングスタイルのカスタムについてはstyle guideを参照してください。

Strict Builder

スタイル設定"strictBuilder" (@Value.Style(strictBuilder = true, ...))により、strict(厳密)モードで操作するようにビルダーの生成を変えられ、forward-onlyの初期化のみ可能になります。つまり、コレクション属性には追加操作のみ可能、通常の属性の設定は一度のみ可能、となります。Strictモードはデフォルトではオフです。生成される通常のビルダは以前の値をリセット可能で、ほとんどの場合はこのモードで問題ありません。一方、strictビルダーではスペルミスやコピペミスなど初期化時のエラーを早期検出可能です。これによりビルダーをオブジェクトリテラル式に近づけられます。

@Value.Immutable
@Value.Style(strictBuilder = true)
interface Aar {
  boolean a();
  Integer b();
}

ImmutableAar.builder()
    .a(true)
    .b(1)
    .b(2) // IllegalStateException will be thrown here, 'b' cannot be reinitialized
    .build();

strictビルダーではコレクションをリセットするメソッドは生成されず、同様に、fromメソッドも生成されません。これらのメソッドはstrictモードではエラー誘発的(error-inducing)なAPIです。

なお、抽象値型に直接@Value.Styleを使うことは非推奨で、直接使用は実験目的の場合のみにしてください。style guideで解説しているスタイル用のメタアノテーションを作成するようにしてください。

Constructor method

別のビルダーの方法として、簡潔な"constructor"ファクトリーメソッドを提供可能です。コンストラクタはイミュータブル実装クラスのofという名前のstaticメソッドとして利用可能です。

コンストラクターメソッドを生成するには、属性にorg.immutables.value.Value.Parameterアノテーションを付与します。

@Value.Immutable
public abstract class HostWithPort {
  @Value.Parameter
  public abstract String hostname();
  @Value.Parameter
  public abstract int port();
}
...
HostWithPort hostWithPort = ImmutableHostWithPort.of("localhost", 8081);

boolean willBeTrue = hostWithPort.equals(
    ImmutableHostWithPort.builder()
        .hostname("localhost")
        .port(8081)
        .build());

オプションでorderアノテーション属性を使うことで順序を指定可能です。ソース通りの順番にならないjavaコンパイラに対し、コンストラクタのパラメータ順序を決定論的に指定可能です。今のところ、ソース順序はJavacとEclipse JDTコンパイラで動作します。

@Value.Immutable
public abstract class HostWithPort {
  @Value.Parameter(order = 2)
  public abstract int port();
  @Value.Parameter(order = 1)
  public abstract String hostname();
}
...
HostWithPort hostWithPort = ImmutableHostWithPort.of("localhost", 8081);

注意点

  • @Value.Parameterをマークしない必須属性がある場合。
    • コンパイルエラー。必須フィールドに対しデフォルト値を生成できないため。

Array, Collection and Map attributes

以下のコレクション型がビルトインでサポートされています。

  • T[]
  • java.util.List<T>
  • java.util.Set<T>
  • java.util.Map<K, V>
  • com.google.common.collect.Multiset<T>
  • com.google.common.collect.Multimap<K, V> (ListMultimap, SetMultimap)
  • com.google.common.collect.BiMap<K, V>
  • com.google.common.collect.Immutable*

配列属性は安全にクローンされます。コレクション属性は、もしクラスパスでGuavaが利用可能であれば、Guavaのイミュータブルコレクションが裏側で使われます。そうでない場合、標準JDKのunmodifiableコレクションクラスでラップして安全なコピーが行われます。

enumキーのjava.util.Setjava.util.Mapは効率の良いEnumSeEnumMapが裏側で使われます。

順序付きmapとsetは@Value.NaturalOrder@Value.ReverseOrderを使用して自然および自然の逆順序にそれぞれ解釈されます。

  • java.util.SortedSet<T>
  • java.util.NavigableSet<T>
  • java.util.SortedMap<K, V>
  • java.util.NavigableMap<K, V>

順序アノテーションが無い場合、順序付きsetとmap属性はカスタムcomparatorでの生成をサポートする通常の属性として生成されます。

上述の通りコレクション型はGuavaのイミュータブルコレクションcom.google.common.collect.Immutable*としても宣言可能です。なお、java.util.ArrayListなど他のコレクション実装は特別なコレクション属性とは解釈されません。

ビルダーを使用する場合、コレクション属性はunspecifiedのままにしておけます。Precondition check method Buildersを使用して要素数を検証可能で、コレクション属性を初期化する専用のメソッドが使えます。

  • T型の要素を持つfooという名前の配列属性の場合。
    • foo(T...)
  • T型の要素を持つfooというlist, setもしくはmultiset属性の場合。
    • foo(Iterable<? extends T>) - strictモードでは利用不可
    • addFoo(T)
    • addFoo(T...)
    • addAllFoo(Iterable<? extends T>)
  • V型でキーの型にKで値を持つbarという名前のmapとbimapの場合。
    • bar(Map<? extends K, ? extends K>) - strictモードでは利用不可
    • putBar(K, V)
    • putBar(Map.Entry<? extends K, ? extends V>)
    • putAllBar(Map<? extends K, ? extends K>)
  • barという名前のmultimapの場合。
    • bar(Multimap<? extends K, ? extends K>) - strictモードでは利用不可
    • putBar(K, V)
    • putBar(Map.Entry<? extends K, ? extends V>)
    • putBar(K, ...V)
    • putAllBar(K, Iterable<V>)
    • putAllBar(Multimap<? extends K, ? extends K>)

バージョン0.16以降ではビルダーにclear*メソッドを生成せず、コレクションとマップ属性に対してclearFoo()clearBar()などは生成されません。コレクションあるいはマップの内容をクリアするには、bar(Collections.emptyList())などのリセットメソッドを使うか、インスタンスのビルド後に正しくcopy methodsを使います。

生成されるメソッドは使い勝手を良くするために最小構成にしています。これにより結果としてコード全体のノイズが減ります。膨大なメソッドは台所の流しのようになります*4。もしメソッドの数に関心があるのなら、アプリケーションで未使用な生成メソッドを除去するProGuardなどのツールを検討してください。

同様な方法で他の種類のコンテナもサポートできないのは何故なのでしょうか? java.lang.Iterable, java.util.Collection, java.util.Queueは? これらその他のコンテナはイミュータブルオブジェクトモデリング目的にtoo-genericもしくはtoo-specificなのがその理由です。もちろん、リクエストに応じて変更は可能です(そして、それはorder annotationsを解釈する順序付きsetとmapに起きたことです)。これの良い点は属性値に任意の型がサポートされ、黒魔術を使うこと無く*5、他のコンテナ型もそのまま使用可能です。

@Value.Immutable
public abstract class DoItYourselfContainer {
   public abstract Iterable<String> iterable();
}
...
ImmutableDoItYourselfContainer.builder()
    .iterable(ImmutableSet.of("a", "b", "c"))
    .build();

Optional attributes

com.google.common.base.Optional<T>型を戻り値型に宣言した属性は必然的にT型のoptional属性になります。

2.0以降、Java 8のjava.util.Optional, java.util.OptionalInt, java.util.OptionalLong, java.util.OptionalDoubleも完全にサポートされました。

optional値はオブジェクト構築時に未指定でも良く、デフォルトはOptional.absent()(もしくはJava 8のOptional.empty())になります。生成されるビルダーはoptional属性用に専用のイニシャライザを持ちます。

  • T型の要素を持つoptという名前のoptional属性の場合。
    • opt(T)はTの現在値をセットする。
    • opt(Optional<T>)は値があれば設定する(opt(Optional<T>) specifies present or absent)*6
import java.util.*;

@Value.Immutable
public interface AllOptional {
  com.google.common.base.Optional<Integer> v1();
  Optional<Integer> v2();
  OptionalInt i1();
  OptionalLong l1();
  OptionalDouble d1();
}
...
// No error as all values are optional
ImmutableAllOptional.builder().build();

ImmutableAllOptional.builder()
    .v1(1)
    .v2(2)
    .i1(1)
    .l1(1L)
    .d1(1.0)
    .build();

Default attributes

builderで属性省略時にデフォルト値を設定可能です。デフォルト属性を宣言するには、非abstractの属性初期化メソッドを作成してorg.immutables.value.Value.Defaultアノテーションを付与します。構築時に値が省略された場合、この初期化メソッド属性がデフォルト値取得に呼ばれてその値がセットされます。よって、アクセサメソッドはそのセットされたデフォルト値を返します。

@Value.Immutable
public abstract class PlayerInfo {

  @Value.Parameter
  public abstract long id();

  @Value.Default
  public String name() {
    return "Anonymous_" + id();
  }

  @Value.Default
  public int gamesPlayed() {
    return 0;
  }
}
...

PlayerInfo veteran = ImmutablePlayerInfo.builder()
    .id(1)
    .name("Fiddler")
    .gamesPlayed(99)
    .build();

PlayerInfo anonymous44 = ImmutablePlayerInfo.of(44);

String name = anonymous44.name(); // Anonymous_44

デフォルト属性のメソッドボディは他の生成されるメソッドやデフォルト属性を参照してはいけません。そうしないと、構築時に不定の初期化順序が原因で失敗します。 保証されるのは、デフォルトとderived attributeの前にabstractアクセサのすべての属性が初期化されることだけです。そのため初期化時には安全にアクセサの参照が可能です。

コレクション属性で空のコレクションを返すのに@Value.Defaultを使う必要はありません。未初期化の場合にはデフォルトで空になります。collection attributes@Value.Defaultを使う場合、通常扱いの属性となり、よろしくやってくれるイニシャライズは生成されません*7

イミュータブルannotation型ではデフォルト属性はdefaultキーワードで定義されており、もし値が設定されない場合におけるデフォルト定数値での初期化に相当します。

デフォルト属性はJava 8のinterfaceデフォルトメソッドで動作します。属性に@Default defaultを付与してください。

注意点

  • 初期化メソッドのボディが非abstract属性のアクセスを参照する場合、結果は不定です。

Derived attributes

Derived attributesとはイミュータブルインスタンスから読み取り可能だが、セットは出来ないもののことです。その値は通常は他の属性を基にして生成されます。

Derived attributesを宣言するには、非abstract属性の初期化メソッドを作成してorg.immutables.value.Value.Derivedアノテーションを付与します。デフォルト属性に似て、メソッドボディは何らかの計算をして属性値を返します。Derived attributesは通常のメソッドのように振る舞い、何らかの計算をして何らかの値を返しますが、一つの重要な違いがあります。Derived attributesの値は一度計算されると(オブジェクト構築時の最後に)フィールドに格納されます。

@Value.Immutable
public abstract class Order {

  public abstract List<Item> items();

  @Value.Derived
  public int totalCount() {
    int count = 0;

    for (Item i : items())
      count += i.count();

    return count;
  }
}

Order order = ImmutableOrder.builder()
    .addItems(Item.of("item1", 11))
    .addItems(Item.of("item2", 22))
    .build();

// total count will be already computed
int totalCount33 = order.totalCount();

デフォルト属性同様に、Derived attributesメソッドのボディは別のDerived attributesもしくはデフォルト属性を参照してはいけません。

注意点

  • 初期化メソッドのボディが非abstract属性のアクセスを参照する場合、結果は不定です。

Nullable attributes

我々はnullable attributesの使用は推奨しませんが、もし本当に必要なら、抽象属性アクセサに@Nullableアノテーションを追加します。Any annotation with simple name Nullable will work. nullable attributesはビルダーを使用してセットする必要はなく、また、null値を初期化時に使用可能です。nullableコレクションとその他の特殊な型(other special types)は非サポートで、より正確に言えば、@Nullableを追加すると"nothing-special"属性になります。

@Value.Immutable
interface NullAccepted {
  @Nullable Integer i1();
  @Nullable Long l2();
}

NullAccepted obj = ImmutableNullAccepted.builder()
    .i1(null)
    .build();

obj.toString(); // NullAccepted{i1=null, l2=null}

Lazy attributes

遅延属性(Lazy attributes)とは、遅延して一度だけ値を計算する初期化メソッドのことです。

遅延属性を宣言するには、非abstractの属性初期化メソッドを作成してorg.immutables.value.Value.Lazyアノテーションを付与します。derived attributesに似て、メソッドボディで計算を行って属性値を返します。derived attributesは通常メソッドのように振る舞いますが、初回アクセス時い値が計算され、それ以降、同一の保存された値が返されます。

注意点

  • 遅延属性はequalsとhashCodeの一部にはなりません。auxiliaryとして振る舞います。
@Value.Immutable
public abstract class Order {

  public abstract List<Item> items();

  @Value.Lazy
  public int totalCost() {
    int cost = 0;

    for (Item i : items())
      cost += i.count() * i.price();

    return cost;
  }
}

Order order = ImmutableOrder.builder()
    .addItems(Item.of("item1", 11, 1))
    .addItems(Item.of("item2", 22, 2))
    .build();

// total cost will be computed now
int lazilyComputedCost = order.totalCost();
// total cost already computed and stored value is returned
lazilyComputedCost = order.totalCost();

遅延属性の値はスレッドセーフで、レースコンディションと関係なく一度だけ計算されます。

デフォルトderived属性とは異なり、遅延属性のアクセサメソッドのボディは別の属性を参照可能です。

つまり、デフォルトかderived属性から抽象アクセサを呼ぶ場合のみ制約があります。この問題を避けるには、デフォルトかderived属性から遅延属性を参照してはいけません。drivedかデフォルト属性から遅延属性の呼び出しは常に安全ではありません(not always safe)。遅延属性の値をイーガー(eagerly)に計算するという意味にはならず、さらに、もしデフォルトやderived属性を計算するのに遅延属性を使う場合*8、遅延属性は未初期化になりえます。

遅延属性の実装は旧バージョンのscalaの実装方法と似ています。現在ではその実装方法は潜在的にScala SIP-20で報告されている問題に苦しめられています。一方、その問題はイミュータブルオブジェクトをmutable/static/thread-localの状態とミックスする場合にのみ発生します。循環依存性を異なるイミュータブルオブジェクト間に導入する必要があります*9

Precondition check method

イミュータブルオブジェクトの中核を成す利点の一つは、イミュータブルオブジェクトは一貫性のある状態(consistent state)で適切に属性値が構築され、それ以降決して変更されない(never changes)、という点にあります。場合によっては、属性値をチェックしたり、属性値の組み合わせが正しいか(cross validation)を確認する必要が発生します。

イミュータブル実装クラスでは自前のコンストラクタを書かないので、非privateメソッドを作成して@Value.Checkアノテーションを付与し、事前条件チェックが失敗する場合には実行時例外をスローします。

@Value.Immutable
public abstract class NumberContainer {
  public abstract List<Number> nonEmptyNumbers();

  @Value.Check
  protected void check() {
    Preconditions.checkState(!nonEmptyNumbers().isEmpty(),
        "'nonEmptyNumbers' should have at least one number");
  }
}
...
// will throw IllegalStateException("'nonEmptyNumbers' should have at least one number")
ImmutableNumberContainer.builder().build();

ただし、何らかの値で構築されるオブジェクトの状態検証と、オブジェクト生成後に何らかのコンテキストにおけるビジネスルールの正しさを検証することは、異なるという点に注意してください*10。事前条件チェックはそうしたビジネスルールなどの検証用途に用いるべきではなく、一貫性の保持とインスタンスが使用可能なことの保証に使用してください。

事前条件チェックのメソッドはイミュータブルオブジェクトがインスタンス化されてすべての属性が初期化(instantiated and all attributes are initialized)される時、呼び出し元に返される前(before returned to a caller)に実行されます。事前条件チェックに失敗したインスタンスはいずれも実行時例外が原因で呼び出し元へは到達不能になります。

Copy methods

with*メソッド(withers)とは、イミュータブルオブジェクトをコピーして新しい値を適用し、残りの未変更属性はそのままにすることで、属性を変更するものです。

counter = counter.withValue(counter.value() + 1)

参照等価チェック==をすることで同一値のコピーを防ぎthisを返します。また、プリミティブも同様に==チェックを行います(ただしfloatdouble除く)。完全同値チェックもしくはその他の特殊なチェックは省略されます。deep-equalityの属性チェックよりも、値の新規コピーと生成の計算コストの削減がねらいです。 === kagmaihoge 注 ここから=== 参照等価チェックが~うんぬんの日本語が自分でも分かりにくいので補足。生成されるコードを見るとwith*メソッドはこんな感じになる。

  public final ImmutableHoge withFoo(Foo foo) {
    if (this.foo == foo) {
      return this;
    }
    ...
  }

元のイミュータブルオブジェクトが持ってる属性と、渡された属性の参照同士が==trueとなる場合、同一のイミュータブルオブジェクトと見なして、新しいインスタンスは生成せずにthisを返す。テキトーなチェックだけど、ディープチェックして高コストになるよりかはよかろーて、という感じらしい。

=== kagmaihoge 注 ここまで===

コピーメソッドは構造を共有してのコピー形式を提供します。これは、属性値のいくつかは変更する場合に有用です。その他のコレクション属性は、手動もしくはビルダで再構築する代わりに、同一のイミュータブル値をそのまま参照します。新しい値は必要とする古い値のサブグラフを効率的に共有します。

コレクションとmap属性についてはどうでしょうか? withItemAddedwithKeyValuePutなどのメソッド群を持つことは魅力的ですが、これにはadd lastadd firstなどなど多数が必要となりコレクションの再ビルドやリハッシュを隠ぺいすることになり、そしてイミュータブルコレクションでは常に不要です。現在のところ、単純に属性をすべて置換し、もし新しいコレクションの値が非イミュータブルの場合にはイミュータブルとしてコピーされることを保証します。

Value changedValue =
    ImmutableValue.copyOf(existingValue)
        .withName("Changed Name")
        .withValues(Arrays.asList("Only new value")) // replacing any copied collection

コピーメソッドはデフォルトで生成されます。抽象値型の参照にアップキャストすることなく、イミュータブル実装クラスのインスタンスの参照を取得したい場合に使います。

@Value.Immutable(copy = false)アノテーションのパラメータによりコピーメソッドの生成を抑制できます。

Singleton instances

シングルトンになる空("empty")もしくはデフォルト("default")のインスタンスを簡単に作成dけいます。@Value.Immutable(singleton = true)アノテーションのパラメータを使用してシングルトンインスタンスを作成可能で、簡潔なof()ファクトリーメソッドでシングルトンインスタンスを取得します。

@Value.Immutable(singleton = true)
public abstract class Data {
  public abstract Set<String> chunks();
}

...
boolean willBeTrue =
    ImmuableData.of() == ImmuableData.of();
// true

boolean willBeTrueAlso =
    ImmuableData.of().chunks().isEmpty();
// true

抽象値型は必須属性を持ってはならず、そうしないとシングルトンインスタンスの生成が不可能です。デフォルト属性もしくはoptionalを使用する必須でない属性を作成可能です。

現状では、空のシングルトンインスタンスは、全ての属性が必須でなければ、ビルダーおよびコンストラクタと組み合わせることが可能です。シングルトンインスタンスのみ使いたければ、ビルダーとコンストラクタを抑制します。

  • @Value.Immutablesingleton = truebuilder = falseを追加する。
  • @Value.Parameterコンストラクタが生成されるので使用しない。
@Value.Immutable(singleton = true, builder = false)
public abstract class Singleton {
  // 必要であればコンストラクタの可視性をpackageに制限する。
  // ImmutableSingletonがSingletonを拡張可能でなければならないので
  // privateでは上手くいかない。
  Singleton() {}
}

...
Singleton singleInstance = ImmutableSingleton.of();

注意点

  • もし抽象値型が必須属性を含みシングルトン生成時に値を要求する。
    • コンパイルエラー。必須フィールドに対しデフォルト値を生成できない。

Instance interning

ある型の値の個数が限定されていると判明しており、それらのインスタンスをintern*11することでパフォーマンスを向上可能な場合があります。

特定の値型の全インスタンス積極的に(strongly)internしたい場合、Immutablesが開発者の代わりにそれを実行します。strong internは@Value.Immutable(intern = true)アノテーションを使うことで有効化されます。

strong internのみサポートします。ソフト参照と弱参照のinternおよび部分範囲intern(partial range interni)形式は対外的には省略しています。しかし、org.immutables:ordinalモジュールでenum-likeオブジェクトの洗練されたドメインベースinternをサポートしています。詳細についてはこのモジュールのjavadocを参照してください。

Precomputed hashCode

もしイミュータブルオブジェクトが多数の属性や巨大なオブジェクトグラフを持つ場合、何度もhashCode値を再計算するのが、広範囲のハッシュテーブルが使われると、非効率になる場合があります。その場合、ハッシュコードを構築時に事前計算をして保存しておくことが可能です。事前ハッシュ計算を有効化するには@Value.Immutable(prehash = true)アノテーションのパラメータを使います。

Auxiliary attributes*12

このアノテーションを付与した属性はequals, hashCode, toStringメソッドの生成から除外されます。

@Value.Auxiliary属性は格納されてアクセス可能ですが、equals, hashCode, toStringメソッドの実装からは除外されます。遅延属性は常にauxiliaryとして振る舞います。

@Value.Immutable(intern = true)
interface TypeDescriptor {
  // インスタンスではqualifiedNameのみintern化される。
  String qualifiedName();
  @Value.Auxiliary
  TypeElement element();
}

これ以外のequals, hashCode, toStringのカスタムハンドリングが必要な場合、customize those methods directlyを参照してください。

Customize toString, hashCode and equals

生成されるequals, hashCode, toStringのカスタムは簡単で、抽象値クラスを実装するのと同じくらい手間はかかりません。Immutablesのプロセッサは生成クラス側ではそうした自前のメソッドをオーバーライドしません。

@Value.Immutable
public abstract class OmniValue {
  @Override
  public boolean equals(Object object) {
    return object instanceof OmniValue;
  }

  @Override
  public int hashCode() {
    return 1;
  }

  @Override
  public String toString() {
    return "OmniValue{*}";
  }
}

boolean willBeTrue =
    ImmutableOmniValue.builder().build()
        .equals(new OmniValue() {});

自前で用意したequalshashCodeメソッドinstance interningprecomputed hashCode機能で問題無く動作します。

これは態々注意することで無いかもしれませんが、何をすべきかが自分の頭の中でクリアである場合のみ、正しくequalshashCodeを実装してください。

ある属性は格納したいがequalshashCodeからは除外したい場合、ただ単にその属性に@Value.Auxiliaryを付与して自動生成されるhashCode/equalsをsそのまま使用してください。

Immutable Annotation

アノテーション型にも@Value.Immutableを付与可能で、イミュータブルアノテーション実装が生成され、仕様通りの振る舞いをします。配列とデフォルト属性とその他の機能も通常のイミュータブルオブジェクトと同様に動作します。

@Value.Immutable
public @inteface MyAnnotation {
  String[] value();
  boolean enable() default true;
}
...
ImmutableMyAnnotation.builder()
  .addValue("a", "b")
  .enable(true)
  .build();

アノテーションが別のライブラリやパッケージにある場合でも、Includeアノテーションを使用してビルダーと実装を生成可能です。

Serialization

基本的なjavaのバイナリシリアライズを以下の方法でサポートしています。

  • 抽象値型がSerializableを実装していることの検出。
  • 遅延属性をtransientにする。
  • 抽象値型からイミュータブル実装型へserialVersionUIDをコピー。
  • singletoninternedインスタンスを保持するためにreadResolveメソッドの実装を生成。

より高度なjavaバイナリシリアライズアノテーションserialモジュールで利用可能です(バージョン2.0.12で試験的な機能として追加)。

  • org.immutables:serial:2.0.21
  • @Serial.Version - エンクロージングの値型にシリアルバージョンを適用する。
  • @Serial.Structural - enables special structural serialization. Object serialized and deserialized using structural serialization are more flexible to enable data evolution. By having fields changed to optional, scalars changed to arrays or collection type changed (was set, then became list, for example), you have more freedom to evolve value objects.*13

JSONシリアライズのオプションはJSONガイドを参照してください。

Generics (not) supported

Immutablesは、抽象値型とconstruct parametrized instancesに型変数を追加できない、という意味で型パラメータをサポートしません。この点は確かにある種の制限であり、将来的には改善されると思われます。しかし、仮に我々が完全なパラメータ化可能なイミュータブルオブジェクトサポートを行うと決めた場合、ビルダーやJSONシリアライズにおけるコレクションサポートなど各種の機能実装時に発生するであろう頭痛の種がどの程度のものか、不明瞭です。アノテーション処理における型の分析には制限有りのツールを用いています。単純なケースではない使い方をする場合、自明で無い型変数の解決はプログラムするあります(ワイルドカード、交差、境界条件など)。

とかなんとか言いましたが、良いお知らせもあります。ジェネリクスのサポートは、パラメータ型のインスタンス生成時に抽象値型を生成することが可能です。以下はImmutablesで可能なことの例です。

// requires version 2.0.2+ to compile

interface TreeElement<T> {}

interface Node<T> extends TreeElement<T> {
  List<TreeElement<T>> elements();
}

interface Leaf<T> extends TreeElement<T> {
  @Value.Parameter T value();
}

@Value.Immutable
interface StringNode extends Node<String> {}

@Value.Immutable
interface StringLeaf extends Leaf<String> {}

TreeElement<String> tree =
    ImmutableStringNode.builder()
        .addElements(ImmutableStringLeaf.of("A"))
        .addElements(ImmutableStringLeaf.of("B"))
        .addElements(
            ImmutableStringNode.builder()
                .addElements(ImmutableStringLeaf.of("C"))
                .addElements(ImmutableStringLeaf.of("D"))
                .build())
        .build();

継承した属性の生成において、型変数の置換が正しく行われている点に注目してください。完全ではありませんが、そこそこ使い物になり、完全な型消去ジェネリクス型よりはまぁまぁマシと言えるでしょう。

Patterns

このセクションではImmutablesを使う際の良くあるパターンとレシピを紹介しますが、ものによってはImmutablesの機能でないものもあります。

Expressive factory methods

我々に寄せられるカスタマイズのリクエストには、コンストラクタメソッド名の変更やコンストラクタのフック、があります。我々はその二つの相互に関連したニーズに取り組んでいました。

  • ファクトリメソッドのパラメータは外から指定するが、それとは異なる何らかの値でオブジェクトを生成したい。
  • あるパラメータがオブジェクト生成に使われる目的を明確にするためにファクトリーメソッドの名前に意味を持たせたい。

専用のアノテーションパラメータとフックメソッドを検討し、我々は最終的に思い至りました……何もしなくて良いと。驚くべきことに、解決策は特にこれといった機能を必要としません。ただ単に抽象値クラスにファクトリーメソッドを宣言し、イミュータブル実装クラスのコンストラクタメソッドを呼ぶだけです。

@Value.Immutable
public abstract class Point {
  @Value.Parameter
  public abstract double x();
  @Value.Parameter
  public abstract double y();

  public static Point origin() {
    return ImmutablePoint.of(0, 0);
  }

  public static Point of(double x, double y) {
    return ImmutablePoint.of(x, y);
  }

  public static Point fromPolar(double r, double t) {
    return ImmutablePoint.of(r * Math.cos(t), r * Math.sin(t));
  }
}

抽象値型APIを使う実装クラスを隠すためにファクトリメソッドを使うことも可能です。上述の例では、ImmutablePointを使っていることはPointのpublicインタフェースからは見えない点に注目してください。

Hide implementation class

先の内容に付け加えていうと、ネストした抽象ビルダーを使うことでビルダー実装を見えなくすることも可能です。繰り返しになりますが、実装クラスはpublic APIとして公開されません。

// 生成されるクラスをpackegeスコープにする。
@Value.Style(visibility = ImplementationVisibility.PACKAGE)
@Value.Immutable
public abstract class Point {
  @Value.Parameter public abstract double x();
  @Value.Parameter public abstract double y();

  public static Point of(double x, double y) {
    return ImmutablePoint.of(x, y);
  }

  public static Builder builder() {
    return ImmutablePoint.builder();
  }
  // 抽象メソッドのシグネチャは実装ビルダーがオーバーライドするものと
  // 一致しなければならない。
  public interface Builder {
    Builder x(double x);
    Builder y(double y);
    Point build();
  }
}

Smart data

イミュータブルオブジェクトは"smart data"として振る舞えます。smart dataは、純粋なデータコンテナに加えて、ドメイン固有の知識や計算も実行します。サービスとエンティティがビジネスロジックの実行を組み合わせる際に、値オブジェクトはビジネスコンテキストには無関係なドメイン固有の計算処理をします。

@Value.Immutable
public abstract class OriginDestination {
  @Value.Parameter
  public abstract Airport origin();
  @Value.Parameter
  public abstract Airport destination();

  public boolean isDomestic() {
    return origin().country().equals(destination().country());
  }

  public boolean isCrossCityTransit() {
    return origin().city().equals(destination().city());
  }

  public OriginDestination reverse() {
    return ImmutableOriginDestination.of(destination(), origin());
  }
  ...
}

値を計算するメソッドで値オブジェクトを充実させていき、それから、適切な場所に複雑な計算処理を移動させていきましょう。

Non-public attributes

抽象値クラスのpublicインタフェースの視点からだと、ある特定の属性が余計な場合があり、可視性を下げることでAPIを使う側からはその属性を隠せます。ただし、コンストラクタのパラメータやビルダー上ではpublicで公開されたままです。

@Value.Immutable
public abstract class Name {
  @Value.Parameter
  abstract String value();

  public String toString() {
    return value();
  }

  public static Name of(String value) {
    return ImmutableName.of(value);
  }
}
...

Name name = Name.of("The Rose");
String value = name.toString();
// "The Rose"

Null-Object pattern in attribute values

Optional<T>属性の代替案として、null-object patternが使えます。Immutablesに何かしらをする必要はありませんが、デフォルト属性を使用してください。

public enum Stars {
  NONE, ONE, TWO, THREE, FOUR, FIVE;
}

@Value.Immutable
public abstract class Hotel {
  @Value.Default
  public Stars stars() {
    return Stars.NONE;
  }
}

Opaque containers

toStringでいくつかのフィールドを伏せたり別の方法にしたり、固有の方法でhashCodeequalsを処理したい場合、アノテーションプロセッサのアドホックな機能を使わずに済む方法があります。たとえば、toStringメソッドからは機密データをマスクしたい場合、機密データ用をマスクするラッパーを作成します。こういう手合いの処理が必要になることはそこそこあります。

@Value.Immutable(builder = false)
abstract class Confidential {
  @Value.Parameter
  abstract String value();
  public String toString() { return "<NON DISCLOSED>"; }
}

上記クラス作成後、属性として使用します。

@Value.Immutable
interface Value {
  int number();
  Confidential confidential();
}
...
// toString
"Value{number=1, confidential=<NON DISCLOSED>}"

Auxiliary attributesも参照してください。

Limitations

いくつかの機能が時間が無かったり興味無かったりして未実装で、複雑すぎたり特化しすぎな機能もありますが、その辺は実装しなくてもまぁソレナリに問題ないです。バランスについては常に気を配っています。

  • デフォルトとderived属性は常にそれ以外のデフォルトやderived属性を参照不可能です。
  • 抽象値クラスは型変数でパラメータ化出来ませんが、実型を与える際にパラメータ型を拡張もしくは実装することが可能です。

Using annotation processor in IDE

Overview

Java 6以降アノテーション処理は標準javaコンパイラの一部になりました。Immutablesアノテーションプロセッサの動作にはJava 7以上を必要とします。Immutablesアノテーションプロセッサはコンパイラーのバックエンドにjavacを使用するJavaビルドツールで動作します。(ビルドツールの設定でアノテーション処理を無効化しないこと)Eclipse JDT compiler (ECJ)でもアノテーションプロセッサをサポートします。

今日では主要なIDEが特別な準備無しにアノテーション処理をサポートしています。通常は何らかの設定が必要で、場合によってはアノテーション処理が動かないような奇妙な振る舞いをすることもあります。

IDEプラグインするアノテーションプロセッサは外部の依存性を必要としない単一のjar形式で作られています。

Eclipse

Mavenを使う場合、m2eでアノテーション処理を設定できます。これを動かすには、まずm2e-aptコネクタをインストールします。Eclipse marketplaceからインストールします。

Eclipse marketplace

Maven dependenciesでJDT/APT autoconfigurationをグローバルないしプロジェクトごとに有効化します。

developer.jboss.org

(画像のリンクはdeveloper.jboss.orgのものです)

上記の設定以降はm2e Mavenプロジェクトのimport/updateごとに、アノテーションプロセッサをクラスパスに設定します。

Manual configuration tutorial

上記の設定が何らかの理由で動作しない場合、手動で設定します。

以下のダイアログ設定例はローカルのmavenリポジトリにインストール済みのアノテーションプロセッサを手動で設定しています。プロジェクト設定でアノテーション処理を一度有効化する必要があり、以下の例では、M2_REPOクラスパス変数(m2eが定義するヤツ)をextendすることでfactory pathがアノテーションプロセッサのjarを指すように設定します。

eclipse-annotation-processing.png

http://immutables.github.io/pix/eclipse-factory-path.png

M2_REPOから正しくjarを選んで下さい。

IntelliJ IDEA

IntelliJ IDEAでアノテーション処理を設定するにはPreferences > Project Settings > Compiler > Annotation Processorsを使います。

Obtain annotation processors from project classpath and specify output directories.

idea-annotation-processor-preferences.png

(Picture linked from restx.io documentation)

設定後、クラスがプロジェクトビルドの度に生成され、ソースが検索可能となりオートコンプリートが利くようになります。

*1:For that kind of systems we just made writing immutable objects a whole lot easier.が原文。

*2:builders compensate lack of named and optional constructor arguments in Java language.が原文。optinal~以下が上手く訳せん

*3:but you might be interested to see for yourself which classes and methods are actually referenced in a calling bytecode.が原文。前の文を受けてbutがこの文の意味が良く分からんので上手く訳せない…

*4:Bigger set of methods resulted in kitchen sinkが原文。http://eow.alc.co.jp/search?q=kitchen+sink を見ると「何でもかんでも」みたいに比喩で使う場合にはネガティブな意味合いが込められているらしい

*5:while there's no any kind of magic supportが原文

*6:つまりisPresentがtrueなら値が設定される

*7: it will turn it into nothing-special attribute, no sugar-sweet initializers in builder will be generated.が原文。属性がコレクションの場合、専用の初期化コードが生成される。ただし、defaultをつけるとそれが生成されなくなる。sugar-sweetは、ゲームのGearts of warでマーカスがアイテム拾うときにsweetsとか呟くけど、まぁそういう感じでsugar-sweet「良い具合」を表現する単語らしい

*8:if lazy value is in turn uses in computation one of those default or derived attributeが原文。in turnってのが良くわからん…

*9:cyclic dependency need to be introduced between different immutable objects.が原文。よくわからん

*10:However, one should notice how this differs from other kinds of object state validation where object may be constructed with some values and later validated for correctness regarding business rules in some context.が原文。大枠はあってると思うけど、細部が自信ない。

*11:詳細はjava internとかでぐぐる

*12:補助属性、とでも訳せば良いんだろうか

*13:よくわからん