JavaRush /Blog Java /Random-FR /Décomposons la classe StringUtils
Roman Beekeeper
Niveau 35

Décomposons la classe StringUtils

Publié dans le groupe Random-FR
Bonjour à tous, mes chers lecteurs. J'essaie d'écrire sur ce qui m'intéresse vraiment et ce qui m'inquiète en ce moment. Par conséquent, aujourd'hui, il y aura quelques lectures légères qui vous seront utiles comme référence à l'avenir : parlons de StringUtils . Décomposons la classe StringUtils - 1Il se trouve qu'à un moment donné, j'ai contourné la bibliothèque Apache Commons Lang 3 . Il s'agit d'une bibliothèque avec des classes auxiliaires pour travailler avec différents objets. Il s'agit d'un ensemble de méthodes utiles pour travailler avec des chaînes, des collections, etc. Sur un projet en cours, où j'ai dû travailler plus en détail avec des chaînes pour traduire une logique métier vieille de 25 ans (de COBOL vers Java), il s'est avéré que je n'avais pas une connaissance suffisamment approfondie de la classe StringUtils . J’ai donc dû tout créer moi-même. Ce que je veux dire? Le fait que vous n'êtes pas obligé d'écrire vous-même certaines tâches impliquant la manipulation de chaînes, mais d'utiliser une solution toute faite. Qu'y a-t-il de mal à l'écrire soi-même ? Au moins dans la mesure où il s'agit d'un code supplémentaire qui a déjà été écrit il y a longtemps. Non moins urgente est la question du test du code écrit en plus. Lorsque nous utilisons une bibliothèque qui a fait ses preuves, nous nous attendons à ce qu'elle ait déjà été testée et que nous n'ayons pas besoin d'écrire de nombreux cas de test pour la tester. Il se trouve que l'ensemble des méthodes permettant de travailler avec une chaîne en Java n'est pas si vaste. Il n'y a vraiment pas beaucoup de méthodes utiles pour le travail. Cette classe est également créée pour fournir des vérifications pour NullPointerException. Le plan de notre article sera le suivant :
  1. Comment se connecter?
  2. Exemples tirés de mon travail : comment, sans connaître un cours aussi utile, j'ai créé ma béquille de vélo .
  3. Regardons d'autres méthodes que j'ai trouvées intéressantes.
  4. Résumons.
Tous les cas seront ajoutés à un référentiel distinct dans l'organisation de la communauté Javarush sur GitHub. Il y aura des exemples et des tests séparés pour eux.

0. Comment se connecter

Ceux qui marchent main dans la main avec moi connaissent déjà plus ou moins Git et Maven, je m'appuierai donc davantage sur ces connaissances et ne me répéterai pas. Pour ceux qui ont raté mes articles précédents ou qui viennent de commencer à lire, voici des documents sur Maven et Git . Bien sûr, sans système de build (Maven, Gredl), vous pouvez aussi tout connecter manuellement, mais c'est fou de nos jours et vous n'avez certainement pas besoin de le faire comme ça : il vaut mieux apprendre immédiatement à tout faire correctement. Par conséquent, pour travailler avec Maven, nous ajoutons d’abord la dépendance appropriée :
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
${apache.common.version} est la version de cette bibliothèque. Ensuite, pour importer dans une classe, ajoutez import :
import org.apache.commons.lang3.StringUtils;
Et c'est tout, tout est dans le sac))

1. Exemples tirés d'un projet réel

  • Méthode leftPad

Le premier exemple semble généralement tellement stupide maintenant qu'il est très bien que mes collègues connaissaient StringUtils.leftPad et me l'aient dit. Quelle était la tâche : le code était construit de telle manière qu'il fallait transformer les données si elles n'arrivaient pas tout à fait correctement. Il était prévu que le champ de chaîne soit uniquement composé de nombres, c'est-à-dire si sa longueur est 3 et sa valeur est 1, alors l'entrée doit être « 001 ». Autrement dit, vous devez d'abord supprimer tous les espaces, puis les recouvrir de zéros. Plus d'exemples pour clarifier l'essence de la tâche : de « 12 » -> « 012 » de « 1 » -> « 001 » Et ainsi de suite. Qu'est-ce que j'ai fait? Décrit cela dans la classe LeftPadExample . J'ai écrit une méthode qui fera tout cela :
public static String ownLeftPad(String value) {
   String trimmedValue = value.trim();

   if(trimmedValue.length() == value.length()) {
       return value;
   }

   StringBuilder newValue = new StringBuilder(trimmedValue);

   IntStream.rangeClosed(1, value.length() - trimmedValue.length())
           .forEach(it -> newValue.insert(0, "0"));
   return newValue.toString();
}
Comme base, j'ai pris l'idée que nous pouvons simplement obtenir la différence entre la valeur originale et la valeur tronquée et la remplir de zéros devant. Pour ce faire, j'ai utilisé IntStream pour effectuer la même opération n fois. Et cela doit absolument être testé. Voici ce que j'aurais pu faire si j'avais connu à l'avance la méthode StringUtils.leftPad :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
Comme vous pouvez le constater, il y a beaucoup moins de code, et une bibliothèque confirmée par tout le monde est également utilisée. Pour cela, j'ai créé deux tests dans la classe LeftPadExampleTest (généralement lorsqu'ils envisagent de tester une classe, ils créent une classe du même nom + Test dans le même package, uniquement dans src/test/java). Ces tests vérifient une méthode pour s'assurer qu'elle transforme correctement la valeur, puis une autre. Bien sûr, il faudrait écrire beaucoup plus de tests, mais les tests ne sont pas le sujet principal dans notre cas :
package com.github.javarushcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Unit-level testing for LeftPadExample")
class LeftPadExampleTest {

