argius note

プログラミング関連

習作:Minestra - Java8以降対応のユーティリティーライブラリー #java8

f:id:argius:20150725093650p:plain

Java8未満を切り捨てて色々と作ってみるシリーズ。


Releases · argius/minestra - GitHub

Java8以降向けのユーティリティー集ライブラリーです。
名前は "Minestra" 。イタリア語でスープの意味です。名前には特に意味はありません。





今のところ、機能は以下の3つです。

  • ImmArray - 不変配列+操作の強化 (Streamに類似?)
  • PathIterator - findイテレーター(Files.findに類似)
  • I18nResource - I18NResourceBundleに類似)

類似機能ばかりですが、それには一応、理由があります。


わざわざ書き換えなくても、こうすれば上手くできるよ!、のようなのがありましたら、是非教えてください。よろしくお願いします。


ImmArray - 不変配列

Java8はラムダ式と共にStreamが使えるようになって、すごく便利になりましたね。しかし、残念ながらメソッドが足りないと思います。仕組み上の制約なんでしょうか。

また、従来のコレクションは、インターフェイスの性質が不変でないので、不変を前提としたコードが書きづらいです。

それなら、Functional Java などの関数型向けライブラリーを使えば良いのでは? とおっしゃるかもしれませんが、あれは言語の拡張に近いというかレイヤーが厚すぎると思うのです。配列に対してそのままmap関数が使えれば良いだけなんです。


そこで、配列をラップして関数型っぽいメソッドを持たせたものを作ってみました。それがImmArrayです。名前は、Javaらしいシンプルで一般的な名前にすると被りそうなので、それっぽくない名前にしてみました。
実は既にSeq-for-Java8というのを作っていて、それの焼き直しです。

  • ImmArrayのコード例

// import java.util.*;
// import minestra.collection.*;

ImmArray<String> a = ImmArray.of(Arrays.asList("1"), Optional.of("2"), ImmArray.of("3", "A"), Optional.empty(),
    Arrays.asList("5"), "B", Stream.of("6", "7")).flatten();
// => [1, 2, 3, A, 5, B, 6, 7]



実装には、ラムダ式インターフェイスのdefaultメソッドとstaticメソッドを大量に使っています。
Streamと同じように、IntXXX,LongXXX,DoubleXXXとセットになっています。
配列をラップしているだけなので、スケーラビリティーやパフォーマンスが求められる場面には向いていません。シビアじゃない状況で簡潔に書きたいときに使ってください。


これでもまだメソッドが足りないくらいですね。でも、タプルが無いJavaでは素直に表現できないものがあったりするので、今のところは作っていません。例えばzipとか。他にも、permutationは迷うところです。


PathIterator - findするIteratorもしくはStream

Streamに合わせて、NIO.2のFilesクラスにfindメソッドが追加されました。


このメソッドは非常に便利なのですが、途中でIOExceptionが発生すると、そこで検索を終了してしまうんです。警告を出して検索を続行する、のようにはできません。

PathIteratorは、エラーの場合でも処理を続行できるようにしています。

  • PathIteratorのコード例

// import java.nio.file.*;
// import minestra.file.*;

// /tmp以下の100KB以上のファイルパスをprint
PathIterator.streamOf(Paths.get("/tmp")).filter(x -> x.toFile().length() >= 1024L * 100)
.forEach(System.out::println);

// Streamに変換しなくても使える
for (Path x : PathIterator.of(Paths.get("/tmp"))) {
    System.out.println(x);
}



これもパフォーマンスの点では本物には敵わないと思いますので、そうでない場合に使ってください。

今後は、エラーハンドラーをラムダ式で指定できるように拡張を考えています。

実はこの名前は標準APIのとあるクラスと名前が被っているのですが、たぶん一緒に使うことは無いので大丈夫でしょう。


なお、このクラスはPotaufeuから単独で使えるように抽出したものです。


I18nResource - I18Nテキストリソース

ResourceBundleは、デフォルトロケールに対応するプロパティーファイルが用意されていない場合の動作に不満があります。
サブクラスを作れば対応できるのだと思いますが、個人的にはファイルだけでタライ回しのようなことがしたいのです。

I18nResourceは、親のリソースを決めておき、指定したリソースが無い場合は祖先をたどることができるようにしたものです。
また、テキストファイルはUTF-8で書きます。拡張子.txtにしています。native2asciiで書いたものは使えません。

  • PathIteratorのコード例

// import java.util.*;
// import minestra.resource.*;

// /default.txt
I18nResource rootBase = I18nResource.create(Locale.JAPAN);

// /yourpkg/default.txt
I18nResource pkgBase = I18nResource.create("/yourpkg/", Locale.JAPAN);

// /yourpkg/YourClass.txt
I18nResource res = rootBase.derive(YourClass.class);

// /yourpkg/YourClass_ja.txt
I18nResource resJa = rootBase.derive(YourClass.class, Locale.JAPAN);

String s = res.string("key1");
int i = res.integer("key2");
boolean b = res.isTrue("key3");



そういえば、Java9ではプロパティーファイルのUTF-8対応があるみたいですね(=>JEP 226: UTF-8 Property Files)。それと互換性を持たせられると良いんですが。


なお、このI18nResourceは、Stew4に含まれているResourceManagerを独立させて機能強化したものです。





まだアルファ版なので大幅に変更するかも知れません。

新しい機能を思いついたら追加しようと思います。
あわよくば、Mavenに登録したりも考えています。



(おわり)