Введение в javax.measure

Введение в javax.measure

1. обзор

В этой статье мы познакомим вас с API единиц измерения, который предоставляетa unified way of representing measures and units in Java.

При работе с программой, содержащей физические величины, нам нужно устранить неопределенность в отношении используемых единиц. Важно, чтобы мы управляли как числом, так и его единицей, чтобы не допустить ошибок в расчетах.

JSR-363 (бывшая библиотекаJSR-275 илиjavax.measure) помогает нам сэкономить время разработки и в то же время делает код более читабельным.

2. Maven Зависимости

Давайте просто начнем с зависимости Maven для извлечения библиотеки:


    javax.measure
    unit-api
    1.0

Последнюю версию можно найти наMaven Central.

Проектunit-api содержит набор интерфейсов, которые определяют, как работать с количествами и единицами. В качестве примеров мы будем использовать эталонную реализациюJSR-363, то естьunit-ri:


    tec.units
    unit-ri
    1.0.3

3. Изучение API

Давайте посмотрим на пример, когда мы хотим хранить воду в резервуаре.

Устаревшая реализация будет выглядеть так:

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

Как мы видим, в приведенном выше коде не упоминается единица количества воды и он не подходит для точных вычислений из-за наличия типаdouble.

Если разработчик ошибочно передает значение с единицей измерения, отличной от той, которую мы ожидаем, это может привести к серьезным ошибкам в расчетах. Такие ошибки очень трудно обнаружить и устранить.

The JSR-363 API provides us with the Quantity and Unit interfaces,, которые разрешают эту путаницу и оставляют подобные ошибки за пределами нашей программы.

3.1. Простой пример

Теперь давайте посмотрим, как это может быть полезно в нашем примере.

Как упоминалось ранее,JSR-363 содержитthe Quantity interface which represents a quantitative property, например объем или площадь. Библиотека предоставляет множество подчиненных интерфейсов, которые моделируют наиболее часто используемые количественные атрибуты. Вот некоторые примеры:Volume,Length,ElectricCharge,Energy,Temperature.

Мы можем определить объектQuantity<Volume>, который должен хранить количество воды в нашем примере:

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

Помимо интерфейсаQuantity,we can also use the Unit interface to identify the unit of measurement for a property. Определения для часто используемых единиц можно найти в библиотекеunit-ri, например:KELVIN,METRE,NEWTON,CELSIUS.

У объекта типаQuantity<Q extends Quantity<Q>> есть методы для получения единицы и значения:getUnit() иgetValue().

Давайте посмотрим на пример установки значения количества воды:

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

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

Мы также можем быстро преобразовать этотVolume вLITRE в любую другую единицу:

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 Kilometer = MetricPrefix.KILO(METRE);
Unit Centimeter = MetricPrefix.CENTI(LITRE); // compilation error

Всегда есть возможность обойти проверку типа с помощью методаasType():

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

Мы также можем использовать подстановочный знак, если не уверены в типе количества:

Unit kelvinPerSec = KELVIN.divide(SECOND);

4. Преобразование единиц

Units можно получить изSystemOfUnits. Эталонная реализация спецификации содержит реализацию интерфейсаUnits, которая предоставляет набор статических констант, представляющих наиболее часто используемые единицы измерения.

Кроме того, мы также можем создать совершенно новую пользовательскую единицу или создать единицу, применяя алгебраические операции к существующим единицам.

Преимущество использования стандартных единиц измерения заключается в том, что мы не сталкиваемся с ошибками при конвертации.

Мы также можем использовать префиксы или множители из классаMetricPrefix, такие какKILO(Unit<Q> unit) иCENTI(Unit<Q> unit), которые эквивалентны умножению и делению на степень 10 соответственно.

Например, мы можем определить «Километр» и «Сантиметр» как:

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

Их можно использовать, когда нужный нам объект недоступен напрямую.

4.1. Пользовательские единицы

В любом случае, если единицы не существует в системе единиц, мы можем создать новые единицы с новыми символами:

  • AlternateUnit – новый блок с тем же размером, но с другим символом и характером

  • ProductUnit – новая единица, созданная как продукт рациональных возможностей других единиц

Давайте создадим несколько пользовательских единиц, используя эти классы. ПримерAlternateUnit для давления:

@Test
public void givenUnit_whenAlternateUnit_ThenGetAlternateUnit() {
    Unit 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 squareMetre = METRE.multiply(METRE).asType(Area.class);
    Quantity line = Quantities.getQuantity(2, METRE);
    assertEquals(line.multiply(line).getUnit(), squareMetre);
}

Здесь мы создали составную единицуsquareMetre, умноживMETRE на себя.

Кроме типов единиц, фреймворк также предоставляет классUnitConverter, который помогает нам преобразовать одну единицу в другую или создать новую производную единицу, называемуюTransformedUnit.

Давайте посмотрим, как изменить единицу измерения двойного значения из метров в километры:

@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,, который связывает общесистемные метки сUnits.

Давайте проверим метки некоторых системных модулей, используя реализацию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(),subtract(),multiply(),divide(). Используя их, мы можем выполнять операции между объектамиQuantity:

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

Эти методы также проверяютUnits объектов, над которыми они работают. Например, попытка умножить метры на литры приведет к ошибке компиляции:

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

С другой стороны, можно добавить два объекта, выраженные в единицах измерения одинакового размера:

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

В этом примере единицы измерения метры и километры соответствуют размеруLength, поэтому их можно добавить. Результат выражается в единицах первого объекта.

6. Заключение

В этой статье мы увидели, чтоUnits of Measurement API дает нам удобную модель измерения. И, помимо использованияQuantity иUnit, мы также увидели, насколько удобно преобразовывать одну единицу в другую несколькими способами.

Для получения дополнительной информации вы всегда можете проверитьthe project here.

И, как всегда, доступен весь кодover on GitHub.