1. Overview
In this article, we’ll introduce the javax.measure library – which provides a unified way of representing measures and units in Java.
While working with a program containing physical quantities, we need to remove the uncertainty about units used. It’s essential that we manage both the number and its unit to prevent errors in calculations.
JSR-275, also known as javax.measure library, helps us save the development time, and at the same time, makes the code more readable.
2. Maven Dependency
Let’s simply start with the Maven dependency to pull in the library:
<dependency> <groupId>javax.measure</groupId> <artifactId>jsr-275</artifactId> <version>0.9.1</version> </dependency>
The latest version can be found over on Maven Central.
3. Exploring javax.measure
Let’s have a look at the example where we want to store water in a tank.
The legacy implementation would look like this:
public class WaterTank { public void setWaterQuantity(double quantity); }
As we can see, the above code does not mention the unit of quantity of water and is not suitable for precise calculations because of the presence of the double type.
If a developer mistakenly passes the value with a different unit of measure than the one we’re expecting, it can lead to serious errors in calculations. Such errors are very hard to detect and resolve.
javax.measure provides us with Measure/Measurable, which resolves this confusion and leaves these kinds of errors out of our program’s scope.
3.1. Simple Example
Now, let’s explore and see how this can be useful in our example.
As mentioned earlier, javax.measure contains a dedicated class for representing measurements – the abstract class Measure – simply made up of a numeric value and a unit, and an interface Measurable – represents a measurable, countable, or a comparable quantity e.g. altitude, delay, etc.
We can define the Measure object, which should store the quantity of water:
public class WaterTank { public void setWaterQuantity(Measure<Volume> quantity); }
We can also implement Measurable, to achieve the same thing:
public class WaterTank implements Measurable<Volume> { ... }
Measurable is an equivalent to Number and provides methods for conversion to primitive types.
Let’s see an example to set the value for the quantity of water:
@Test public void givenMeasure_whenGetUnitAndConvertValue_thenSuccess() { waterTank.setQuantity(Measure.valueOf(9.2, LITRE)); Measure<Volume> waterQuantity = waterTank.getQuantity(); assertEquals(LITRE, waterQuantity.getUnit()); assertEquals(9.2, waterQuantity.getValue().doubleValue(), 0.0f); }
We can also convert this Volume in Litre to any other unit quickly:
double volumeInMilliLitre = waterQuantity.doubleValue(SI.MILLI(LITRE)); assertEquals(9200.0, volumeInMilliLitre, 0.0f);
But, when we try to convert the amount of water into another unit – which is not of type Volume, we get a compilation error:
// Compilation Error double volumeInLitre = waterQuantity.doubleValue(SI.KILOGRAM);
There’s no restriction on which one to use out of Measure and Measurable. Measurable is more flexible, whereas Measure is straightforward and easy to use.
Just note that, if we want to retrieve the original numeric value with the original unit, Measure is the only choice.
3.2. Class Parameterization
To maintain the dimension consistency, the framework naturally takes advantage of generics.
Classes and interfaces are parameterized by their quantity type, which makes it possible to have our units checked at compile time. The compiler will give an error or warning based on what it can identify:
Unit<Length> inch = CENTI(METER).times(3.64); // OK Unit<Length> inch = CENTI(LITRE).times(3.64); // Compile error
There’s always a possibility of bypassing the type check using the asType() method:
Unit<Length> inch = CENTI(METER).times(2.54).asType(Length.class);
We can also use a wildcard if we are not sure of the type of quantity:
Unit<?> kelvinPerSec = KELVIN.divide(SECOND);
4. Unit Conversion
Units can be retrieved from SystemOfUnits. There are some subclasses of this: SI, NonSI, etc. We can also create an entirely new custom unit or create a unit by applying algebraic operations on existing units.
The benefit of using a standard unit is that we don’t run into the conversion pitfalls.
The SI class provides prefixes, or multipliers, like KILO(Unit<Q> unit) and CENTI(Unit<Q> unit), which are equivalent to multiply and divide by a power of 10 respectively.
For example, we can define “Kilometer” and “Centimeter” as:
Unit<Length> kilometer = SI.KILO(METER); Unit<Length> centimeter = SI.CENTI(METER);
These can be used when a unit we want is not available directly.
4.1. Custom Units
In any case, if a unit doesn’t exist in the SI or NonSI system of units, we can create new units with new symbols:
- AlternateUnit – a new unit with the same dimension but different symbol and nature
- CompoundUnit – a combination of several units
Let’s create some custom units using these classes. An example of AlternateUnit for pressure:
@Test public void givenMeasure_whenAlternateMeasure_ThenGetAlternateMeasure() { Unit<Pressure> PASCAL = NEWTON.divide(METER.pow(2)) .alternate("Pa"); assertTrue(Unit.valueOf("Pa").equals(PASCAL)); }
Similarly, an example of CompoundUnit and its conversion:
@Test public void givenMeasure_whenCompoundMeasure_ThenGetCompoundMeasure() { Unit<Duration> HOUR_MINUTE_SECOND = HOUR.compound(MINUTE) .compound(SECOND); Measure<Duration> duration = Measure.valueOf(12345, SECOND); assertEquals("3h25min45s",duration.to(HOUR_MINUTE_SECOND).toString()); }
Here, we have created a compound unit for the duration which consists of hours, minutes and seconds.
The framework also provides a UnitConverter class, which helps us convert one unit to another, or create a new derived unit called TransformedUnit.
Let’s see an example to turn the unit of a double value, from miles to kilometers:
@Test public void givenMiles_whenConvertToKilometer_ThenConverted() { double distanceInMiles = 50.0; UnitConverter mileToKilometer = MILE.getConverterTo(KILO(METER)); double distanceInKilometers = mileToKilometer.convert(distanceInMiles); assertEquals(80.4672, distanceInKilometers, 0.00f); }
Some units are location-specific and some are not. To parse and format both these types, we have two abstract classes derived from java.util.Format – UnitFormat and MeasureFormat.
Both of these classes provide the locale-sensitive instances as well as locale-insensitive instances to facilitate unambiguous electronic communication of quantities with their units.
The UnitFormat recognizes some of the SI prefixes, which are used to form decimal multiples and submultiples of SI units:
@Test public void givenSymbol_WhenCompareToSystemUnit_ThenSuccess() { assertTrue(Unit.valueOf("kW") .equals(SI.KILO(SI.WATT))); assertTrue(Unit.valueOf("ms").equals(SI.SECOND.divide(1000))); }
Note that Unit.valueOf(String) and Unit.toString() methods are locale-sensitive. Thus, they should be avoided while dealing with locale specific units.
5. Conclusion
In this article, we saw that javax.measure gives us a convenient measurement model. And, apart from the usage ofMeasure and Measurable, we also saw how convenient it is to convert one unit to another, in a number of ways.
For further information, you can always check out documentation here.
And, as always, the entire code is available over on GitHub.