Furyu

[フリュー公式] Tech Blog

フリュー株式会社の技術ブログです

2016年11月15日

SatoKeita

JMockitを使ってテストを書いていて困った点

こんにちはフリューのジョンです。普段はJMockitを使ってテストコードを書いているのですが、今回は、そのJMockitを使ってテストを書いていて、困った2点を書きたいと思います。

  1. すでに@Injectableで書いてあるクラスをモックしたい
  2. FileOutputStreamをJMockitでMockUpを使ってコンストラクタをモックする

この2つです。例えば以下のようなメソッドをテストした場合はどうすればよいのかを書きます。

環境

JDK 1.8.0_65
JMockit 1.18
JUnit 4.12

解説

1. すでに@Injectableで書いてあるクラスをモックしたい

これは、テスト対象のクラスに@Resourceがあるため、@Injectableを書いているため起きます。
@Mockedを引数に書いてつかうこと、MockUpを使ってやることもできません。(すでにモックされています、というエラーが出ます)

対応としては、ソースコードの①にある通り、Expectationsにクラスを渡すことで、Fileのモックを対象のExpectations内で書くことができます。
今回はコンストラクタをモックしたかったので、返り値となるFileのモックオブジェクトを@Injectableで定義してExpectations内で使用しています。

2. FileOutputStreamのコンストラクタをモックする

これはFileOutputStreamに@Mockedをつけてモックすることができないために起きます。(@Mockedは使えません、代わりに@Injectableや部分モックを使ってくださいというエラーが出ます)

対応しては、ソースコードの②にある通り、MockUpで記述します。
closeメソッドもモックしているのはtry-with-resource文にて自動的にcloseメソッドが呼ばれるためです。(もちろん無いとテストが落ちます)

ここで気をつけることが、一点あります。
実際のコードでは、ファイルを引数としたコンストラクタを使用していますが、テストコードでは異なるコンストラクタをモックします。理由としては以下になります。

To mock the target “FileOutputStream(File)” constructor, JMockit must replace the internal call to another constructor (“this(file, false);”) with an innocuous “super(…)” call. This is because, inside a constructor, the JVM forbids extra calls before a “super(…)” or “this(…)” call is complete. So, in this case calling “proceed()” from $init has no effect since there is no remaining code in the original constructor.

https://groups.google.com/forum/#!topic/jmockit-users/EvTuJKTecDk

意訳すると、以下のようになります。

JMockitでFileOutputStream(File)をモックするためには、内部でsuperによってFileOutputStream(File, false)を呼び出しているのでそちらをモックしてください。 理由としては、JVMによってsuper(…)またはthis(…)の余分な呼び出しが解消されます。だから、MockUpのproceed()で呼ばれる$initは存在しないコンストラクタを参照してしまうのです。

まとめ

JMockitは複数の書き方ができますが、それ故にわからなくなることも多いと思います。
みんな細かい部分で詰まっていると思いますので、ソースコードを共有していければ解消できるかもしれないですね(切実)


2016年11月15日

Kayo

Open vSwitchのVRRP(L3HA)で高可用性を実現する!

みなさん、こんにちは。コンテンツ・メディア第1事業部インフラ担当の藤本佳世です。
今回もOpenStackの続き、【Open vSwitchのVRRP(L3HA)で高可用性を実現する!】についてお話しします。
前回の記事はこちら

VRRPとは

VRRP(Virtual Router Redundancy Protocol)とは、ルータの冗長化をサポートするプロトコルです。
フリューでは、OpenStackで立ち上げた仮想インスタンスに接続する際に、このVRRP機能を採用しています。
仮想インスタンスへはFloatingIPでssh接続しています。
その通信が、Neutron(networknode)サーバでプライベートIPアドレスにNAT変換され、仮想インスタンスに接続されます。この処理を行うNeutron(networknode)サーバ2台をVRRP構成にすることで、master側のサーバに障害が発生した場合、すぐにstandby側のもう1台のサーバに自動的に処理を切り替えることができます。