   @DisplayName("Should transform by using ownLeftPad method as expected")
   @Test
   public void shouldTransformOwnLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.ownLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

}
Je peux faire quelques commentaires sur les tests pour l'instant. Ils sont écrits en utilisant JUnit 5 :
  1. Un test sera traité comme un test s'il possède l'annotation appropriée - @Test.
  2. S'il est difficile de décrire le fonctionnement du test dans le nom ou si la description est longue et peu pratique à lire, vous pouvez ajouter l'annotation @DisplayName et en faire une description normale qui sera visible lors de l'exécution des tests.
  3. Lors de l'écriture des tests, j'utilise l'approche BDD, dans laquelle je divise les tests en parties logiques :
    1. //donné - bloc de configuration des données avant le test ;
    2. //quand est le bloc où est lancée la partie du code que nous testons ;
    3. //then est un bloc dans lequel les résultats du bloc when sont vérifiés.
Si vous les exécutez, ils confirmeront que tout fonctionne comme prévu.

  • Méthode stripStart

Ici, je devais résoudre un problème avec une ligne qui pouvait avoir des espaces et des virgules au début. Après la transformation, ils n'auraient pas dû avoir une nouvelle signification. L’énoncé du problème est plus clair que jamais. Quelques exemples renforceront notre compréhension : « , , books » -> « books » « ,,, books » -> « books » b , books » -> « b , books » Comme dans le cas de leftPad, j’ai ajouté le Classe StrimStartExample , dans laquelle dispose de deux méthodes. Un - avec sa propre solution :
public static String ownStripStart(String value) {
   int index = 0;
   List commaSpace = asList(" ", ",");
   for (int i = 0; i < value.length(); i++) {
       if (commaSpace.contains(String.valueOf(value.charAt(i)))) {
           index++;
       } else {
           break;
       }
   }
   return value.substring(index);
}
Ici l'idée était de trouver l'index à partir duquel il n'y a plus d'espaces ni de virgules. S'ils n'étaient pas du tout là au début, alors l'indice sera nul. Et le second - avec une solution via StringUtils :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
Ici, nous transmettons le premier argument des informations sur la chaîne avec laquelle nous travaillons, et dans le second, nous transmettons une chaîne composée de caractères qui doivent être ignorés. Nous créons la classe StripStartExampleTest de la même manière :
package com.github.javarushcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Unit-level testing for StripStartExample")
class StripStartExampleTest {

   @DisplayName("Should transform by using stripStart method as expected")
   @Test
   public void shouldTransformOwnStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.ownStripStart(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }
}

  • Méthode isEmpty

Cette méthode est bien sûr beaucoup plus simple, mais cela ne la rend pas moins utile. Il étend les capacités de la méthode String.isEmpty() , qui ajoute également une vérification de null. Pour quoi? Pour éviter NullPointerException, c'est-à-dire pour éviter d'appeler des méthodes sur une variable qui est null . Donc, pour ne pas écrire :
if(value != null && value.isEmpty()) {
   //doing something
}
Vous pouvez simplement faire ceci :
if(StringUtils.isEmpty(value)) {
   //doing something
}
L’avantage de cette méthode est qu’il est immédiatement clair où quelle méthode est utilisée.

2. Analyse d'autres méthodes de la classe StringUtils

Parlons maintenant de ces méthodes qui, à mon avis, méritent également notre attention. Parlant de manière générale de StringUtils , il convient de dire qu'il fournit des méthodes null safe analogues à celles trouvées dans la classe String (comme c'est le cas avec la méthode isEmpty ). Passons-les en revue :

  • comparer la méthode

