JSR 275: Units Specificationについて
Java SE 7にJavaの新機能として追加される多くのJSRの中で、一風変わったJSRに「JSR 275: Units Specification」というものがある。JSR 275はJavaプログラムにおいて数量や単位を扱うための強力な機能を提供する拡張APIである。このAPIを使えば、同じint型やdouble型の値でも、その単位がメートルなのかマイルなのか、グラムなのかポンドなのかといったことを区別して扱うことができるようになる。また、メートルでの距離からマイルへの距離へ変換するなどといった単位変換のための機能も用意される。
JSR 275で提供される予定の主な機能を以下に挙げる。
- 単位を扱うためのインタフェースや抽象クラス
- それらのクラス/インタフェースによる以下の機能
- 単位間の互換性
- さまざまな単位における数量
- 単位の演算
- 一般的な単位に対する実装クラス
- 文字列から単位へのパース
- 単位から文字列へのフォーマッティング
- 定義済み単位のデータベース
たとえば長さを表すdouble型のプロパティを持っているクラスを考えてみよう。通常のJavaプログラムでは、このdouble値がどの単位の長さを表すのか区別することができない。それどころか、誤って長さ以外の値を設定してしまったとしても、それを検出する有効な手段もない。JSR 275を使用することで、実世界で使用される単位を正確にモデリングすることができるようになる。
また、単位に関連する主要な値があらかじめ設定されている点も見逃せない。たとえば1マイルが何メートルなのかといった情報が保持されているため、単位変換も容易に行うことができる。
JSR 275の詳細な仕様はまだ公開されていないが、科学技術に関連したJavaライブラリの開発を目指すJScienceプロジェクトによって、すでにその参照実装が公開されている。まだ仕様が決まっていないため今後も大きな変更が加えられる可能性はあるが、今回は現時点での実装を用いてUnit Specificationの使用例を紹介しよう。
JSR 275の使用例
JSR 275の参照実装はJScienceの公式サイトからダウンロードすることができる。配布ファイルはJScienceで開発されている各種ライブラリがセットになった「jscience-3.3.Beta-bin.zip」で、これに含まれている「jscience.jar」をにクラスパスに含めて使用する。
JSR 275は大きく3つのパートに分かれており、それぞれ次に挙げるパッケージでまとめられている。
- java.measure.units: 単位を表すクラス/インタフェースが提供される
- javax.measure.quantities: 測定可能な数量を表すクラス/インタフェースが提供される
- javax.measure.converters: 単位の変換を行うためのクラスが提供される
単位の基本となるのはjava.measure.units.Unitクラスで、このクラスのインスタンスとしてさまざまな単位がjava.measure.units.SIおよびjava.measure.units.NonSIクラスに定義されている。
Unitのクラス定義は「Unit<Q extends Quantity>」となっている。このjavax.measure.quantities.Quantityというのは数量を表すための基底インタフェースで、このインタフェースを継承して長さや重さなどを表すさまざまなインタフェースが多数用意されている。Quantityの定義は「Quantity<Q extends Quantity>」となっているため、自身のサブインタフェースでパラメータ化されることになる。
たとえば、このAPIを利用して単位の変換を行うにはリスト1のようにする。まずSIクラスにはメートル単位を表すSI.METERという定数が用意されており、これをSI.METER()メソッドでキロ単位に変換している。長さの単位を表す型はUnit<Length>となる。続いてgetConverterTo()メソッドでマイル単位であるNonSI.MILEへのコンバータを取得し、convert()メソッドで変換を行っている。
リスト1 UnitSample.java - JSR 275を使用した単位の変換
package apisample;
import javax.measure.quantities.Mass;
import javax.measure.quantities.Scalar;
import javax.measure.units.NonSI;
import javax.measure.units.SI;
public class PersonSample {
public static void main(String[] args) {
Person person = new Person();
// 体重をキログラムでセット
Scalar weight = new Scalar(60, SI.KILOGRAM);
person.setWeight(weight);
Scalar returnWeight = (Scalar)person.getWeight();
System.out.println(weight.doubleValue(SI.KILOGRAM) + "キログラム");
System.out.println(weight.doubleValue(NonSI.POUND) + "ポンド");
// 体重をポンドでセット
weight = new Scalar(140, NonSI.POUND);
person.setWeight(weight);
returnWeight = (Scalar)person.getWeight();
System.out.println(weight.doubleValue(SI.KILOGRAM) + "キログラム");
System.out.println(weight.doubleValue(NonSI.POUND) + "ポンド");
// System.out.println(weight.doubleValue(SI.METER) + "メートル"); // コンパイルエラー
}
}
このプログラムの実行結果はプロンプト2のようになる。
プロンプト2
> java [PATH_TO_JSCIENCE] UnitSample
100キロ = 62.1371192237334マイル
JSR 275をリスト2のように使うと、オブジェクトのプロパティに入れる値の単位を明確にすることができる。Personクラスの場合、weightプロパティは体重を表すようにしたいので、javax.measure.quantities.Massを利用してQuantity<Mass>型にしてある。
リスト2 Person.java
package apisample;
import javax.measure.quantities.Mass;
import javax.measure.quantities.Quantity;
public class Person {
private Quantity weight;
public void setWeight(Quantity weight) {
this.weight = weight;
}
public Quantity getWeight() {
return this.weight;
}
}
Personクラスはリスト3のようにして使う。Quantityを実装したクラスは、スカラー値を表すjavax.measure.quantities.Scalarクラスのみ用意されている。今回は、数値と単位(SI.KILOGRAM)を指定して体重のスカラー値(Scalar<Mass>)を作成して、それをPersonオブジェクトにセットしている。
Quantityインタフェースに宣言されているdoubleValue()およびlongValue()メソッドは、引数に任意のUnitを指定すれば、数値をその単位に自動的に変換して返してくれる。したがって今回はweight.doubleValue(SI.KILOGRAM)のようにすればキログラム単位の値が得られ、weight.doubleValue(NonSI.POUND)とすればポンド単位の値が得られる。変換に対応していないUnitが指定された場合は当然コンパイルできない。
リスト3 PersonSample.java
package apisample;
import javax.measure.quantities.Mass;
import javax.measure.quantities.Scalar;
import javax.measure.units.NonSI;
import javax.measure.units.SI;
public class PersonSample {
public static void main(String[] args) {
Person person = new Person();
// 体重をキログラムでセット
Scalar weight = new Scalar(60, SI.KILOGRAM);
person.setWeight(weight);
Scalar returnWeight = (Scalar)person.getWeight();
System.out.println(weight.doubleValue(SI.KILOGRAM) + "キログラム");
System.out.println(weight.doubleValue(NonSI.POUND) + "ポンド");
// 体重をポンドでセット
weight = new Scalar(140, NonSI.POUND);
person.setWeight(weight);
returnWeight = (Scalar)person.getWeight();
System.out.println(weight.doubleValue(SI.KILOGRAM) + "キログラム");
System.out.println(weight.doubleValue(NonSI.POUND) + "ポンド");
// System.out.println(weight.doubleValue(SI.METER) + "メートル"); // コンパイルエラー
}
}
プロンプト2 PersonSampleの実行例
> java [PATH_TO_JSCIENCE] PersonSample
60.0キログラム
132.27735731092656ポンド
63.5029318キログラム
140.0ポンド
JSR 275には今回紹介した以外にも、各Unitの表記を取得する機能や、単位の次元を取得する機能、次元の互換性を判定する機能などを備えている。また、各Unitどうしの演算を行うこともできる。JSR 275は決して目を見張るような派手な機能を提供してくれるAPIではないが、これまで曖昧だった数値の扱いをより明確にすることで、プログラムの可読性や安全性を向上させてくれることだろう。