はじめに
プログラムにおいて、長さ、質量、時間、電流、温度などのさまざまな物理量(測定量)を扱うことがあります。これらの物理量をプログラム中で取り扱う際は、整数や浮動小数点数の型の変数、Javaであればint, long, doubleといった型の変数を使うことが多いでしよう。
たとえば、東京からサンフランシスコまで飛行機で移動するのにかかる時間を計算するコードは次のようになります。
int distance = 5130; // 東京からサンフランシスコ(マイル)
double speed = 925.0; // 巡航速度(km/h)
double requiredTime = distance / speed; // 所要時間(h)
このコードには、敢えて単位の扱いにバグ1を含んでいます。バグがありますが、プログラムをコンパイルしてもエラーにはならず、実行することができ、パッと見には、それらしく計算して結果を出してしまいます。
Javaにおいて、このような物理量を取り扱う際に単位が異なる場合、エラーとして検出できる専門のAPIがないか探してみたところ、Javaの標準化活動を行っているJCPにおいて、JSR 363 Units of Measurement API(訳すと「測定単位」?)が活動中ということを知りました。
https://jcp.org/en/jsr/detail?id=363
このJSRは、ちょうどPublic Reviewをしているところです。上述URLから仕様書案と参照実装がダウンロードできるようになっています。また、仕様を策定しているExpert Groupのメンバーによって参照実装と追加ライブラリ、サンプルなどが以下のGithubで公開されています。
https://github.com/unitsofmeasurement
このJSR 363について少し調べてみたことを調査メモとして簡単に紹介します。
JSR 363
経緯
物理量の単位については、2001年にJSR 108 Units Specificationが活動を開始しました。しかし、この活動は途中で中断してしまいました。
次に、2005年にJSR 275 Units Specificationが活動を開始しました。これはJ2SE(Java SE)入りを狙ったものですが、2010年にJCPのEC(最終決定機関)で採択が否決されてさいました[^jsr275reject]。ECメンバーの否決コメントを見ると、Java SEの標準APIたるには未熟といったところのようです。
しかし、Java SE入りは果たせなかったものの、JSR 275のAPIはJScienceなどのオープンソースライブラリに取り入れられて使われています。
その後、2014年にJSR 363 Units of Measurement APIが活動を開始しました。2015年11月に仕様案が公開されています(Public Review)。来年初めに採決が実施される予定と思われます。
なお、JSR 363は、2015年のJCP Most Significant JSRという賞を獲得しました。
https://blogs.oracle.com/jcp/entry/jcp_2015_annual_award_winners
対象とするJava環境
Java SE 6とJava ME 8での動作を対象としている記述が仕様案にありました。参照実装でもCLDC 1.8を対象に含めているそうです。
JSR 363 APIのごく簡単な概要
JSR 363のパッケージ構成は次のようになります。
- javax.measure
- javax.measure.format
- javax.measure.quantity
- javax.measure.spi
JSR 363では、javax.measureを必須パッケージとし、それ以外はオプションパッケージと定義しています。組込みの最小限の環境では、javax.measureパッケージだけサポートするということが可能になります。
また、APIはインタフェース型で定義されるので、JSR 363の実装を提供するライブラリでインスタンスを生成するファクトリ等を提供することになります。
javax.measureパッケージ
4つのインタフェースがあります。
- Dimension
- Quantity
- Unit
- UnitConverter
Unit(単位)、Quantity(量)、Dimensin(次元)は、国際単位系(SI単位系)で用いられる概念です。
プログラムで扱う諸物理量(Quantity)は、値と単位(Unit)で定義されます。また、単位(Unit)には次元(Dimension)があります。
例えば、長さを表す100m という物理量は、100という値、メートルという単位、長さという次元で表されます。
javax.measure.quantityパッケージ
SI単位系で定義される7つの基本量と、基本量の組合せで定義される多数の組立量のインタフェースがあります。
7つの基本量とそれを表すインタフェース
基本量 | インタフェース |
---|---|
長さ | Length |
質量 | Mass |
時間 | Time |
電流 | ElectricCurrent |
熱力学温度 | Temperature |
物質量 | AmountOfSubstance |
光度 | LuminousIntensity |
組立量は、複数の基本量の組合せで定義される量を表すインタフェースです。例えば、面積(Area)は、長さの積で定義されます。速度(Speed)は、長さ/時間で定義されます。
コードで具体例をみる
冒頭で記載した東京ーサンフランシスコの距離、速度、時間の例を、JSR 363 APIを使ったコードで記述します。
まず、距離(5130マイル)を表現するコードは次になります。
Quantity<Length> distance = Quantities.getQuantity(5130, US.MILE);
距離は基本量の長さなので、Quantity型を使います。
インスタンスを生成するには、JSR 363の参照実装で用意しているQuantitiesクラスのファクトリメソッドを使用し、数と単位を指定します。単位で使用するマイルはSI単位系ではないので、参照実装とは別に存在するライブラリを使い、ヤード・ポンド法の単位系を定義するクラス(コード例ではUS.MILE)を使っています。
次に、速度(925km/h)を表現するコードは次になります。
Quantity<Speed> speed = Quantities.getQuantity(925, Units.KILOMETERS_PER_HOUR);
速度は定義済みの組立量なので、Quantity型を使います。単位の指定はSI単位系の単位を定義したUnitsクラスを使っています。
次に、時間を距離と速度から計算するコードは次になります。
Quantity<Time> requiredTime = distance.divide(speed).asType(Time.class);
時間は基本量なので、Quantity型を使います。
時間は、距離を時間で除算して計算します。Quantityインタフェースには、四則演算用のメソッドadd、subtract、multiply、divideと逆数のinverseがあります。
ここは、距離の単位MILEと、speedの単位KILOMETERS_PER_HOURとの間で変換方法をライブラリ側が知っているので、プログラマが明示的に変換ロジックを実装する必要がありません。
計算結果の時間を、単位H(Hour)としたときの数値で取得するには次のようにします。
System.out.println("所要時間(H)=" + requiredTime.to(Units.HOUR));
計算の際に順序を間違えた場合、コンパイルは通りますが実行時に例外がスローされました。
Quantity<Time> requiredTime = speed.divide(distance).asType(Time.class);
と、誤って速度÷距離としてしまった場合、実行時に次の例外が発生しています。
java.lang.ClassCastException: The unit: kph/m*201168.0/125.0 is
not compatible with quantities of type interface
javax.measure.quantity.Time
ここで、"kph/m*201168.0/125.0"とちょっと複雑なのは、マイルとメートルの変換が表現されているためです。
このように、物理量を値と単位とを合わせて表現し、演算することで単位の扱いミスによるバグを検出することができるようになります。
JSR 363 参照実装
JSR 363 のAPIおよび参照実装は、Expert GroupメンバーらによってGithubリポジトリで開発・公開されています。
https://github.com/unitsofmeasurement
参照実装のユーザーガイドが公開されています。
https://www.gitbook.com/book/unitsofmeasurement/unit-ri-userguide/details
JSR 363の活動内容、講演の動画・スライドなどは、次のURLにあります。
http://unitsofmeasurement.github.io/
参考)国際単位系(SI単位系)
JSR 363を紐解くと、Quantity、Unit、BaseUnit、DerivedUnit といったクラスが登場します。これらを理解するには、最初に単位に関する国際規格としてSI単位系をさらっと把握しておくのが早道かと思います。
SI単位系の規格は、当初ISO 1000(1992年)で規格化され、その後ISO 80000-1(2009年)に置き換えられています。JIS規格では、ISO 1000に対応したJIS Z 8203が、ISO 80000-1に対応したJIS Z 8000-1(2014年)が出ています。
SI単位系について、産業技術研究所 計量標準総合センターのWebサイトに、SIパンフレットとSI文書(邦訳あり)が掲載されています。
フォローアップ
JSR 363の参照実装を使ったプログラミング、今後の動向などは次のWikiページに記載していきます。
http://www.torutk.com/projects/swe/wiki/JSR363
-
距離の単位をマイルからkmに変換してから計算する必要があります。 ↩