1. 概要

この記事では、Javaで測定値と単位を表す統一された方法を提供するUnitsofMeasurementAPIを紹介します。

物理量を含むプログラムで作業している間、使用される単位に関する不確実性を取り除く必要があります。 計算の誤りを防ぐために、数とその単位の両方を管理することが不可欠です。

JSR-363 (以前のJSR-275またはjavax.measureライブラリ)は、開発時間を節約すると同時に、コードを読みやすくするのに役立ちます。

2. Mavenの依存関係

ライブラリをプルするためのMaven依存関係から始めましょう。

<dependency>
    <groupId>javax.measure</groupId>
    <artifactId>unit-api</artifactId>
    <version>1.0</version>
</dependency>

最新バージョンは、 MavenCentralにあります。

unit-api プロジェクトには、数量と単位の操作方法を定義する一連のインターフェースが含まれています。 例では、JSR-363のリファレンス実装であるunit-riを使用します。

<dependency>
    <groupId>tec.units</groupId>
    <artifactId>unit-ri</artifactId>
    <version>1.0.3</version>
</dependency>

3. APIの探索

水をタンクに貯めたい例を見てみましょう。

従来の実装は次のようになります。

public class WaterTank {
    public void setWaterQuantity(double quantity);
}

ご覧のとおり、上記のコードは水の量の単位を示しておらず、 double タイプが存在するため、正確な計算には適していません。

開発者が予想とは異なる測定単位で誤って値を渡した場合、計算で重大なエラーが発生する可能性があります。 このようなエラーは、検出して解決するのが非常に困難です。

JSR-363 APIは、数量と単位のインターフェース を提供します。これらのインターフェースは、この混乱を解決し、これらの種類のエラーをプログラムの範囲外にします。

3.1. 簡単な例

それでは、この例でこれがどのように役立つかを調べてみましょう。

前述のように、 JSR-363 には、体積や面積などの定量的プロパティを表すQuantityインターフェイスが含まれています。 ライブラリは、最も一般的に使用される定量化可能な属性をモデル化する多数のサブインターフェイスを提供します。 例としては、 Volume Length ElectricCharge Energy Temperatureがあります。

を定義することができますオブジェクト。この例では、水の量を保存する必要があります。

public class WaterTank {
    public void setCapacityMeasure(Quantity<Volume> capacityMeasure);
}

数量インターフェースに加えて、ユニットインターフェースを使用して、プロパティの測定単位を識別することもできます。 頻繁に使用されるユニットの定義は、 unit-ri ライブラリにあります。たとえば、 KELVIN METRE NEWTON 、[X138X ]摂氏。

タイプのオブジェクト数量> 単位と値を取得するためのメソッドがあります。 単位を取得() getValue()

水の量の値を設定する例を見てみましょう。

@Test
public void givenQuantity_whenGetUnitAndConvertValue_thenSuccess() {
    WaterTank waterTank = new WaterTank();
    waterTank.setCapacityMeasure(Quantities.getQuantity(9.2, LITRE));
    assertEquals(LITRE, waterTank.getCapacityMeasure().getUnit());

    Quantity<Volume> waterCapacity = waterTank.getCapacityMeasure();
    double volumeInLitre = waterCapacity.getValue().doubleValue();
    assertEquals(9.2, volumeInLitre, 0.0f);
}

このボリュームinLITREを他のユニットにすばやく変換することもできます。

double volumeInMilliLitre = waterCapacity
  .to(MetricPrefix.MILLI(LITRE)).getValue().doubleValue();
assertEquals(9200.0, volumeInMilliLitre, 0.0f);

ただし、水の量を Volume タイプではない別の単位に変換しようとすると、コンパイルエラーが発生します。

// compilation error
waterCapacity.to(MetricPrefix.MILLI(KILOGRAM));

3.2. クラスのパラメータ化

ディメンションの一貫性を維持するために、フレームワークは当然ジェネリックを利用します。

クラスとインターフェイスは数量タイプによってパラメータ化されているため、コンパイル時にユニットをチェックできます。 コンパイラーは、識別できる内容に基づいてエラーまたは警告を出します。

Unit<Length> Kilometer = MetricPrefix.KILO(METRE);
Unit<Length> Centimeter = MetricPrefix.CENTI(LITRE); // compilation error

asType()メソッドを使用して型チェックをバイパスする可能性は常にあります。

Unit<Length> inch = CENTI(METER).times(2.54).asType(Length.class);

数量の種類がわからない場合は、ワイルドカードを使用することもできます。

Unit<?> kelvinPerSec = KELVIN.divide(SECOND);

4. 単位変換

Unit は、SystemOfUnitsから取得できます。 仕様のリファレンス実装には、最も一般的に使用されるユニットを表す一連の静的定数を提供するインターフェイスのUnits実装が含まれています。