<イメージ図>

openstack_vrrp

構築について

公式ドキュメントを参考に構築を進めました。
ネットワークの知識が必要なので、慣れないうちは苦労するかもしれません。

サーバ構成について

コンポーネント ノード
サーバ1 Keystone/Horizon/Nova/Cinder/Glance/Neutron
※1 MySQL/Memcached/RabbitMQ
※2 Heartbeat
controllernode
サーバ2 Keystone/Horizon/Nova/Cinder/Glance/Neutron
※1 MySQL/Memcached/RabbitMQ
※2 Heartbeat
controllernode
サーバ3 Nova/Cinder/Neutron computenode/storagenode
サーバ4 Nova/Cinder/Neutron computenode/storagenode
サーバ5 Nova/Cinder/Neutron computenode/storagenode
サーバ6 Nova/Cinder/Neutron computenode/storagenode
サーバ7 Neutron networknode
サーバ8 Neutron networknode

ネットワーク作成について

ネットワーク名 ext-net80 vxlan-net80
プロジェクト admin service
ネットワーク種別 flat vxlan
外部ネットワーク external1
割り当てサブネット ext-subnet80
10.0.80.0/24
vxlan-subnet80
192.168.80.0/24
Gateway 10.0.80.1 192.168.80.1

※1ネットワークセグメントのみの情報です。実際には同じような構成で3パターン作成しました。

設定内容

各ノードに対して設定を行います。ポイントとなる部分を書き出してみました。

用語の説明

NAT変換 IPアドレスを変換する技術のこと
VXLAN L3ネットワーク上に論理的なL2ネットワークを構築するトンネリングプロトコルのこと
ML2プラグイン OpenStack Networking で、複雑な実世界のデータセンターで使われている様々な L2 ネットワーク技術を同時に利用できるようにするフレームワークのこと
GRE ML2 がサポートするネットワークセグメントタイプの一種
Flat ML2 がサポートするネットワークセグメントタイプの一種
DHCP IPアドレスなどを自動的に割り当てる仕組みのこと

controllernode

共通のオプションを設定します。

ML2プラグインを設定します。

networknode

Open vSwitchエージェントを設定します。

L3エージェントの設定します。

DHCPエージェントを設定します。

メタデータエージェントの設定します。

computenode

Open vSwitchエージェントを設定します。

ネットワークの作成

次は、ネットワークの作成の手順をご紹介します。
フリューでは、3つのネットワークセグメントが存在し、それぞれ仮想ルータを3台構築しました。

下記の通り、flat外部ネットワークとプロジェクトネットワーク(vxlan)を作成しました。

ネットワーク名 ext-net80 vxlan-net80
プロジェクト admin service
ネットワーク種別 flat vxlan
外部ネットワーク external1
割り当てサブネット ext-subnet80
10.0.80.0/24
vxlan-subnet80
192.168.80.0/24
Gateway 10.0.80.1 192.168.80.1

※1ネットワークセグメントのみの情報です。実際には同じような構成で3パターン作成しました。

flat外部ネットワーク作成

  1. 外部ネットワークを作成 ※下記の例では「ext-net80」というネットワーク名で作成しました。
  2. 外部ネットワークのサブネットを作成

プロジェクトネットワーク(vxlan)作成

  1. serviceプロジェクトのidを取得
    ※ここではすでにserviceプロジェクトが存在することを前提としています。
  2. ネットワーク作成
    ※先ほどとは異なり、network_typeにvxlanを指定します。
  3. サブネットを作成
  4. 仮想ルータの作成
  5. サブネットを仮想ルータにインターフェースとして接続
  6. ルータに外部ネットワークへのゲートウェイを追加