Une telle méthode existe dans String et lèvera une NullPointerException si, lors de la comparaison de deux chaînes, l'une d'elles est nulle. Pour éviter les vérifications laides dans notre code, nous pouvons utiliser la méthode StringUtils.compare(String str1, String str2) : elle renvoie un int comme résultat de la comparaison. Que signifient ces valeurs ? int = 0 s'ils sont identiques (ou les deux sont nuls). int < 0, si str1 est inférieur à str2. int > 0, si str1 est supérieur à str2. Aussi, si vous regardez leur documentation, le Javadoc de cette méthode présente les scénarios suivants :
StringUtils.compare(null, null)   = 0
StringUtils.compare(null , "a")   < 0
StringUtils.compare("a", null)    > 0
StringUtils.compare("abc", "abc") = 0
StringUtils.compare("a", "b")     < 0
StringUtils.compare("b", "a")     > 0
StringUtils.compare("a", "B")     > 0
StringUtils.compare("ab", "abc")  < 0

  • contient... des méthodes

Ici, les développeurs d’utilitaires se sont bien amusés. Quelle que soit la méthode que vous souhaitez, elle existe. J'ai décidé de les regrouper :
  1. contain est une méthode qui vérifie si la chaîne attendue se trouve dans une autre chaîne. En quoi est-ce utile ? Vous pouvez utiliser cette méthode si vous devez vous assurer qu'il y a un certain mot dans le texte.

    Exemples:

    StringUtils.contains(null, *)     = false
    StringUtils.contains(*, null)     = false
    StringUtils.contains("", "")      = true
    StringUtils.contains("abc", "")   = true
    StringUtils.contains("abc", "a")  = true
    StringUtils.contains("abc", "z")  = false

    Encore une fois, la sécurité NPE (Null Pointer Exception) est présente.

  2. containAny est une méthode qui vérifie si l'un des caractères présents dans la chaîne est présent. C'est aussi une chose utile : vous devez souvent le faire.

    Exemples tirés de la documentation :

    StringUtils.containsAny(null, *)                  = false
    StringUtils.containsAny("", *)                    = false
    StringUtils.containsAny(*, null)                  = false
    StringUtils.containsAny(*, [])                    = false
    StringUtils.containsAny("zzabyycdxx", ['z', 'a']) = true
    StringUtils.containsAny("zzabyycdxx", ['b', 'y']) = true
    StringUtils.containsAny("zzabyycdxx", ['z', 'y']) = true
    StringUtils.containsAny("aba", ['z'])             = false

  3. containIgnoreCase est une extension utile de la méthode contain . En effet, pour vérifier un tel cas sans cette méthode, vous devrez passer par plusieurs options. Ainsi une seule méthode sera utilisée harmonieusement.

  4. Quelques exemples tirés de la documentation :

    StringUtils.containsIgnoreCase(null, *) = false
    StringUtils.containsIgnoreCase(*, null) = false
    StringUtils.containsIgnoreCase("", "") = true
    StringUtils.containsIgnoreCase("abc", "") = true
    StringUtils.containsIgnoreCase("abc", "a") = true
    StringUtils.containsIgnoreCase("abc", "z") = false
    StringUtils.containsIgnoreCase("abc", "A") = true
    StringUtils.containsIgnoreCase("abc", "Z") = false

  5. containNone - à en juger par le nom, vous pouvez déjà comprendre ce qui est vérifié. Il ne devrait y avoir aucune ligne à l’intérieur. Une chose utile, certainement. Recherche rapide de certains caractères indésirables ;). Dans notre bot de télégramme, nous filtrerons les obscénités et n'ignorerons pas ces méthodes amusantes.

    Et des exemples, où serions-nous sans eux :

    StringUtils.containsNone(null, *)       = true
    StringUtils.containsNone(*, null)       = true
    StringUtils.containsNone("", *)         = true
    StringUtils.containsNone("ab", '')      = true
    StringUtils.containsNone("abab", 'xyz') = true
    StringUtils.containsNone("ab1", 'xyz')  = true
    StringUtils.containsNone("abz", 'xyz')  = false

  • Méthode defaultString

Une série de méthodes qui permettent d'éviter d'ajouter une information supplémentaire si la chaîne est nulle et que vous devez définir une valeur par défaut. Il existe de nombreuses options pour tous les goûts. Le principal d'entre eux est StringUtils.defaultString(final String str, final String defaultStr) - dans le cas où str est nul, nous transmettrons simplement la valeur defaultStr . Exemples tirés de la documentation :
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
Il est très pratique à utiliser lorsque vous créez une classe POJO avec des données.

  • Méthode deleteWhitespace

C'est une méthode intéressante, même si il n'existe pas beaucoup d'options pour son application. En même temps, si un tel cas se présente, la méthode sera certainement très utile. Il supprime tous les espaces de la chaîne. Où que se trouve cet écart, il n'y en aura aucune trace))) Exemples tirés de la documentation :
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • méthode se termine avec

