If you’re developing software that operates a lot with physical quantities you absolutely should use a library that defines types for quantities and supports safe conversions between units of measurements. Our go-to library for this in Java was JScience. The latest version of JScience is 4.3.1, which was released in 2012.
Since then a group of developers has formed that strives towards the standardization of a units API for Java. JScience maintainer Jean-Marie Dautelle is actively involved in this effort. The group operates under the name Units of Measurement alongside with their GitHub presence unitsofmeasurement.
Over the years there have been several JSRs (Java Specification Requests) by the group:
- JSR-275 Units Specification
- JSR-363 Units of Measurement API 1.0
- JSR-385 Units of Measurement API 2.0
The current state of affairs is JSR-385, which is the basis of this post. The Units of Measurement API 2.0, or Unit API 2.0 for short, was released in July 2019.
While JScience is distributed as one JAR (~600 KiB), a setup of Unit API involves three JARs (~300 KiB in total):
JScience offers a lot more functionality than just quantities and units, but that’s the part we have been using and what we are interested in.
The unit-api JAR only defines interfaces, which is the scope of JSR-385. So you need an implementation to do anything useful with it. The reference implementation is called Indriya, provided by the second JAR. The third JAR, uom-lib-common, is a utility library used by Indriya for common functionality shared with other projects under the Units of Measurement umbrella.
Here’s a simple use of a physical quantity with JScience, in this example Length:
import org.jscience.physics.amount.Amount; import javax.measure.quantity.Length; import static javax.measure.unit.SI.*; // ... final Amount<Length> d = Amount.valueOf(214, CENTI(METRE)); final double d_metre = d.doubleValue(METRE);
And here’s the equivalent code using Units API 2.0 and Indriya:
import tech.units.indriya.quantity.Quantities; import javax.measure.Quantity; import javax.measure.quantity.Length; import static javax.measure.MetricPrefix.CENTI; import static tech.units.indriya.unit.Units.METRE; // ... final Quantity<Length> d = Quantities.getQuantity(214, CENTI(METRE)); final double d_metre = d.to(METRE).getValue().doubleValue();
While JScience also defines aliases with alternative spellings like METER and constants for many prefixed units like CENTIMETER or MILLIMETER, Indriya encourages consistency and only allows METRE, CENTI(METRE), MILLI(METRE).
Most quantities have the same names in both projects, but there are some differences:
- Amount<Duration> becomes Quantity<Time>
- Amount<Velocity> becomes Quantity<Speed>
In these cases Unit API uses the correct SI names, i.e. time and speed. Wikipedia explains the difference between speed and velocity.
The method names for the elementary arithmetic operations have changed:
- plus() becomes add()
- minus() becomes subtract()
- times() becomes multiply()
Only the method name for division is the same:
- divide() is still divide()
However, the runtime exceptions thrown on division by zero are different:
- JScience: java.lang.ArithmeticException: / by zero
- Indriya: java.lang.IllegalArgumentException: cannot initalize a rational number with divisor equal to ZERO
If you divide or multiply two quantities the Java type system needs a type hint, because it doesn’t know the resulting quantity. Here’s how this looks in JScience versus Unit API:
Amount<Area> a = Amount.valueOf(100, SQUARE_METRE); Amount<Length> b = Amount.valueOf(10, METRE); Amount<Length> c = a.divide(b) .to(METRE);
With Unit API:
Quantity<Area> a = Quantities.getQuantity(100, SQUARE_METRE); Quantity<Length> b = Quantities.getQuantity(10, METRE); Quantity<Length> c = a.divide(b) .asType(Length.class);
If you want to compare quantities via compareTo(), isLessThan(), etc. you need quantities of type ComparableQuantity. The Quantities.getQuantity() factory method returns a ComparableQuantity, which is a sub-interface of Quantity.
Defining custom units
Defining custom units is very similar to JScience. Here’s an example for degree (angle), which is not an SI unit:
public static final Unit<Angle> DEGREE_ANGLE = new TransformedUnit<>("°", RADIAN, MultiplyConverter.ofPiExponent(1).concatenate(MultiplyConverter.ofRational(1, 180)));