1. Обзор
В этой статье мы представим API единиц измерения, который обеспечивает унифицированный способ представления мер и единиц измерения в 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 .
Если разработчик ошибочно передает значение с другой единицей измерения, чем та, которую мы ожидаем, это может привести к серьезным ошибкам в расчетах. Такие ошибки очень трудно обнаружить и устранить.
API JSR-363 предоставляет нам интерфейсы Quantity и Unit , которые устраняют эту путаницу и оставляют подобные ошибки вне сферы действия нашей программы.
3.1. Простой Пример
Теперь давайте рассмотрим и посмотрим, как это может быть полезно в нашем примере.
Как упоминалось ранее, JSR-363 содержит интерфейс Quantity , который представляет количественное свойство , такое как объем или площадь. Библиотека предоставляет множество подинтерфейсов, которые моделируют наиболее часто используемые количественные атрибуты. Некоторые примеры: Объем , Длина , Электрический заряд , Энергия , Температура .
Мы можем определить объект Quantity , в котором должно храниться количество воды в нашем примере:
public class WaterTank { public void setCapacityMeasure(QuantitycapacityMeasure); }
Помимо интерфейса Quantity , мы также можем использовать интерфейс Unit для определения единицы измерения для свойства . Определения часто используемых единиц измерения можно найти в библиотеке unit-ri , например: КЕЛЬВИН , МЕТР , НЬЮТОН , ЦЕЛЬСИЙ .
Объект типа Quantity расширяет Quantity> имеет методы для извлечения единицы измерения и значения: getUnit() и GetValue() . расширяет Quantity>
Давайте рассмотрим пример, чтобы установить значение для количества воды:
@Test public void givenQuantity_whenGetUnitAndConvertValue_thenSuccess() { WaterTank waterTank = new WaterTank(); waterTank.setCapacityMeasure(Quantities.getQuantity(9.2, LITRE)); assertEquals(LITRE, waterTank.getCapacityMeasure().getUnit()); QuantitywaterCapacity = waterTank.getCapacityMeasure(); double volumeInLitre = waterCapacity.getValue().doubleValue(); assertEquals(9.2, volumeInLitre, 0.0f); }
Мы также можем быстро преобразовать этот Объем в литр в любую другую единицу измерения:
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. Параметризация классов
Чтобы поддерживать согласованность измерений, фреймворк, естественно, использует преимущества дженериков.
Классы и интерфейсы параметризуются по типу их количества, что позволяет проверять наши единицы измерения во время компиляции. Компилятор выдаст ошибку или предупреждение на основе того, что он может идентифицировать:
UnitKilometer = MetricPrefix.KILO(METRE); Unit Centimeter = MetricPrefix.CENTI(LITRE); // compilation error
Всегда есть возможность обойти проверку типа с помощью метода asType() :
Unitinch = CENTI(METER).times(2.54).asType(Length.class);
Мы также можем использовать подстановочный знак, если мы не уверены в типе количества:
Unit> kelvinPerSec = KELVIN.divide(SECOND);
4. Преобразование Единиц измерения
Единица измерения s может быть получена из Системы единиц измерения . Эталонная реализация спецификации содержит реализацию интерфейса Units , которая предоставляет набор статических констант, представляющих наиболее часто используемые единицы измерения.
Кроме того, мы также можем создать совершенно новую пользовательскую единицу или создать единицу, применив алгебраические операции к существующим единицам.
Преимущество использования стандартной единицы измерения заключается в том, что мы не сталкиваемся с ловушками преобразования.
Мы также можем использовать префиксы или множители из класса Metric Prefix , такие как KILO(Unit unit) и CENTS(Unit unit) , которые эквивалентны умножению и делению на степень 10 соответственно.
Например, мы можем определить “Километр” и “Сантиметр” как:
UnitKilometer = MetricPrefix.KILO(METRE); Unit Centimeter = MetricPrefix.CENTI(METRE);
Их можно использовать, когда нужный нам блок недоступен напрямую.
4.1. Пользовательские Единицы Измерения
В любом случае, если единица не существует в системе единиц, мы можем создать новые единицы с новыми символами:
- AlternateUnit – новая единица измерения с тем же размером, но другим символом и характером
- ProductUnit – новая единица, созданная как произведение рациональных мощностей других единиц
Давайте создадим несколько пользовательских единиц, используя эти классы. Пример Альтернативного блока для давления:
@Test public void givenUnit_whenAlternateUnit_ThenGetAlternateUnit() { UnitPASCAL = NEWTON.divide(METRE.pow(2)) .alternate("Pa").asType(Pressure.class); assertTrue(SimpleUnitFormat.getInstance().parse("Pa") .equals(PASCAL)); }
Аналогично, пример Единицы продукции и ее преобразования:
@Test public void givenUnit_whenProduct_ThenGetProductUnit() { Unit squareMetre = METRE.multiply(METRE).asType(Area.class); Quantityline = Quantities.getQuantity(2, METRE); assertEquals(line.multiply(line).getUnit(), squareMetre); }
Здесь мы создали квадратный метр составную единицу, умножив МЕТР на себя.
Далее, помимо типов единиц, фреймворк также предоставляет класс Unit Converter , который помогает нам преобразовать одну единицу в другую или создать новую производную единицу под названием 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 интерфейс , который связывает общесистемные метки с Единицами .
Давайте проверим метки некоторых системных блоков, используя реализацию Simple Unit Format :
@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() { Quantitytotal = Quantities.getQuantity(2, METRE) .add(Quantities.getQuantity(3, METRE)); assertEquals(total.getValue().intValue(), 5); }
Методы также проверяют Единицы измерения объектов, с которыми они работают. Например, попытка умножить метры на литры приведет к ошибке компиляции:
// compilation error Quantitytotal = Quantities.getQuantity(2, METRE) .add(Quantities.getQuantity(3, LITRE));
С другой стороны, можно добавить два объекта, выраженных в единицах измерения, которые имеют одинаковое измерение:
QuantitytotalKm = Quantities.getQuantity(2, METRE) .add(Quantities.getQuantity(3, MetricPrefix.KILO(METRE))); assertEquals(totalKm.getValue().intValue(), 3002);
В этом примере единицы измерения метр и километр соответствуют измерению Длина , поэтому их можно добавить. Результат выражается в единице первого объекта.
6. Заключение
В этой статье мы увидели, что Units of Measurement API дает нам удобную модель измерения. И, помимо использования Количества и Единицы , мы также увидели, насколько удобно конвертировать одну единицу в другую несколькими способами.
Для получения дополнительной информации вы всегда можете проверить проект здесь .
И, как всегда, весь код доступен на GitHub .