さらに、まったく新しいカスタムユニットを作成したり、既存のユニットに代数演算を適用してユニットを作成したりすることもできます。

標準ユニットを使用する利点は、変換の落とし穴に遭遇しないことです。

プレフィックスまたは乗数を使用することもできます MetricPrefix クラスのように KILO(ユニットユニット) CENTI(単位) 、これは、それぞれ10の累乗で乗算および除算することに相当します。

たとえば、「キロメートル」と「センチメートル」を次のように定義できます。

Unit<Length> Kilometer = MetricPrefix.KILO(METRE);
Unit<Length> Centimeter = MetricPrefix.CENTI(METRE);

これらは、必要なユニットが直接入手できない場合に使用できます。

4.1. カスタムユニット

いずれにせよ、単位系に単位が存在しない場合は、新しい記号で新しい単位を作成できます。

  • AlternativeUnit – 同じ寸法で、シンボルと性質が異なる新しいユニット
  • ProductUnit –他のユニットの有理数の積として作成された新しいユニット

これらのクラスを使用して、いくつかのカスタムユニットを作成しましょう。 圧力のAlternateUnitの例:

@Test
public void givenUnit_whenAlternateUnit_ThenGetAlternateUnit() {
    Unit<Pressure> PASCAL = NEWTON.divide(METRE.pow(2))
      .alternate("Pa").asType(Pressure.class);
    assertTrue(SimpleUnitFormat.getInstance().parse("Pa")
      .equals(PASCAL));
}

同様に、 ProductUnitとその変換の例:

@Test
public void givenUnit_whenProduct_ThenGetProductUnit() {
    Unit<Area> squareMetre = METRE.multiply(METRE).asType(Area.class);
    Quantity<Length> line = Quantities.getQuantity(2, METRE);
    assertEquals(line.multiply(line).getUnit(), squareMetre);
}

ここでは、 METRE にそれ自体を乗算して、squareMetre複合ユニットを作成しました。

次に、ユニットのタイプに対して、フレームワークは UnitConverter クラスも提供します。これは、あるユニットを別のユニットに変換したり、TransformedUnitという新しい派生ユニットを作成したりするのに役立ちます。

メートルからキロメートルへのdouble値の単位を変換する例を見てみましょう。

@Test
public void givenMeters_whenConvertToKilometer_ThenConverted() {
    double distanceInMeters = 50.0;
    UnitConverter metreToKilometre = METRE.getConverterTo(MetricPrefix.KILO(METRE));
    double distanceInKilometers = metreToKilometre.convert(distanceInMeters );
    assertEquals(0.05, distanceInKilometers, 0.00f);
}

数量とそのユニットとの明確な電子通信を容易にするために、ライブラリは、システム全体のラベルをユニットに関連付ける UnitFormat インターフェイスを提供します。

SimpleUnitFormat 実装を使用して、いくつかのシステムユニットのラベルを確認してみましょう。

@Test
public void givenSymbol_WhenCompareToSystemUnit_ThenSuccess() {
    assertTrue(SimpleUnitFormat.getInstance().parse("kW")
      .equals(MetricPrefix.KILO(WATT)));
    assertTrue(SimpleUnitFormat.getInstance().parse("ms")
      .equals(SECOND.divide(1000)));
}

5. 数量を使用した操作の実行

Quantity インターフェイスには、最も一般的な数学演算のメソッドが含まれています: add() extract() multiply()、[X152X ] divide()。 これらを使用して、Quantityオブジェクト間で操作を実行できます。

@Test
public void givenUnits_WhenAdd_ThenSuccess() {
    Quantity<Length> total = Quantities.getQuantity(2, METRE)
      .add(Quantities.getQuantity(3, METRE));
    assertEquals(total.getValue().intValue(), 5);
}

これらのメソッドは、操作しているオブジェクトのユニットも検証します。 たとえば、メートルにリットルを掛けようとすると、コンパイルエラーが発生します。

// compilation error
Quantity<Length> total = Quantities.getQuantity(2, METRE)
  .add(Quantities.getQuantity(3, LITRE));

一方、同じ寸法の単位で表された2つのオブジェクトを追加できます。

Quantity<Length> totalKm = Quantities.getQuantity(2, METRE)
  .add(Quantities.getQuantity(3, MetricPrefix.KILO(METRE)));
assertEquals(totalKm.getValue().intValue(), 3002);

この例では、メートル単位とキロメートル単位の両方が長さの寸法に対応しているため、追加できます。 結果は、最初のオブジェクトの単位で表されます。

6. 結論

この記事では、測定単位APIが便利な測定モデルを提供することを確認しました。 また、数量単位の使用法とは別に、さまざまな方法で1つの単位を別の単位に変換することがいかに便利であるかを確認しました。

詳細については、いつでもプロジェクトをここでチェックできます。

そして、いつものように、コード全体はGitHub利用できます。