HA確認

  1. 下記のコマンドでVRRP(active,standby)構成になっていることが確認できます。
  2. networknodeで、qrouterとqdhcp名前空間の作成を検証します。
    ※両方のqrouter名前空間で同じUUIDが使われているはずです。

  3. networknodeでHAの動作確認をします。
    ※qrouter 名前空間には ha, qr, qg インターフェースがあります。バックアップノードはqrとqgインターフェースはIPアドレスを持ちません。

    ha インターフェース 169.254.192.0/18 の範囲の一意な IP アドレスを持つ
    qr インターフェース プロジェクトネットワークのゲートウェイ IP アドレスを持つ
    qg インターフェース 外部ネットワーク上のプロジェクトルーターの IP アドレスを持つ

  4. Networknodeで、マスターノードの HA インターフェースの IP アドレスから VRRP 通信が行われていることを確認します。

冗長化することは、どんなシステムでも重要視される部分ですね。

最後に

今回初めてVRRPという単語を聞いた私にとっては、かなり苦戦する部分ではありましたが、何とか構築することができました。ネットワーク知識が必要となりますが、少しでも構築の参考になれば嬉しいです。


2016年10月28日

SatoKeita

Vue.jsを使ってみよう

こんばんは、フリューのジョンです。普段はサイト開発をしており、最近Mithril.jsからVue.jsに移行しています。

今回はVue.jsのお話です。
JavaScriptのフレームワークっていっぱい聞くけどどういうところが嬉しいんだろう、というレベルの方にお伝えしたいと思います。

概要

Vue.jsはReact.jsやAngular.jsと同じようにMV*系のフレームワークです。
近年はJavaScriptのフレームワークは飽和していますが(React.js、Angular.js、Riot.js、Aurora.jsなどなど)
それではなぜそのようなフレームワークを使うのでしょうか?jQueryじゃダメなのでしょうか?

 

いえ、jQueryでもできます。しかし、既存のフレームワークのほうが楽をすることができる可能性が高いです。

  • view周りをフレームワークに任せることができるので、logicに集中できる。
    • jQueryのみで大きなページを作成すると、logicとview部分が複雑になる。
  • 要素をJavaScriptで作成している複雑なページは、仮想DOMを使用しているフレームワークを使うと、描画速度が早くなる

一つ目の利点について考えてみます。
例えば以下のようにselectタグの値によってコンテンツの表示切り替えを行いたい場合、どのようにjQueryを使えば良いのでしょうか?

おそらく、コード量を抑えるとしたら以下のように書くと思われます。他を消してから、選択されたものを表示するということになると思います。

では、少し意地悪な変更が加えられたとしましょう。現状idを使って選択していますが、このidは一般的すぎて変更したいですね。
その場合、JavaScript側のセレクタも変更しなければいけません。それに加え、optionタグのvalueを使って切り替えられると思っていたlogicの前提が崩れてしまいます。*1

フレームワークを使うと、このような画面切り替えなどもフレームワーク側が対応してくれるのです。(後にこの問題をVue.jsではどう記述するかを説明します)

そこでVue.jsです

では他のフレームワークではなく、Vue.jsを使う利点はなんでしょうか?私としては、以下だと考えられます

  • viewをhtmlに書くことができるので、viewを分業しているプロダクトではデザイナさんが変更しやすい

Angular.jsでも同じようなことができるのでは?となりますがVue.jsではよりデザイナさんが理解しやすいhtmlの書き方になっています。また、軽量な画面に関しては、少量のコード量で済みます。
そして、仮想DOMを用いているため描画スピードも早いです。

Vue.jsを使ってみよう

先ほどのselectタグの値によってコンテンツの表示切り替えを行いたい場合を題材にします。

各コンテンツにidを書かないようになりました。その代わりにv-showという表示、非表示を切り替える際に使う「ディレクティブ」をつけています。
selectタグにもidを書いていません。その代わりにv-modelという双方向のデータバインディングを行うディレクティブをつけています。

