For many applications, it is important to use the correct units of measurement for primitive types. For example, NASA’s Mars Climate Orbiter (cost: $327 million) was lost because of a discrepancy between use of the metric unit newton and the imperial measure pound-force.
The Units Checker ensures consistent usage of units. For example, consider the following code:
@m int meters = 5 * UnitsTools.m; @s int secs = 2 * UnitsTools.s; @mPERs int speed = meters / secs;
Due to the annotations @m and @s, the variables meters and secs are guaranteed to contain only values with meters and seconds as units of measurement. The assignment of an unqualified value to meters, as in meters = 99, will
be flagged as an error by the Units Checker. Utility class UnitsTools provides constants that you can multiply with an unqualified integer to get values of the corresponding unit; for
example, meters = 99 * UnitsTools.m is legal, or just meters = 99 * m if the file contains import static org.checkerframework.checker.units.util.UnitsTools.*;. To use the UnitsTools class, the checker-util.jar file must be on the classpath at run time.
The division meters/secs takes the types of the two operands into account and determines that the result is of type meters per second, signified by the @mPERs qualifier. We provide an extensible framework to define the result of operations on units.
The checker currently supports three varieties of units annotations: kind annotations (@Length, @Mass, …), the SI units (@m, @kg, …), and polymorphic annotations (@PolyUnit).
Kind annotations can be used to declare what the expected unit of measurement is, without fixing the particular unit used. For example, one could write a method taking a @Length value, without specifying whether it will take meters or kilometers. The following kind annotations are defined:
@Acceleration@Angle@Area@Current@Force@Length@Luminance@Mass@Speed@Substance@Temperature@Time@Volume
For each kind of unit, the corresponding SI unit of measurement is defined:
1. For @Acceleration: meters per second squared @mPERs2
2. For @Angle: radians @radians, and the derived unit degrees @degrees
3. For @Area: the derived units square millimeters @mm2, square meters @m2, and square kilometers @km2
4. For @Current: amperes @A
5. For @Force: newtons @N and the derived unit kilonewton @kN
6. For @Length: meters @m and the derived units millimeters @mm and kilometers @km
7. For @Luminance: candelas @cd
8. For @Mass: kilograms @kg and the derived units grams @g and metric tons @t
9. For @Speed: meters per second @mPERs and kilometers per hour @kmPERh
10. For @Substance: moles @mol
11. For @Temperature: kelvins @K and the derived unit degrees Celsius @C
12. For @Time: seconds @s and the derived units minutes @min and hours @h
13. For @Volume: the derived units cubic millimeters @mm3, cubic meters @m3, and cubic kilometers @km3
You may specify SI unit prefixes, using enumeration . The basic SI units (Prefix@s, @m, @g, @A, @K,
@mol, @cd) take an optional Prefix enum as argument. For example, to use nanoseconds as a unit, you could use @s(Prefix.nano) as a unit type. You can sometimes use a different annotation instead of a prefix; for example, @mm is equivalent to
@m(Prefix.milli).
Class UnitsTools contains a constant for each SI unit. To create a value of the particular unit, multiply an unqualified value with one of these constants. Static imports allow very natural notation; for example, after statically importing UnitsTools.m, the expression 5 * m
represents five meters. As all these unit constants are public, static, and final with value one, the compiler will optimize away these multiplications. To use the UnitsTools class, the
checker-util.jar file must be on the classpath at run time.
The polymorphic annotation @PolyUnit enables you to write a method that takes an argument of any unit type and returns a result of that same type. For more about polymorphic
qualifiers, see Section 32.2. For an example of its use, see the @PolyUnit
Javadoc.
You can create new kind annotations and unit annotations that are specific to the particular needs of your project. An easy way to do this is by copying and adapting an existing annotation. (In addition, search for all uses of the annotation’s name throughout the Units Checker implementation, to find other code to adapt; read on for details.)
Here is an example of a new unit annotation.
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})
@SubtypeOf({Time.class})
@UnitsMultiple(quantity=s.class, prefix=Prefix.nano)
public @interface ns {}
The @SubtypeOf meta-annotation specifies that this annotation introduces an additional unit of time. The @UnitsMultiple meta-annotation specifies that this annotation should be a nano multiple of the basic unit @s: @ns and @s(Prefix.nano)
behave equivalently and interchangeably. Most annotation definitions do not have a @UnitsMultiple meta-annotation.
Note that all custom annotations must have the @Target(ElementType.TYPE_USE) meta-annotation. See Section 37.5.1.
To take full advantage of the additional unit qualifier, you need to take two additional steps. (1) Provide constants that convert from unqualified types to types that use the new unit. See class UnitsTools for examples (you will need to suppress a checker warning in just those few locations).
(2) Put the new unit in relation to existing units. Provide an implementation of the UnitsRelations interface as a meta-annotation to one of the units.
See demonstration docs/examples/units-extension/ for an example extension that defines hertz (Hz) as scalar per second, and defines an implementation of UnitsRelations to enforce it.
The Units Checker ensures that unrelated types are not mixed.
All types with a particular unit annotation are disjoint from all unannotated types, from all types with a different unit annotation, and from all types with the same unit annotation but a different prefix.
Subtyping between the units and the unit kinds is taken into account, as is the @UnitsMultiple meta-annotation.
Multiplying a scalar by a unit type results in the same unit type.
The division of a unit type by the same unit type results in the unqualified type.
Multiplying or dividing different unit types, for which no unit relation is known to the system, will result in a MixedUnits type, which is separate from all other units. If you encounter a MixedUnits annotation in an error message, ensure that your operations are performed on correct
units or refine your UnitsRelations implementation.
The Units Checker does not change units based on multiplication; for example, if variable mass has the type @kg double, then mass * 1000 has that same type rather than the type @g double. (The Units Checker has no way of knowing whether
you intended a conversion, or you were computing the mass of 1000 items. You need to make all conversions explicit in your code, and it’s good style to minimize the number of conversions.)
The Units Checker can be invoked by running the following commands.
• If your code uses only the SI units that are provided by the framework, simply invoke the checker:
javac -processor org.checkerframework.checker.units.UnitsChecker MyFile.java ...
• If you define your own units, provide the fully-qualified class names of the annotations through the -Aunits option, using a comma-no-space-separated notation:
javac -classpath /full/path/to/myProject/bin:/full/path/to/myLibrary/bin\-processor org.checkerframework.checker.units.UnitsChecker\-Aunits=myPackage.qual.MyUnit,myPackage.qual.MyOtherUnit MyFile.java ...
The annotations listed in -Aunits must be accessible to the compiler during compilation. Before you run the Units Checker with javac, they must be compiled and on the same path (the classpath or processorpath) as the Checker Framework. It is not sufficient to supply their source files
on the command line.
• You can also provide the fully-qualified paths to a set of directories that contain units qualifiers through the -AunitsDirs option, using a colon-no-space-separated notation. For example, if the Checker Framework is on the classpath rather than the processorpath:
javac -classpath /full/path/to/myProject/bin:/full/path/to/myLibrary/bin\-processor org.checkerframework.checker.units.UnitsChecker\-AunitsDirs=/full/path/to/myProject/bin:/full/path/to/myLibrary/bin MyFile.java ...
Note that in these two examples, the compiled class file of the myPackage.qual.MyUnit and myPackage.qual.MyOtherUnit annotations must exist in either the myProject/bin directory or the myLibrary/bin directory. The following placement of the
class files will work with the above commands:
.../myProject/bin/myPackage/qual/MyUnit.class .../myProject/bin/myPackage/qual/MyOtherUnit.class
The two options can be used at the same time to provide groups of annotations from directories, and individually named annotations.
Also, see the example project in the docs/examples/units-extension directory.
One example of when you need to suppress warnings is when you initialize a variable of a unit type with a literal value. To remove this warning message, it is best to introduce a constant that represents the unit and to add a @SuppressWarnings annotation to that constant. For examples, see class
UnitsTools.
• The GNU Units tool provides a comprehensive list of units:
https://www.gnu.org/software/units/
• The F# units of measurement system inspired some of our syntax:
https://en.wikibooks.org/wiki/F_Sharp_Programming/Units_of_Measure