1. 概述
本文将介绍单位测量API(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
项目定义了一套接口规范。示例中我们将使用其参考实现**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
类型精度不足。若开发者误用不同单位(如用公斤代替升),将导致难以排查的计算错误。
JSR-363通过Quantity
和Unit
接口彻底解决此类问题。
3.1 基础示例
Quantity<Q extends Quantity<Q>>
接口表示物理量(如体积、面积)。库提供了常用子接口:
Volume
(体积)Length
(长度)ElectricCharge
(电荷)Energy
(能量)Temperature
(温度)
改造水箱类:
public class WaterTank {
public void setCapacityMeasure(Quantity<Volume> capacityMeasure);
}
同时Unit
接口用于标识单位。unit-ri
库预定义了常用单位:
KELVIN
(开尔文)METRE
(米)NEWTON
(牛顿)CELSIUS
(摄氏度)
创建数量对象并操作:
@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);
❌ 尝试跨维度转换会编译报错:
// 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. 单位转换
SystemOfUnits
提供单位库。参考实现中的Units
类包含常用单位常量。支持:
- 使用前缀(如
KILO
,CENTI
) - 自定义单位
- 单位运算
4.1 自定义单位
当系统单位不足时,可创建:
- 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); }
单位转换器(UnitConverter)实现转换:
@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接口处理单位标签:
@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()
(除)
示例运算:
@Test
public void givenUnits_WhenAdd_ThenSuccess() {
Quantity<Length> total = Quantities.getQuantity(2, METRE)
.add(Quantities.getQuantity(3, METRE));
assertEquals(total.getValue().intValue(), 5);
}
✅ 同维度单位可运算:
Quantity<Length> totalKm = Quantities.getQuantity(2, METRE)
.add(Quantities.getQuantity(3, MetricPrefix.KILO(METRE)));
assertEquals(totalKm.getValue().intValue(), 3002);
❌ 跨维度运算编译报错:
// compilation error
Quantity<Length> total = Quantities.getQuantity(2, METRE)
.add(Quantities.getQuantity(3, LITRE));
6. 总结
单位测量API提供了强大的物理量处理模型。除了核心的Quantity
和Unit
,其灵活的单位转换机制能有效避免计算踩坑。