JavaScript側では、Vueに、rootとなるタグのセレクタと他のデータを渡しています。これだけです。

  1. Vueのコンストラクタに渡したオブジェクトにあるdataにはcontentが1で入っています。これがselectタグに渡り、どのoptionタグを表示するかを決めています。
  2. selectタグのonchangeイベントが走るとoptionタグのvalueがcontentに入ります。
  3. contentの値が変化すると、各コンテンツに書いてあるv-showの評価が行われます。その評価値がtrueになった場合、そのコンテンツが表示されるという仕組みになっています。

どうでしょうか?
idの変更やデザインの変更がある場合もこれならば影響はありません。

まとめ

jQueryだけでもできるページは少なくありませんし、広く一般的になっていると思われます。しかし、少し複雑度が増してくると、途端にコード量が増えていきます。

これまでの例からもVue.jsはかんたんに導入できることがわかりましたので、何か面倒だなと感じたときには、ぜひライブラリやフレームワークを使ってみてください。

次はVue.jsを使った少し複雑な例をお送りしたいと思います。

おまけ

今回のソースコードについてはjsfiddleにあげておりますのでご確認ください。
https://jsfiddle.net/satojohn/x9fjajuL/

参考

Vue.jsのドキュメント : https://vuejs.org/

 

*1 もちろんdata属性を使うなど回避策はありますが、ここではわかりやすいようにしています。


2016年10月13日

sakata

JavaOne 2016 サンフランシスコに参加しました!(その4) #javaone #j1jp

Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

前回はコレクションのセッションについてのレポートでした。今回はクラスローダーのセッションについてレポートします。

Join the War on ClassLoader Leaks

スライドはこちらにあります。

https://static.rainfocus.com/oracle/oow16/sess/1461358415846001E0TZ/ppt/Classloader%20leaks%20public.pptx

内容

java.lang.OutOfMemoryError(OOME)はみな出会ったことがあるだろう。Java 8になってPermGen spaceとは出なくなったが、代わりにMetaspaceが出るようになった。ローカルでの開発時にも、継続的デプロイでもOOMEは出る。このセッションのアジェンダは次のようなものだ。

  • OOMEは何を意味するのか?
  • OOMEはなぜ起こるのか?
  • どこでリークするのか?
  • どのように避けるのか?
  • OSSの例
  • トレーニング

OOMEは何を意味するのか?

JVMのメモリはヒープとPermGen/Metaspaceとスタックである。スタックはスレッドごと、ローカル変数やメソッドのパラメータを持つ。ヒープはオブジェクトのインスタンスを持つ。PermGen/Metaspaceはjava.lang.Classインスタンスなどを持つ。PermGenという名前はJava EEとクラスのアンロードができる前に名付けられた。Java 8でMetaspaceへ置き換えられた。OOME PermGen space/Metaspaceは、あまりに多くのクラスをロードしたときに起こる。

OOMEはなぜ起こるのか?

OOME PermGen space/Metaspaceが起こる原因は次の2つが考えられる。

  1. アプリケーションが大きすぎる。Java 7までは-XX:MaxPermSize=256Mなどと指定すればよい。Java 8ではMetaspaceは自動的に増えていく。
  2. java.lang.Classインスタンスが再デプロイ後にガベージコレクトされない

参照の型は次の4つがある。

  • 強い参照:到達可能なときは決してGCされない
  • ソフト参照:OOMEになる前にGCされる
  • 弱い参照(WeakHashMap):強い参照とソフト参照がないときにGCされる
  • ファントム参照:GCは妨げない

GCの到達可能性は以下の図のようなことである。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-40-30

再デプロイでは、新しいWARファイルをデプロイすれば前のクラスローダはGCされる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-43-30

クラスローダーの参照は次の図のようになる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-44-55

どのようにリークが起きるのか。GCルートからクラスローダへ到達可能だと、クラスローダをGCすることができない。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-05-17-45-38

どこでリークするのか?

