Introduction à javax.measure

Introduction à javax.measure

1. Vue d'ensemble

Dans cet article, nous présenterons l'API Unités de mesure, qui fournit desa unified way of representing measures and units in Java.

Lorsque vous travaillez avec un programme contenant des quantités physiques, nous devons lever l’incertitude concernant les unités utilisées. Il est essentiel de gérer à la fois le nombre et son unité pour éviter les erreurs de calcul.

JSR-363 (anciennement bibliothèqueJSR-275 oujavax.measure) nous aide à gagner du temps de développement, et en même temps, rend le code plus lisible.

2. Dépendances Maven

Commençons simplement par la dépendance Maven pour extraire la bibliothèque:


    javax.measure
    unit-api
    1.0

La dernière version peut être trouvée surMaven Central.

Le projetunit-api contient un ensemble d'interfaces qui définissent comment travailler avec les quantités et les unités. Pour les exemples, nous utiliserons l'implémentation de référence deJSR-363, qui estunit-ri:


    tec.units
    unit-ri
    1.0.3

3. Explorer l'API

Jetons un coup d'œil à l'exemple où nous voulons stocker de l'eau dans un réservoir.

L'implémentation héritée ressemblerait à ceci:

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

Comme on peut le voir, le code ci-dessus ne mentionne pas l'unité de quantité d'eau et ne convient pas pour des calculs précis en raison de la présence du typedouble.

Si un développeur transmet par erreur la valeur avec une unité de mesure différente de celle que nous attendons, cela peut entraîner de graves erreurs de calcul. De telles erreurs sont très difficiles à détecter et à résoudre.

The JSR-363 API provides us with the Quantity and Unit interfaces, qui résolvent cette confusion et laissent ce genre d'erreurs hors de la portée de notre programme.

3.1. Exemple simple

Voyons maintenant comment cela peut être utile dans notre exemple.

Comme mentionné précédemment,JSR-363 contient desthe Quantity interface which represents a quantitative property tels que le volume ou la surface. La bibliothèque fournit de nombreuses sous-interfaces qui modélisent les attributs quantifiables les plus couramment utilisés. Quelques exemples sont:Volume,Length,ElectricCharge,Energy,Temperature.

Nous pouvons définir l'objetQuantity<Volume>, qui doit stocker la quantité d'eau dans notre exemple:

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

Outre l'interfaceQuantity,we can also use the Unit interface to identify the unit of measurement for a property. Les définitions des unités souvent utilisées peuvent être trouvées dans la bibliothèqueunit-ri, telles que:KELVIN,METRE,NEWTON,CELSIUS.

Un objet de typeQuantity<Q extends Quantity<Q>> a des méthodes pour récupérer l'unité et la valeur:getUnit() etgetValue().

Voyons un exemple pour définir la valeur de la quantité d’eau:

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

Nous pouvons également convertir rapidement ceVolume enLITRE vers n'importe quelle autre unité:

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

Mais, lorsque nous essayons de convertir la quantité d'eau en une autre unité - qui n'est pas de typeVolume, nous obtenons une erreur de compilation:

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

3.2. Paramétrage de classe

Pour maintenir la cohérence de la dimension, le cadre tire naturellement parti des génériques.

Les classes et les interfaces sont paramétrées par leur type de quantité, ce qui permet de faire contrôler nos unités au moment de la compilation. Le compilateur donnera une erreur ou un avertissement en fonction de ce qu'il peut identifier:

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

Il est toujours possible de contourner la vérification de type en utilisant la méthodeasType():

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

Nous pouvons également utiliser un caractère générique si nous ne sommes pas sûr du type de quantité:

Unit kelvinPerSec = KELVIN.divide(SECOND);

4. Conversion d'unité

Units peut être récupéré à partir deSystemOfUnits. L'implémentation de référence de la spécification contient l'implémentationUnits de l'interface qui fournit un ensemble de constantes statiques représentant les unités les plus couramment utilisées.

En outre, nous pouvons également créer une unité personnalisée entièrement nouvelle ou créer une unité en appliquant des opérations algébriques aux unités existantes.

L'avantage d'utiliser une unité standard est que nous ne nous heurtons pas aux pièges de la conversion.

On peut aussi utiliser des préfixes, ou des multiplicateurs de la classeMetricPrefix, commeKILO(Unit<Q> unit) etCENTI(Unit<Q> unit), qui équivalent respectivement à multiplier et diviser par une puissance de 10.

Par exemple, nous pouvons définir «Kilomètre» et «Centimètre» comme suit:

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

Ceux-ci peuvent être utilisés quand une unité que nous voulons n'est pas disponible directement.

4.1. Unités personnalisées

Dans tous les cas, si une unité n’existe pas dans le système d’unités, nous pouvons créer de nouvelles unités avec de nouveaux symboles:

  • AlternateUnit – une nouvelle unité avec la même dimension mais un symbole et une nature différents

  • ProductUnit – une nouvelle unité créée comme le produit des puissances rationnelles d'autres unités

Créons des unités personnalisées à l'aide de ces classes. Un exemple deAlternateUnit pour la pression:

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

De même, un exemple deProductUnit et sa conversion:

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

Ici, nous avons créé une unité composéesquareMetre en multipliantMETRE avec elle-même.

Ensuite, aux types d'unités, le framework fournit également une classeUnitConverter, qui nous aide à convertir une unité en une autre, ou à créer une nouvelle unité dérivée appeléeTransformedUnit.

Voyons un exemple pour transformer l’unité d’une valeur double, de mètres en kilomètres:

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

Pour faciliter la communication électronique sans ambiguïté des grandeurs avec leurs unités, la bibliothèque fournit l'interfaceUnitFormat, qui associe des étiquettes à l'échelle du système àUnits.

Vérifions les étiquettes de certaines unités système à l'aide de l'implémentationSimpleUnitFormat:

@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. Effectuer des opérations avec des quantités

L'interfaceQuantity contient des méthodes pour les opérations mathématiques les plus courantes:add(),subtract(),multiply(),divide(). En utilisant ceux-ci, nous pouvons effectuer des opérations entre les objetsQuantity:

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

Les méthodes vérifient également lesUnits des objets sur lesquels elles opèrent. Par exemple, essayer de multiplier les compteurs avec des litres entraînera une erreur de compilation:

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

D'autre part, deux objets exprimés en unités ayant la même dimension peuvent être ajoutés:

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

Dans cet exemple, les unités de mètre et de kilomètre correspondent à la dimensionLength afin qu'elles puissent être ajoutées. Le résultat est exprimé dans l'unité du premier objet.

6. Conclusion

Dans cet article, nous avons vu queUnits of Measurement API nous donne un modèle de mesure pratique. Et, mis à part l'utilisation deQuantity etUnit, nous avons également vu à quel point il est pratique de convertir une unité en une autre, de plusieurs manières.

Pour plus d'informations, vous pouvez toujours consulterthe project here.

Et, comme toujours, l'intégralité du code est disponibleover on GitHub.