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通过QuantityUnit接口彻底解决此类问题

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 自定义单位

当系统单位不足时,可创建:

  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));
    }
    
  2. 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提供了强大的物理量处理模型。除了核心的QuantityUnit,其灵活的单位转换机制能有效避免计算踩坑。

进一步探索可参考项目官网,完整代码见GitHub仓库


原始标题:Introduction to javax.measure