Tomcatなどアプリケーションサーバのバグだ。他にCommons LoggingやLog4Jなどロギングフレームワークのバグ、Bean Validation APIやUnified Expression Languageのバグといったこともある。

GCルートでも起こる。まず、システムクラスローダによってロードされるクラス。JDKのクラスにおけるstaticフィールドで起こる。ライブスレッドではローカル変数、メソッドのパラメータのスタック、java.lang.Threadインスタンスで起こる。

システムクラスでは、java.sql.DriverManagerやBean introspectionのキャッシュ、シャットダウンフック、カスタムでデフォルトにしたAuthenticator、カスタムのセキュリティでのProvider、カスタムMBean、カスタムのThreadGroup、カスタムのプロパティエディタ…最初の呼び出し元でのcontextClassLoaderへの参照などで起こる。

DriverManagerをもう少し詳しく見てみよう。たとえばMySQLのJDBCドライバを使うときはこうするだろう。

com.mysql.jdbc.Driverクラスにはstaticイニシャライザがある。

registerDriver()メソッドは以下のようになっている。

このregisterDriversという変数は、以下のものだ。

図で表すと以下のようになる。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-16-55-03

そのため、コンテキストのシャットダウンで以下のように書かなければ、リークしてしまう。

スレッドもここで止めておかなければならない。次のようにスレッドを実装するのは悪いアイデアだ。

止められるようには以下のようにする。

このことは、次の記事に紹介されている。

Heinz Kabutz / Java Specialists’ – The Law of the Blind Spot

次にThreadLocalについて考えてみよう。次のように実装してはならない。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-09-13

ThreadLocalのJavaDocには以下のようにある。

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; …

各スレッドは、スレッドが生存していてThreadLocalインスタンスがアクセス可能である間は、スレッド・ローカル変数のコピーへの暗黙的な参照を保持します。

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-13-40

プールされたスレッドではスレッドはクラスローダより長生きする。ThreadLocalはThreadGlobalになる!

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-17-35

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-06-17-18-20

ThreadLocalMapのJavaDocには以下のようにある。

However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space

(私訳)しかしながらリファレンスキューが使われないので、テーブルが容量不足になり始めたときだけ失効したエントリが削除されることが保証されます。

参照を含んだカスタムの値は、staticなThreadLocalであればリークし、そうでなければ予測されていないGCとなる。カスタムのThreadLocalであればリークしない。

ThreadLocalはクリアするようにしよう。

実際には、多くのOSSライブラリが違反している。

どのように避けるのか?

リーク防止のライブラリがある。アプリケーションサーバ非依存、Tomcatよりも多くをカバーしており、ログで警告を出力、ライセンスはApache 2ライセンスだ。実行時の依存は0で、設定は必要ない。Mavenであれば以下のように依存ライブラリを追加する。

これはServlet 3.0以降用だが、2系のものもある。

OSSの例

Bean Validationの1.0.0.GAでは以下のようになっていた。

(私訳)List<ValidationProvider>はキーであるクラスローダに強い参照を持っている。JPAのCachingPersistenceProviderResolverと同じモデルを使うようにして。

jyukutyo調査

Bean Validationの1.1.0.GAではこのクラスは次のようになっています。

providersPerClassloader.put( classLoader, new SoftReference<List<ValidationProvider<?>>>( providers ) );とあるので、ソフト参照を使うようになったようです。

JPAのCachingPersistenceProviderResolverクラスも調べてみました。

その他のOSS

javax.el.BeanELResolverやorg.apache.cxf.transport.http.CXFAuthenticator、com.sun.star.lib.util.AsynchronousFinalizerも該当する。

トレーニング

Eclipse Memory Analyzer(MAT)を使おう。

http://www.eclipse.org/mat/

次のオプションをつけてアプリケーションを実行しよう。

感想

最後に出てきた、MATについてはこのブログで私が記事を書いています。

ヒープダンプをEclipse Memory Analyzerで解析しよう!