Parle de lui-même. C'est une méthode très utile : elle vérifie si la chaîne se termine ou non par la chaîne suggérée. Cela est souvent nécessaire. Bien sûr, vous pouvez rédiger le chèque vous-même, mais utiliser une méthode toute faite est clairement plus pratique et meilleure. Exemples:
StringUtils.endsWith(null, null)      = true
StringUtils.endsWith(null, "def")     = false
StringUtils.endsWith("abcdef", null)  = false
StringUtils.endsWith("abcdef", "def") = true
StringUtils.endsWith("ABCDEF", "def") = false
StringUtils.endsWith("ABCDEF", "cde") = false
StringUtils.endsWith("ABCDEF", "")    = true
Comme vous pouvez le voir, tout se termine par une ligne vide))) Je pense que cet exemple (StringUtils.endsWith("ABCDEF", "") = true) n'est qu'un bonus, car c'est absurde) Il existe aussi une méthode qui ignore le cas.

  • méthode égale

Un excellent exemple de méthode null-safe qui compare deux chaînes. Quoi que nous y mettions, la réponse sera là, et elle sera sans erreur. Exemples:
StringUtils.equals(null, null)   = true
StringUtils.equals(null, "abc")  = false
StringUtils.equals("abc", null)  = false
StringUtils.equals("abc", "abc") = true
StringUtils.equals("abc", "ABC") = false
Bien sûr, il existe également equalsIgnoreCase - tout est fait exactement de la même manière, sauf que nous ignorons la casse. Voyons?
StringUtils.equalsIgnoreCase(null, null)   = true
StringUtils.equalsIgnoreCase(null, "abc")  = false
StringUtils.equalsIgnoreCase("abc", null)  = false
StringUtils.equalsIgnoreCase("abc", "abc") = true
StringUtils.equalsIgnoreCase("abc", "ABC") = true

  • méthode égaleAny

Allons de l'avant et étendons la méthode equals . Disons qu'au lieu de plusieurs contrôles d'égalité, nous souhaitons en effectuer un seul. Pour cela, nous pouvons passer une chaîne avec laquelle un ensemble de chaînes sera comparé ; si l'une d'elles est égale à celle proposée, elle sera VRAIE. Nous passons une chaîne et une collection de chaînes pour les comparer entre elles (la première chaîne avec les chaînes de la collection). Difficile? Voici des exemples tirés de la documentation pour vous aider à comprendre ce que cela signifie :
StringUtils.equalsAny(null, (CharSequence[]) null) = false
StringUtils.equalsAny(null, null, null)    = true
StringUtils.equalsAny(null, "abc", "def")  = false
StringUtils.equalsAny("abc", null, "def")  = false
StringUtils.equalsAny("abc", "abc", "def") = true
StringUtils.equalsAny("abc", "ABC", "DEF") = false
Il existe également equalsAnyIgnoreCase . Et des exemples pour cela :
StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null) = false
StringUtils.equalsAnyIgnoreCase(null, null, null)    = true
StringUtils.equalsAnyIgnoreCase(null, "abc", "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", null, "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", "abc", "def") = true
StringUtils.equalsAnyIgnoreCase("abc", "ABC", "DEF") = true

Conclusion

En conséquence, nous repartons avec la connaissance de ce qu'est StringUtils et de ses méthodes utiles. Eh bien, en réalisant qu'il existe des choses tellement utiles et qu'il n'est pas nécessaire de clôturer à chaque fois avec des béquilles des endroits où il serait possible de clore le problème à l'aide d'une solution toute faite. En général, nous n’avons analysé qu’une partie des méthodes. Si vous le souhaitez, je peux continuer : il y en a beaucoup plus, et ils méritent vraiment qu'on s'y intéresse. Si vous avez des idées sur la manière dont cela pourrait être présenté autrement, écrivez-nous - je suis toujours ouvert aux nouvelles idées. La documentation des méthodes est très bien rédigée, des exemples de tests avec résultats sont ajoutés, ce qui permet de mieux comprendre le fonctionnement de la méthode. Par conséquent, nous n’hésitons pas à lire la documentation : elle dissipera vos doutes sur la fonctionnalité de l’utilitaire. Pour acquérir une nouvelle expérience de codage, je vous conseille de regarder comment les classes utilitaires sont créées et écrites. Cela sera utile à l'avenir, car généralement chaque projet a ses propres classes de scrap, et l'expérience de leur écriture sera utile. Traditionnellement, je vous propose de vous abonner à mon compte sur Github ) Pour ceux qui ne connaissent pas mon projet avec un bot télégramme, voici un lien vers le premier article . Merci à tous d'avoir lu. J'ai ajouté quelques liens utiles ci-dessous.
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION