I. Introduction▲
Cet article est le septième d'une série consacrée à la bibliothèque Guava :
- introduction et installation ;
- collections ;
- programmation fonctionnelle ;
- utilitaires ;
- cache et concurrence ;
- tout pour vos Strings et primitifs ;
- un peu de math ;
- hash et I/O.
I-A. Versions des logiciels et bibliothèques utilisés▲
Pour écrire ce document, j'ai utilisé les versions suivantes :
- Java JDK 1.6.0_24-b07 ;
- Eclipse Indigo 3.7 JEE 64b ;
- Maven 3.0.3 ;
- JUnit 4.10 ;
- Guava 14.0.
J'utilise Java 6, car Java 7 n'est pas encore très répandu en entreprise. C'est ce que je vérifie durant mes conférences lorsque je demande qui utilise Java 7 sur ses serveurs de production, mais que très peu de mains se lèvent...
I-B. Mises à jour▲
xxx : création
II. La bosse des maths▲
II-A. Les intervalles (range)▲
Nous allons voir les fonctionnalités avancées offertes par "Range".
Mais, avant d'aller plus loin, rappelons qu'un intervalle est construit à l'aide de deux valeurs, qui constituent les bornes min et max d'un ensemble de valeurs. Un intervalle peut être ouvert (pour exclure une valeur) ou fermé (pour inclure une valeur) à gauche comme à droite. Par exemple, l'intervalle des valeurs entières noté "[3; 6[" est fermé à gauche et ouvert à droite. Il contient donc les valeurs 3, 4 et 5.
La notation des intervalles ouverts diffère en français et en anglais. Ainsi, l'intervalle noté "[3; 6[" en français s'écrira "[3..6)" en anglais. Ce détail aura son importance par la suite.
Il y a quatre méthodes pour construire les quatre types d'intervalles de base :
- open, pour ]3; 6[ ;
- closed, pour [3; 6] ;
- closedOpen, pour [3; 6[ ;
- openClosed, pour ]3; 6].
@Test
public
void
testIntervalleSimple
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
int
nb =
5
;
// Act
final
Range<
Integer>
range =
Range.closed
(
min, max); // ie. [3; 6]
// Assert
Assert.assertTrue
(
range.contains
(
nb));
}
On peut aussi créer des intervalles à partir d'une seule valeur. Dans ce cas, c'est l'infini négatif ou positif qui sera utilisé pour l'autre borne :
- greaterThan, pour ]3; +inf[ ;
- lessThan, pour ]-inf; 3[ ;
- atLeast, pour [3; +inf[ ;
- atMost, pour ]-inf; 3] ;
- all, pour ]-inf; +inf[.
@Test
public
void
testIntervalleInfini
(
) {
// Arrange
final
int
min =
3
;
final
int
nb =
12
;
// Act
final
Range<
Integer>
range =
Range.atLeast
(
min); // ie. [3; +inf[
// Assert
Assert.assertTrue
(
range.contains
(
nb));
}
On peut aussi préciser en paramètre si on veut un intervalle ouvert ou fermé, ce qui peut être pratique :
@Test
public
void
testIntervalleSpecifique
(
) {
// Arrange
final
int
min =
3
;
final
BoundType gauche =
BoundType.CLOSED;
final
int
max =
6
;
final
BoundType droite =
BoundType.CLOSED;
final
int
nb =
5
;
// Act
final
Range<
Integer>
range =
Range.range
(
min, gauche, max, droite); // ie. [3; 6]
// Assert
Assert.assertTrue
(
range.contains
(
nb));
}
Vous pensez que les intervalles ne s'appliquent qu'à des entiers et vous avez tout faux, car ça peut aussi s'utiliser avec des flottants :
@Test
public
void
testIntervalleFlottant
(
) {
// Arrange
final
double
min =
3.2
;
final
double
max =
6.7
;
final
double
nb =
5
;
// Act
final
Range<
Double>
range =
Range.closed
(
min, max); // ie. [3,2; 6,7]
// Assert
Assert.assertTrue
(
range.contains
(
nb));
}
Maintenant, vous pensez que ça ne s'utilise qu'avec des nombres et vous avez encore tort, car on peut utiliser des strings. Dans ce cas, l'intervalle va simplement permettre de vérifier si un mot est bien alphabétiquement compris entre la borne min et la borne max :
@Test
public
void
testIntervalleString
(
) {
// Arrange
final
String min =
"idefix"
;
final
String max =
"milou"
;
final
String prenom =
"lassie"
;
// Act
final
Range<
String>
range =
Range.closed
(
min, max); // ie. ["idefix"; "milou"]
// Assert
Assert.assertTrue
(
range.contains
(
prenom));
}
On a déjà vu, à travers les précédents exemples, qu'on peut demander si une valeur fait partie de l'intervalle. On peut aussi demander si une liste de valeurs en fait partie :
@Test
public
void
testIntervalleListe
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
List<
Integer>
liste =
Ints.asList
(
4
, 5
, 6
);
// Act
final
Range<
Integer>
range =
Range.closed
(
min, max); // ie. [3; 6]
// Assert
Assert.assertTrue
(
range.containsAll
(
liste));
}
On peut faire plein de choses sur un intervalle, à commencer par savoir s'il est vide :
@Test
public
void
testIntervalleVide
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
// Act
final
Range<
Integer>
range1 =
Range.closed
(
min, max); // ie. [3; 6]
final
Range<
Integer>
range2 =
Range.closedOpen
(
min, min); // ie. [4; 4[
// Assert
Assert.assertFalse
(
range1.isEmpty
(
)); // pas vide
Assert.assertTrue
(
range2.isEmpty
(
)); // vide
}
Une question encore plus courante est de déterminer si deux intervalles sont inclus l'un dans l'autre :
@Test
public
void
testIntervalleInclusion
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
int
min2 =
4
;
final
int
max2 =
6
;
// Act
final
Range<
Integer>
grand =
Range.closed
(
min, max); // ie. [3; 6]
final
Range<
Integer>
petit =
Range.closed
(
min2, max2); // ie. [4; 6]
// Assert
Assert.assertTrue
(
grand.encloses
(
petit));
}
On peut aussi vérifier si deux intervalles se chevauchent :
@Test
public
void
testIntervalleChevauchement
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
int
min2 =
5
;
final
int
max2 =
9
;
// Act
final
Range<
Integer>
range1 =
Range.closed
(
min, max); // ie. [3; 6]
final
Range<
Integer>
range2 =
Range.closed
(
min2, max2); // ie. [5; 9]
// Assert
Assert.assertTrue
(
range1.isConnected
(
range2));
}
On peut même calculer l'intersection de deux intervalles :
@Test
public
void
testIntervalleIntersection
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
int
min2 =
5
;
final
int
max2 =
9
;
final
List<
Integer>
liste =
Ints.asList
(
5
, 6
);
// Act
final
Range<
Integer>
range1 =
Range.closed
(
min, max); // ie. [3; 6]
final
Range<
Integer>
range2 =
Range.closed
(
min2, max2); // ie. [5; 9]
final
Range<
Integer>
inter =
range1.intersection
(
range2); // -> [5; 6]
// Assert
Assert.assertTrue
(
inter.containsAll
(
liste));
}
Si on a besoin d'une union, on utilisera un « RangeSet ».
Si on préfère, on demandera l'intervalle suffisant pour encadrer deux intervalles :
@Test
public
void
testIntervalleSpan
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
int
min2 =
8
;
final
int
max2 =
9
;
final
List<
Integer>
liste =
Ints.asList
(
3
, 4
, 5
, 6
, 7
, 8
, 9
);
// Act
final
Range<
Integer>
range1 =
Range.closed
(
min, max); // ie. [3; 6]
final
Range<
Integer>
range2 =
Range.closed
(
min2, max2); // ie. [8; 9]
final
Range<
Integer>
inter =
range1.span
(
range2); // -> [3; 9]
// Assert
Assert.assertTrue
(
inter.containsAll
(
liste));
}
Ici, les intervalles [3; 6] et [8; 9] sont disjoints et n'incluent pas le nombre 7.
Enfin, si on a un intervalle dont on veut le contenu, il suffit de préciser le domaine qui nous intéresse :
@Test
public
void
testIntervalleAsSet
(
) {
// Arrange
final
int
min =
3
;
final
int
max =
6
;
final
Set<
Integer>
expected =
Sets.newHashSet
(
3
, 4
, 5
, 6
);
// Act
final
Range<
Integer>
range =
Range.closed
(
min, max); // ie. [3; 6]
final
Set<
Integer>
set =
range.asSet
(
DiscreteDomain.integers
(
));
// Assert
Assert.assertEquals
(
expected, set);
}
II-B. Calculs▲
Dans la vie d'un programme, on doit toujours, à un moment ou un autre, programmer des fonctions mathématiques absentes du JDK. Ce sont généralement des calculs « simples » (sous réserve qu'on considère comme simples des calculs comme ceux des logarithmes), mais qui comportent de nombreux pièges : arrondis, capacité, etc.
La bibliothèque Guava travaille principalement avec les trois classes « IntMath », « LongMath » et « BigIntegerMath » dont l'utilisation spécifique dépend (sans surprise) de la grandeur des valeurs. Les méthodes correspondantes sont capables de lever une exception si le résultat du calcul mathématique est faux. Le JDK, quant à lui, ne le fait pas... Prenons un exemple :
@Test
public
void
testAdditionStandard
(
) {
// Arrange
final
int
nb =
Integer.MAX_VALUE; // ie. 2^31 - 1
final
int
expected =
Integer.MIN_VALUE; // ie. -2^31
// Act
final
int
result =
nb +
1
;
// Arrange
Assert.assertEquals
(
expected, result);
}
Dans l'exemple précédent, on tombe sur le cas classique des entiers en Java. Quand on arrive à la capacité maximale positive, on boucle simplement sur les négatifs en partant donc de la valeur minimale. Ce qui est gênant, c'est que le JDK ne râle pas et on peut donc facilement passer à côté d'un bug. Au contraire, Guava ne va pas rester silencieux. Prenons le même test :
@Test
(
expected =
ArithmeticException.class
)
public
void
testAdditionGuava
(
) {
// Arrange
final
int
nb =
Integer.MAX_VALUE; // ie. 2^31 - 1
final
int
expected =
Integer.MIN_VALUE; // ie. -2^31
// Act
final
int
result =
IntMath.checkedAdd
(
nb, 1
); // AE
}
Cela ne permet pas de réaliser le calcul, mais le programme sait, au moins, qu'il n'a pas la bonne valeur.
On a des opérations fonctionnant sur le même principe pour les « ints » et les « longs » :
- checkedAdd ;
- checkedSubtract ;
- checkedMultiply ;
- checkedPow.
La bibliothèque peut également réaliser des calculs donnant un résultat flottant (ie. avec une virgule). Dans ce cas, il est prévu de spécifier le type d'arrondis souhaité :
@Test
public
void
testDivisionArrondieEnDessous
(
) {
// Arrange
final
int
a =
5
;
final
int
b =
2
;
final
RoundingMode arrondi =
RoundingMode.DOWN;
final
int
expected =
2
;
// Act
final
int
result =
IntMath.divide
(
a, b, arrondi);
// Assert
Assert.assertEquals
(
expected, result);
}
Les types d'arrondis offerts par Guava sont :
- DOWN, qui arrondit en dessous (en valeur absolue) : 2.5 donne 2 et -2.5 donne -2 ;
- UP, qui arrondit au-dessus (en valeur absolue) ;
- FLOOR, qui arrondit en dessous : 2.5 donne 2 et -2.5 donne -3 ;
- CEILING, qui arrondit au-dessus ;
- UNNECESSARY, qui lève une exception (ArithmeticException) si un arrondi est nécessaire ;
- HALF_DOWN, qui arrondit au demi-inférieur (en valeur absolue) : 2.4 donne 2 et 2.6 donne 3 ;
- HALF_UP, qui arrondit au demi-supérieur (en valeur absolue) ;
- HALF_EVEN, qui arrondit au demi le plus proche.
Voici une série d'exemples pour bien comprendre les différences :
@Test
public
void
testDivisionArrondieEnDessous2
(
) {
// Arrange
final
int
a =
-
5
;
final
int
b =
2
;
final
RoundingMode arrondi =
RoundingMode.DOWN;
final
int
expected =
-
2
;
// Act
final
int
result =
IntMath.divide
(
a, b, arrondi);
// Assert
Assert.assertEquals
(
expected, result);
}
@Test
public
void
testDivisionArrondieEnDessous3
(
) {
// Arrange
final
int
a =
7
;
final
int
b =
3
;
final
RoundingMode arrondi =
RoundingMode.HALF_DOWN;
final
int
expected =
2
;
// Act
final
int
result =
IntMath.divide
(
a, b, arrondi);
// Assert
Assert.assertEquals
(
expected, result);
}
@Test
public
void
testDivisionArrondieEnDessous4
(
) {
// Arrange
final
int
a =
8
;
final
int
b =
3
;
final
RoundingMode arrondi =
RoundingMode.HALF_DOWN;
final
int
expected =
3
;
// Act
final
int
result =
IntMath.divide
(
a, b, arrondi);
// Assert
Assert.assertEquals
(
expected, result);
}
Les méthodes de calcul qui nécessiteront des arrondis seront :
- divide ;
- log2 (logarithme) ;
- log10 (logarithme en base 10) ;
- sqrt (racine carrée).
En plus de ces méthodes, Guava permet de calculer quelques opérations qui sont des souvenirs d'école :
- gcd, qui calcule le Plus Grand Diviseur Commun (PGCD en français) ;
- mod, qui calcule le modulo (positif) ;
- pow, qui calcule une puissance (attention ça croit très vite) ;
- isPowerOfTwo, qui dit si le paramètre est une puissance de 2 ;
- factorial, qui calcule la fonction « factoriel » (retourne MAX_VALUE en cas de dépassement de capacité, sachant que le factoriel croit plus vite que la puissance) ;
- binomial(a, b), qui renvoie le coefficientbinomial, c'est-à-dire l'élément à la ligne « a » et à la position « b » dans un triangle de Pascal.
@Test
public
void
testPgcd
(
) {
// Arrange
final
int
a =
12
; // 3 x 4
final
int
b =
8
; // 2 x 4
final
int
expected =
4
;
// Act
final
int
pgcd =
IntMath.gcd
(
a, b);
// Assert
Assert.assertEquals
(
expected, pgcd);
}
@Test
public
void
testPascal
(
) {
// Arrange
final
int
a =
5
;
final
int
b =
2
;
final
int
expected =
10
;
// Act
final
int
binomial =
IntMath.binomial
(
a, b);
// Assert
Assert.assertEquals
(
expected, binomial);
}
La bibliothèque permet également quelques petites opérations sur les flottants à l'aide de « DoubleMath » :
- isMathematicalInteger, qui dit si une valeur est entière : 1.0 donne true ;
- roundToInt, qui arrondit vers un int selon le type d'arrondi spécifié ;
- roundToLong, qui arrondit vers un long ;
- roundToBigInteger, qui arrondit vers un BigInteger ;
- log2, qui calcule le logarithme et arrondit selon le type d'arrondi spécifié.
III. Conclusion▲
Nous venons de découvrir qu'il ne faut plus avoir peur des maths, enfin quand vous avez Guava sous la main. N'hésitez pas à consulter les autres épisodes de cette série pour découvrir les fonctionnalités fantastiques de la bibliothèque.
Vos retours nous aident à améliorer nos publications. N'hésitez donc pas à commenter cet article sur le forum : Commentez
IV. Remerciements▲
D'abord j'adresse mes remerciements à l'équipe Guava, chez Google, pour avoir développé une bibliothèque aussi utile et pour la maintenir. Je n'oublie pas tous les contributeurs qui participent notamment sur le forum Guava.
Plus spécifiquement en ce qui concerne cet article, je tiens à remercier l'équipe de Developpez.com et plus particulièrement Bernard Le Roux, Ricky81, Mickael Baron, Yann Caron, Logan, et Cédric Duprez.
V. Annexes▲
V-A. Liens▲
Guava : https://code.google.com/p/guava-libraries/
Article "Simplifier le code de vos beans Java à l'aide de Commons Lang, Guava et Lombok" :
https://thierry-leriche-dessirier.developpez.com/tutoriels/java/simplifier-code-guava-lombok/
Blog sur Guava : https://blog.developpez.com/guava/
Article "J2SE 1.5 Tiger" par Lionel Roux :
https://lroux.developpez.com/article/java/tiger/
Article "Présentation de Java SE 7" par F. Martini (adiGuba) :
https://adiguba.developpez.com/tutoriels/java/7/
V-B. Liens personnels▲
Retrouvez ma page et mes autres articles sur Developpez.com à l'adresse
https://thierry-leriche-dessirier.developpez.com/#page_articlesTutoriels
Suivez-moi sur Twitter : @thierryleriche(https://twitter.com/thierryleriche)@thierryleriche