セッションとしては非常におもしろかったです。とくにOSSの実例を挙げながらリークの原因を解説する箇所ではとても興奮しました。こういう内容のセッションはJavaOneだと他にもいろいろとあります。なかなか普段の勉強会でこの分野を扱っていることは少ないと思いますので、興味がある人にはJavaOneはとても楽しめるカンファレンスです!


2016年10月12日

sakata

JavaOne 2016 サンフランシスコに参加しました!(その3) #javaone #j1jp

Hello world! コンテンツ・メディア第1事業部のjyukutyoこと阪田です。

前回はAngular 2とGWTのチュートリアルセッションをレポートしました。今日はJDKの開発者であるStuart Marksさんのセッション”Collections Refueled”をレポートします。

Collections Refueled

スライドはこちらにあります。
https://stuartmarks.files.wordpress.com/2016/09/collectionsrefueled-final.pdf
タイトルの通り、Javaのコレクションフレームワークについての話です。その歴史とJava 8での拡張、Java 9での拡張と将来の作業についてでした。

内容

JDK 1.0はレガシーコレクション、VectorやHashtableなどである。1.2はコレクションフレームワークが導入された。インタフェースとしてCollection、List、Set、Map、Iteratorが、具象クラスとしてArrayList、HashSet、HashMap、TreeSet、TreeMapがある。5.0ではジェネリクス、java.util.concurrentパッケージが追加された。

Java 8

Java 8ではラムダとストリームが追加され、インタフェースにデフォルトメソッドとstaticメソッドが定義できるようになった。15年経って初めての変更だ。

 


CollectionはIterableのサブインタフェースなので、これはすべてのコレクションで動作する。
Iteratorインタフェースでは、ほとんどのイテレータは削除をサポートしていない。そのためこう書く必要があった。



remove()のデフォルトメソッドがまさにこれだ。削除できないイテレータを作るときは、単にremove()を省くだけでよい。削除できるものを書くときは、remove()メソッドをオーバーライドするだけだ。

Collectionインタフェースでは、removeIfでバルク更新ができるようになった。


もしコレクションがArrayListなら、7の場合の計算量はO(n^2)であり、8の場合はO(n)となる。

List

ListインタフェースのreplaceAllもバルク処理だ。


ただし、要素の型を変更することはできない。そうしたいときはStreamを使う。

List.sortはCollections.sortよりなぜよいのか?Collections.sortは3つのステップを使う。

  • 一時的な配列にコピーする
  • 配列をソートする
  • リストにコピーして戻す

List.sortはデフォルトでは上記と同じだが、ArrayList.sortはオーバーライドしてin-placeでソートするため、コピーはしない。Collections.sortは現在単にList.sortを呼び出すだけだ。

Map

MapインタフェースにもforEachがある。



replaceAllもある。



Java 7までMulti-mapはとても扱いづらかった。Multi-mapはキーに対して複数の値があるマップだ。

Java 8ではこうなる。

Comparator

Comparatorを書いて楽しい人はいる?Comparatorは多くの条件とコードの繰り返しだ。Java 8はComparatorにstaticとデフォルトメソッドを追加した。名字とnullがある名前でソートし、nullを最初に持ってくる2レベルのソートのサンプルを見てみよう。まずはJava 7から。

Java 8ではこうできる。

Java 9

JEP 269: Convenience Factory Methods for Collectionsだ。こういったAPIが追加される。

設計と実装の課題としては以下のものがあった。

  •  Immutability
  •  Iteration Order
  •  Nulls Disallowed
  •  Duplicate Handling
  •  Space Efficiency
  •  Serializability

新しいstaticファクトリメソッドが返すコレクションはイミュータブルとなる。これは従来の不変性であり、不変の永続性ではない(addなどはUnsupportedOperationExceptionをスローする)。不変性はよいものである。一般的な場合、コレクションは既知の値で初期化した後、変更することはない。不変であれば自動的にスレッドセーフになる。効率、とくにスペースについて改善するチャンスがある。JDKには一般的な目的のための不変コレクションはない。

