Introdução ao javax.measure

Introdução ao javax.measure

1. Visão geral

Neste artigo, apresentaremos a API de unidades de medida - que fornecea unified way of representing measures and units in Java.

Ao trabalhar com um programa que contém quantidades físicas, precisamos remover a incerteza sobre as unidades usadas. É essencial gerenciarmos o número e sua unidade para evitar erros nos cálculos.

JSR-363 (antiga bibliotecaJSR-275 oujavax.measure) nos ajuda a economizar tempo de desenvolvimento e, ao mesmo tempo, torna o código mais legível.

2. Dependências do Maven

Vamos simplesmente começar com a dependência Maven para obter a biblioteca:


    javax.measure
    unit-api
    1.0

A versão mais recente pode ser encontrada emMaven Central.

O projetounit-api contém um conjunto de interfaces que definem como trabalhar com quantidades e unidades. Para os exemplos, usaremos a implementação de referência deJSR-363, que éunit-ri:


    tec.units
    unit-ri
    1.0.3

3. Explorando a API

Vamos dar uma olhada no exemplo em que queremos armazenar água em um tanque.

A implementação legada ficaria assim:

public class WaterTank {
    public void setWaterQuantity(double quantity);
}

Como podemos ver, o código acima não menciona a unidade de quantidade de água e não é adequado para cálculos precisos devido à presença do tipodouble.

Se um desenvolvedor passar erroneamente o valor com uma unidade de medida diferente daquela que esperamos, isso pode levar a erros graves nos cálculos. Tais erros são muito difíceis de detectar e resolver.

The JSR-363 API provides us with the Quantity and Unit interfaces, que resolve essa confusão e deixa esses tipos de erros fora do escopo do nosso programa.

3.1. Exemplo Simples

Agora, vamos explorar e ver como isso pode ser útil em nosso exemplo.

Como mencionado anteriormente,JSR-363 contémthe Quantity interface which represents a quantitative property, como volume ou área. A biblioteca fornece inúmeras subinterfaces que modelam os atributos quantificáveis ​​mais usados. Alguns exemplos são:Volume,Length,ElectricCharge,Energy,Temperature.

Podemos definir o objetoQuantity<Volume>, que deve armazenar a quantidade de água em nosso exemplo:

public class WaterTank {
    public void setCapacityMeasure(Quantity capacityMeasure);
}

Além da interfaceQuantity,we can also use the Unit interface to identify the unit of measurement for a property. As definições das unidades usadas com frequência podem ser encontradas na bibliotecaunit-ri, como:KELVIN,METRE,NEWTON,CELSIUS.

Um objeto do tipoQuantity<Q extends Quantity<Q>> possui métodos para recuperar a unidade e o valor:getUnit()egetValue().

Vejamos um exemplo para definir o valor da quantidade de água:

@Test
public void givenQuantity_whenGetUnitAndConvertValue_thenSuccess() {
    WaterTank waterTank = new WaterTank();
    waterTank.setCapacityMeasure(Quantities.getQuantity(9.2, LITRE));
    assertEquals(LITRE, waterTank.getCapacityMeasure().getUnit());

    Quantity waterCapacity = waterTank.getCapacityMeasure();
    double volumeInLitre = waterCapacity.getValue().doubleValue();
    assertEquals(9.2, volumeInLitre, 0.0f);
}

Também podemos converter esteVolume emLITRE para qualquer outra unidade rapidamente:

double volumeInMilliLitre = waterCapacity
  .to(MetricPrefix.MILLI(LITRE)).getValue().doubleValue();
assertEquals(9200.0, volumeInMilliLitre, 0.0f);

Mas, quando tentamos converter a quantidade de água em outra unidade - que não é do tipoVolume, obtemos um erro de compilação:

// compilation error
waterCapacity.to(MetricPrefix.MILLI(KILOGRAM));

3.2. Parametrização de Classe

Para manter a consistência da dimensão, a estrutura naturalmente tira proveito dos genéricos.

Classes e interfaces são parametrizadas pelo tipo de quantidade, o que possibilita que nossas unidades sejam verificadas em tempo de compilação. O compilador emitirá um erro ou aviso com base no que ele pode identificar:

Unit Kilometer = MetricPrefix.KILO(METRE);
Unit Centimeter = MetricPrefix.CENTI(LITRE); // compilation error

Sempre há a possibilidade de ignorar a verificação de tipo usando o métodoasType():

Unit inch = CENTI(METER).times(2.54).asType(Length.class);

Também podemos usar um curinga se não tivermos certeza do tipo de quantidade:

