1. Обзор
В этой статье мы познакомимся с Units of Measurement API, который обеспечивает унифицированный способ представления мер и единиц в Java .
При работе с программой, содержащей физические величины, нам необходимо устранить неопределенность в отношении используемых единиц измерения. Очень важно, чтобы мы управляли как числом, так и его единицей измерения, чтобы предотвратить ошибки в вычислениях.
JSR-363
(ранее JSR-275
или библиотека javax.measure
) помогает нам сэкономить время разработки и в то же время делает код более читабельным.
2. Зависимости Maven
Давайте просто начнем с зависимости Maven, чтобы получить библиотеку:
<dependency>
<groupId>javax.measure</groupId>
<artifactId>unit-api</artifactId>
<version>1.0</version>
</dependency>
Последнюю версию можно найти на Maven Central .
Проект 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
.
Если разработчик по ошибке передает значение с единицей измерения, отличной от той, которую мы ожидаем, это может привести к серьезным ошибкам в вычислениях. Такие ошибки очень трудно обнаружить и устранить.
API JSR-363
предоставляет нам интерфейсы Quantity
и Unit
,
которые устраняют эту путаницу и оставляют подобные ошибки за рамками нашей программы.
3.1. Простой пример
Теперь давайте рассмотрим и посмотрим, как это может быть полезно в нашем примере.
Как упоминалось ранее, JSR-363
содержит интерфейс Quantity
, который представляет количественное свойство , такое как объем или площадь. Библиотека предоставляет множество подинтерфейсов, которые моделируют наиболее часто используемые количественные атрибуты. Некоторые примеры: Volume
, Length
, ElectricCharge
, Energy
, Temperature
.
Мы можем определить объект Quantity<Volume>
, который должен хранить количество воды в нашем примере:
public class WaterTank {
public void setCapacityMeasure(Quantity<Volume> capacityMeasure);
}
Помимо интерфейса Quantity ,
мы также можем использовать интерфейс Unit
для определения единицы измерения свойства . Определения часто используемых единиц можно найти в библиотеке unit-ri
, например: КЕЛЬВИН
, МЕТР
, НЬЮТОН
, ЦЕЛЬСИЙ
.
Объект типа 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<Volume> waterCapacity = 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. Параметризация класса
Чтобы поддерживать согласованность измерений, платформа естественным образом использует преимущества дженериков.
Классы и интерфейсы параметризуются по типу количества, что позволяет проверять наши единицы во время компиляции. Компилятор выдаст ошибку или предупреждение в зависимости от того, что он может идентифицировать:
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. Преобразование единиц
`
Units можно получить из
SystemOfUnits . Эталонная реализация спецификации содержит реализацию интерфейса
Units` , которая предоставляет набор статических констант, представляющих наиболее часто используемые единицы измерения.
Кроме того, мы также можем создать совершенно новую пользовательскую единицу или создать единицу, применяя алгебраические операции к существующим единицам.
Преимущество использования стандартной единицы заключается в том, что мы не сталкиваемся с ловушками преобразования.
Мы также можем использовать префиксы или множители из класса MetricPrefix , такие как
KILO(Unit<Q> unit)
и CENTI(Unit<Q> unit)
, которые эквивалентны умножению и делению на степень 10 соответственно.
Например, мы можем определить «километр» и «сантиметр» как:
Unit<Length> Kilometer = MetricPrefix.KILO(METRE);
Unit<Length> Centimeter = MetricPrefix.CENTI(METRE);
Их можно использовать, когда нужная нам единица недоступна напрямую.
4.1. Пользовательские единицы
В любом случае, если единица не существует в системе единиц, мы можем создать новые единицы с новыми символами:
AlternateUnit —
новая единица с тем же размером, но другим символом и природой.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);
}
Здесь мы создали составную единицу SquareMetre
, умножив METER
на себя.
Помимо типов единиц, платформа также предоставляет класс 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
,
который связывает общесистемные метки с единицами измерения
.
Проверим метки некоторых системных единиц с помощью реализации 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
содержит методы для наиболее распространенных математических операций: сложение()
, вычитание()
, умножение()
, деление()
. Используя их, мы можем выполнять операции между объектами 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));
С другой стороны, можно добавить два объекта, выраженных в единицах измерения, имеющих одинаковую размерность:
Quantity<Length> totalKm = Quantities.getQuantity(2, METRE)
.add(Quantities.getQuantity(3, MetricPrefix.KILO(METRE)));
assertEquals(totalKm.getValue().intValue(), 3002);
В этом примере метры и километры соответствуют измерению длины
, поэтому их можно добавить. Результат выражается в единицах измерения первого объекта.
6. Заключение
В этой статье мы увидели, что Units of Measurement API
дает нам удобную модель измерения. И, помимо использования Quantity
и Unit
, мы также увидели, насколько удобно конвертировать одну единицу в другую несколькими способами.
Для получения дополнительной информации вы всегда можете ознакомиться с проектом здесь .
И, как всегда, весь код доступен на GitHub .