Setの要素とMapのキーのイテレーションの順序については、HashSetやHashMapであれば公式には順序が保証されないとしていたが、長い間たいてい一貫していた。これを変更すると、順序に依存しているコードは動作が変わってしまう。適用するのは新しいコレクション実装だけにする。既存のコレクションは同じままとなるだろう。

ListやSetの要素、Mapのキーや値にnullを許可しない。NullPointerExceptionをスローする。1.2でコレクションにnullを許可したのは失敗だった。Java 5以降コレクションではnullを許可していない。とくにjava.util.concurrentのコレクションではそうだ。nullはNullPointerExceptionの原因だからだ。

Setの要素やMapのキーで重複したときはIllegalArgumentExceptionをスローする。”コレクションのリテラル”で重複があるのはプログラミングのエラーである可能性が高い。理想的にはこれをコンパイル時に検出する。値はコンパイル時の定数ではない。次にいいのは、実行時の生成においてフェイルファストにする。

2つの文字列の要素を持つSetを考えてみる。

どのぐらいスペースを使っているのか?オブジェクトを数えてみる。

  • unmodifiableなラッパーが1つ
  • HashSetが1つ
  • HashMapが1つ
  • 長さが3のObject配列のテーブルが1つ
  • Nodeオブジェクトが2つ、各要素に1つずつ

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-03-14-36-08

サイズの見積もりとしては、オブジェクトごとに12バイトのヘッダーがある(64ビットJVMで32GB未満のヒープ、OOP圧縮あり)。int、float、参照フィールドごとにプラス4バイトだ。ということは、先ほどのオブジェクトを合計すると152バイトになる。

 

  • unmodifiableなラッパー: ヘッダー + 1フィールド = 16バイト
  • HashSetが1つ : ヘッダー + 1フィールド = 16バイト
  • HashMapが1つ: ヘッダー + 6フィールド = 36バイト
  • 長さが3のObject配列のテーブル: ヘッダー + 4フィールド = 28バイト
  • Nodeオブジェクトが2つ、各要素に1つずつ: (ヘッダー + 4フィールド) * 2 = 56バイト

フィールドベースのSet実装だとオブジェクト1つとフィールド2つで20バイトとなる。

 

%e3%82%b9%e3%82%af%e3%83%aa%e3%83%bc%e3%83%b3%e3%82%b7%e3%83%a7%e3%83%83%e3%83%88-2016-10-03-14-43-09

実装はすべてstaticファクトリの背後にあるプライベートなクラスとしている。

コレクションはすべてシリアライズできる。ただ、デフォルトのシリアライズ形式は内部実装について漏らしてしまっていた。新しいコレクションの実装は、カスタマイズしたシリアライズ形式となる。

将来的な作業

これらはまだ計画されていない将来的な作業だ。

VectorやHashtableといったレガシーなコレクションは非推奨にする。LinkedListもそうするかもしれない。

コアなコレクションのイテレーション順序をランダムにする。新しいミューテータのデフォルトメソッドを追加する。ArrayDequeへのインデックスアクセスを追加する。

JEP 269のコレクション拡張では、パフォーマンスの改善と、順序があるSet/Mapを追加する。

Project ValhallaでのValue typesなど。

感想

Stuartさんのセッションは熱い感じで印象深かったです。これはStuartさんの最後のセッションでしたが、他2つ、Stuartさんのすべてのセッションを聴きました。

このセッションの内容としては、Javaプログラミングで使わないことはほぼないであろう、コレクションについて歴史をたどれ、興味深いものでした。

JEP 269については、以前私は個人ブログにて動作確認していました。

そのためAPIの追加は知っていました。しかしほかにも数多くの改善があり、とくにコレクションが使うスペースについて理解が深まったのはうれしいです。