Unit kelvinPerSec = KELVIN.divide(SECOND);

4. Conversão de unidades

Units pode ser recuperado deSystemOfUnits. A implementação de referência da especificação contém a implementaçãoUnits da interface que fornece um conjunto de constantes estáticas que representam as unidades mais comumente usadas.

Além disso, também podemos criar uma unidade personalizada totalmente nova ou criar uma unidade aplicando operações algébricas em unidades existentes.

A vantagem de usar uma unidade padrão é que não caímos nas armadilhas de conversão.

Também podemos usar prefixos, ou multiplicadores da classeMetricPrefix, comoKILO(Unit<Q> unit)eCENTI(Unit<Q> unit), que são equivalentes a multiplicar e dividir por uma potência de 10 respectivamente.

Por exemplo, podemos definir "Quilômetro" e "Centímetro" como:

Unit Kilometer = MetricPrefix.KILO(METRE);
Unit Centimeter = MetricPrefix.CENTI(METRE);

Elas podem ser usadas quando uma unidade que desejamos não está disponível diretamente.

4.1. Unidades personalizadas

Em qualquer caso, se uma unidade não existe no sistema de unidades, podemos criar novas unidades com novos símbolos:

  • AlternateUnit – uma nova unidade com a mesma dimensão, mas símbolo e natureza diferentes

  • ProductUnit – uma nova unidade criada como o produto de poderes racionais de outras unidades

Vamos criar algumas unidades personalizadas usando essas classes. Um exemplo deAlternateUnit para pressão:

@Test
public void givenUnit_whenAlternateUnit_ThenGetAlternateUnit() {
    Unit PASCAL = NEWTON.divide(METRE.pow(2))
      .alternate("Pa").asType(Pressure.class);
    assertTrue(SimpleUnitFormat.getInstance().parse("Pa")
      .equals(PASCAL));
}

Da mesma forma, um exemplo deProductUnit e sua conversão:

@Test
public void givenUnit_whenProduct_ThenGetProductUnit() {
    Unit squareMetre = METRE.multiply(METRE).asType(Area.class);
    Quantity line = Quantities.getQuantity(2, METRE);
    assertEquals(line.multiply(line).getUnit(), squareMetre);
}

Aqui, criamos uma unidade compostasquareMetre multiplicandoMETRE por ela mesma.

Em seguida, para os tipos de unidades, a estrutura também fornece uma classeUnitConverter, que nos ajuda a converter uma unidade em outra, ou a criar uma nova unidade derivada chamadaTransformedUnit.

Vejamos um exemplo para transformar a unidade de um valor duplo, de metros para quilômetros:

@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);
}

Para facilitar a comunicação eletrônica inequívoca de quantidades com suas unidades, a biblioteca fornece a interfaceUnitFormat,, que associa rótulos de todo o sistema aUnits.

Vamos verificar os rótulos de algumas unidades do sistema usando a implementaçãoSimpleUnitFormat:

@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. Execução de operações com quantidades

A interfaceQuantity contém métodos para as operações matemáticas mais comuns:add(),subtract(),multiply(),divide(). Usando isso, podemos realizar operações entre objetosQuantity:

@Test
public void givenUnits_WhenAdd_ThenSuccess() {
    Quantity total = Quantities.getQuantity(2, METRE)
      .add(Quantities.getQuantity(3, METRE));
    assertEquals(total.getValue().intValue(), 5);
}

Os métodos também verificam oUnits dos objetos nos quais estão operando. Por exemplo, tentar multiplicar medidores por litros resultará em um erro de compilação:

// compilation error
Quantity total = Quantities.getQuantity(2, METRE)
  .add(Quantities.getQuantity(3, LITRE));

Por outro lado, dois objetos expressos em unidades com a mesma dimensão podem ser adicionados:

Quantity totalKm = Quantities.getQuantity(2, METRE)
  .add(Quantities.getQuantity(3, MetricPrefix.KILO(METRE)));
assertEquals(totalKm.getValue().intValue(), 3002);

Neste exemplo, as unidades de metro e quilômetro correspondem à dimensãoLength para que possam ser adicionadas. O resultado é expresso na unidade do primeiro objeto.

6. Conclusão

Neste artigo, vimos queUnits of Measurement API nos dá um modelo de medição conveniente. E, além do uso deQuantityeUnit, também vimos como é conveniente converter uma unidade em outra, de várias maneiras.

Para obter mais informações, você pode sempre verificarthe project here.

E, como sempre, todo o código está disponívelover on GitHub.