JavaRush /Blog Java /Random-PL /Podzielmy klasę StringUtils
Roman Beekeeper
Poziom 35

Podzielmy klasę StringUtils

Opublikowano w grupie Random-PL
Witam wszystkich, moi drodzy czytelnicy. Staram się pisać o tym, co naprawdę mnie interesuje i co mnie w danej chwili martwi. Dlatego dzisiaj będzie trochę lekkiej lektury, która przyda Ci się jako odniesienie w przyszłości: porozmawiajmy o StringUtils . Podzielmy klasę StringUtils - 1Tak się złożyło, że pewnego razu pominąłem bibliotekę Apache Commons Lang 3 . Jest to biblioteka z klasami pomocniczymi do pracy z różnymi obiektami. Jest to zbiór przydatnych metod pracy z ciągami znaków, kolekcjami i tak dalej. W bieżącym projekcie, w którym musiałem bardziej szczegółowo pracować z ciągami znaków w tłumaczeniu 25-letniej logiki biznesowej (z COBOL-a na Javę), okazało się, że nie mam wystarczająco głębokiej wiedzy na temat klasy StringUtils . Musiałem więc wszystko stworzyć sam. Co miałem na myśli? Fakt, że nie trzeba samodzielnie pisać niektórych zadań polegających na manipulacji ciągami znaków, lecz skorzystać z gotowego rozwiązania. Co jest złego w tym, że sam to napiszesz? Przynajmniej pod tym względem jest to więcej kodu, który został już napisany dawno temu. Nie mniej paląca jest kwestia testowania dodatkowo napisanego kodu. Kiedy używamy biblioteki, która okazała się dobra, oczekujemy, że została już przetestowana i że nie musimy pisać wielu przypadków testowych, aby ją przetestować. Tak się składa, że ​​zestaw metod pracy z ciągiem znaków w Javie nie jest aż tak duży. Metod, które przydałyby się w pracy, jest naprawdę niewiele. Ta klasa została również utworzona w celu zapewnienia kontroli wyjątku NullPointerException. Zarys naszego artykułu będzie następujący:
  1. Jak się połączyć?
  2. Przykłady z mojej pracy: jak nie wiedząc o tak przydatnych zajęciach, stworzyłem swoją kulę rowerową .
  3. Przyjrzyjmy się innym metodom, które uznałem za interesujące.
  4. Podsumujmy.
Wszystkie przypadki zostaną dodane do osobnego repozytorium w organizacji społeczności Javarush na GitHubie. Będą dla nich osobne przykłady i testy.

0. Jak się połączyć

Ci, którzy idą ze mną ramię w ramię, znają już mniej więcej zarówno Gita, jak i Mavena, więc dalej będę polegać na tej wiedzy i nie będę się powtarzać. Dla tych, którzy przegapili moje poprzednie artykuły lub dopiero zaczęli czytać, oto materiały o Mavenie i Gicie . Oczywiście bez systemu kompilacji (Maven, Gredl) można też wszystko podłączyć ręcznie, ale w dzisiejszych czasach jest to szaleństwo i zdecydowanie nie trzeba tego robić w ten sposób: lepiej od razu nauczyć się, jak wszystko zrobić poprawnie. Dlatego, aby pracować z Mavenem, najpierw dodajemy odpowiednią zależność:
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
Gdzie ${apache.common.version} to wersja tej biblioteki. Następnie, aby zaimportować w jakiejś klasie, dodaj import:
import org.apache.commons.lang3.StringUtils;
I to wszystko, wszystko jest w torbie))

1. Przykłady z prawdziwego projektu

  • metoda leftPad

Pierwszy przykład ogólnie wydaje się teraz tak głupi, że bardzo dobrze, że moi koledzy wiedzieli o StringUtils.leftPad i mi powiedzieli. Jakie było zadanie: kod został zbudowany w taki sposób, że konieczne było przekształcenie danych, jeśli nie dotarły całkiem poprawnie. Oczekiwano, że pole tekstowe powinno składać się wyłącznie z liczb, tj. jeśli jego długość wynosi 3, a jego wartość wynosi 1, wówczas wpis powinien mieć wartość „001”. Oznacza to, że najpierw musisz usunąć wszystkie spacje, a następnie pokryć je zerami. Więcej przykładów, aby wyjaśnić istotę zadania: od „12” -> „012” od „1” -> „001” I tak dalej. Co ja zrobiłem? Opisano to w klasie LeftPadExample . Napisałem metodę, która zrobi to wszystko:
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();
}
Jako podstawę przyjąłem pomysł, że możemy po prostu uzyskać różnicę między wartością oryginalną a wartością przyciętą i wypełnić ją zerami z przodu. Aby to zrobić, użyłem IntStream do wykonania tej samej operacji n razy. I to zdecydowanie trzeba przetestować. Oto, co mógłbym zrobić, gdybym wiedział wcześniej o metodzie StringUtils.leftPad :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
Jak widać, kodu jest znacznie mniej, a korzysta się także z biblioteki potwierdzonej przez wszystkich. W tym celu stworzyłem dwa testy w klasie LeftPadExampleTest (zwykle gdy planują przetestować klasę, tworzą klasę o tej samej nazwie + Test w tym samym pakiecie, tylko w src/test/java). Testy te sprawdzają jedną metodę, aby upewnić się, że poprawnie przekształca wartość, a następnie inną. Oczywiście trzeba by napisać znacznie więcej testów, ale w naszym przypadku testowanie nie jest głównym tematem:
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);
   }

}
Na razie mogę podzielić się kilkoma uwagami na temat testów. Są one napisane przy użyciu JUnit 5:
  1. Test będzie traktowany jako test, jeśli będzie opatrzony odpowiednią adnotacją - @Test.
  2. Jeśli opisanie działania testu w nazwie jest trudne lub opis jest długi i niewygodny w czytaniu, możesz dodać adnotację @DisplayName i uczynić go normalnym opisem, który będzie widoczny podczas uruchamiania testów.
  3. Pisząc testy stosuję podejście BDD, w którym testy dzielę na logiczne części:
    1. //podano - blok ustawień danych przed testem;
    2. //kiedy jest blok, w którym uruchamiana jest część kodu, którą testujemy;
    3. //then to blok, w którym sprawdzane są wyniki bloku kiedy.
Jeśli je uruchomisz, potwierdzą, że wszystko działa zgodnie z oczekiwaniami.

  • metoda stripStart

Tutaj musiałem rozwiązać problem z linią, która na początku mogła zawierać spacje i przecinki. Po transformacji nie powinny mieć nowego znaczenia. Opis problemu jest wyraźniejszy niż kiedykolwiek. Kilka przykładów wzmocni nasze zrozumienie: „, , książki” -> „książki” „,,, książki” -> „książki” b , książki” -> „b , książki” Podobnie jak w przypadku leftPad, dodałem Klasa StrimStartExample , w której znajdują się dwie metody. Jeden - z własnym rozwiązaniem:
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);
}
Tutaj pomysł polegał na znalezieniu indeksu, od którego nie ma już spacji ani przecinków. Jeśli na początku w ogóle ich nie było, wówczas indeks będzie wynosił zero. A drugi - z rozwiązaniem poprzez StringUtils :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
Tutaj w pierwszym argumencie przekazujemy informację o tym, z jakim ciągiem pracujemy, a w drugim podajemy ciąg składający się ze znaków, które należy pominąć. Klasę StripStartExampleTest tworzymy w ten sam sposób :
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);
   }
}

  • metoda isEmpty

Ta metoda jest oczywiście znacznie prostsza, ale to nie czyni jej mniej użyteczną. Rozszerza możliwości metody String.isEmpty() , która dodaje również sprawdzanie wartości null. Po co? Aby uniknąć wyjątku NullPointerException, to znaczy uniknąć wywoływania metod na zmiennej, która ma wartość null . Żeby więc nie pisać:
if(value != null && value.isEmpty()) {
   //doing something
}
Możesz po prostu to zrobić:
if(StringUtils.isEmpty(value)) {
   //doing something
}
Zaletą tej metody jest to, że od razu wiadomo, która metoda jest stosowana.

2. Analiza pozostałych metod klasy StringUtils

Porozmawiajmy teraz o tych metodach, które moim zdaniem również zasługują na uwagę. Mówiąc ogólnie o StringUtils warto powiedzieć, że udostępnia on bezpieczne metody null, analogiczne do tych, które znajdziemy w klasie String (tak jak ma to miejsce w przypadku metody isEmpty ). Przejrzyjmy je:

  • porównaj metodę

Taka metoda istnieje w String i zgłosi wyjątek NullPointerException, jeśli podczas porównywania dwóch ciągów jeden z nich ma wartość null. Aby uniknąć brzydkich kontroli w naszym kodzie, możemy użyć metody StringUtils.compare(String str1, String str2) : zwraca ona wartość int jako wynik porównania. Co oznaczają te wartości? int = 0, jeśli są takie same (lub oba mają wartość null). int < 0, jeśli str1 jest mniejszy niż str2. int > 0, jeśli str1 jest większy niż str2. Ponadto, jeśli spojrzysz na ich dokumentację, Javadoc tej metody przedstawia następujące scenariusze:
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

  • zawiera... metody

Tutaj twórcy narzędzi świetnie się bawili. Dostępna jest jakakolwiek metoda, którą chcesz. Postanowiłem je połączyć:
  1. zawiera to metoda sprawdzająca, czy oczekiwany ciąg znaków znajduje się wewnątrz innego ciągu. Jak to jest przydatne? Możesz skorzystać z tej metody, jeśli chcesz się upewnić, że w tekście znajduje się określone słowo.

    Przykłady:

    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

    Ponownie obecne są zabezpieczenia NPE (wyjątek wskaźnika zerowego).

  2. zawieraAny to metoda sprawdzająca, czy występuje którykolwiek ze znaków występujących w ciągu. Również przydatna rzecz: często musisz to robić.

    Przykłady z dokumentacji:

    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. zawieraIgnoreCase jest użytecznym rozszerzeniem metody zawiera . Rzeczywiście, aby sprawdzić taki przypadek bez tej metody, będziesz musiał przejść przez kilka opcji. I tak tylko jedna metoda będzie stosowana harmonijnie.

  4. Kilka przykładów z dokumentów:

    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. zawieraNone - sądząc po nazwie, można już zrozumieć, co jest sprawdzane. Wewnątrz nie powinno być żadnych linii. Zdecydowanie przydatna rzecz. Szybkie wyszukiwanie niechcianych postaci ;). W naszym bocie telegramowym będziemy filtrować wulgaryzmy i nie będziemy ignorować tych zabawnych metod.

    I przykłady, gdzie bylibyśmy bez nich:

    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

  • metoda domyślnaString

Seria metod, które pomagają uniknąć dodawania dodatkowych informacji, jeśli ciąg znaków ma wartość null i trzeba ustawić wartość domyślną. Istnieje wiele opcji, które zaspokoją każdy gust. Najważniejszym z nich jest StringUtils.defaultString(final String str, final String defaultStr) - w przypadku, gdy str ma wartość null, po prostu przekażemy wartość defaultStr . Przykłady z dokumentacji:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
Jest bardzo wygodny w użyciu, gdy tworzysz klasę POJO z danymi.

  • metoda usuwania białych znaków

Jest to ciekawa metoda, choć możliwości jej zastosowania nie jest zbyt wiele. Jednocześnie, jeśli pojawi się taki przypadek, metoda z pewnością będzie bardzo przydatna. Usuwa wszystkie spacje z ciągu. Gdziekolwiek jest ta luka, nie będzie po niej śladu))) Przykłady z dokumentów:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • kończy się metodą

Mówi samo za siebie. Jest to bardzo przydatna metoda: sprawdza, czy ciąg kończy się sugerowanym ciągiem, czy nie. Jest to często konieczne. Można oczywiście wypisać czek samodzielnie, jednak skorzystanie z gotowego sposobu jest zdecydowanie wygodniejsze i lepsze. Przykłady:
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
Jak widać wszystko kończy się pustą linijką))) Myślę, że ten przykład (StringUtils.endsWith("ABCDEF", "") = true) to tylko bonus, bo to absurd) Istnieje również metoda, która ignoruje wielkość liter.

  • metoda równa się

Świetny przykład bezpiecznej metody o wartości null, która porównuje dwa ciągi. Cokolwiek tam umieścimy, odpowiedź będzie tam i będzie bez błędów. Przykłady:
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
Oczywiście jest też równościIgnoreCase – wszystko odbywa się dokładnie w ten sam sposób, tyle że ignorujemy wielkość liter. Zobaczmy?
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

  • metoda równaAny

Przejdźmy dalej i rozszerzmy metodę równości . Załóżmy, że zamiast kilku kontroli równości chcemy przeprowadzić jedną. W tym celu możemy przekazać ciąg znaków, z którym zostanie porównany zbiór ciągów; jeśli któryś z nich będzie równy zaproponowanemu, będzie to PRAWDA. Przekazujemy ciąg znaków i kolekcję ciągów, aby je ze sobą porównać (pierwszy ciąg z ciągami z kolekcji). Trudny? Oto przykłady z dokumentów, które pomogą Ci zrozumieć, co to oznacza:
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
Istnieje również RównaAnyIgnoreCase . I przykłady na to:
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

Konkluzja

Dzięki temu wychodzimy z wiedzą czym jest StringUtils i jakie ma przydatne metody. No cóż, ze świadomością, że są takie przydatne rzeczy i nie ma potrzeby każdorazowego płoszenia się kulami w miejscach, w których dałoby się zamknąć sprawę za pomocą gotowego rozwiązania. Ogólnie rzecz biorąc, przeanalizowaliśmy tylko część metod. Jeśli chcesz, mogę kontynuować: jest ich znacznie więcej i naprawdę zasługują na uwagę. Jeśli masz pomysł jak inaczej można by to przedstawić, napisz – zawsze jestem otwarty na nowe pomysły. Dokumentacja metod jest napisana bardzo dobrze, dodano przykłady testów z wynikami, co pozwala lepiej zrozumieć działanie metody. Dlatego nie boimy się zapoznać z dokumentacją: rozwieje ona Twoje wątpliwości co do funkcjonalności narzędzia. Aby zdobyć nowe doświadczenie w kodowaniu, radzę przyjrzeć się sposobowi tworzenia i pisania klas narzędziowych. Przyda się to w przyszłości, ponieważ zazwyczaj każdy projekt ma swoje własne klasy złomu, a doświadczenie w ich pisaniu się przyda. Tradycyjnie proponuję subskrybować moje konto na Githubie ) Dla tych, którzy nie wiedzą o moim projekcie z botem telegramowym, poniżej link do pierwszego artykułu . Dziękuję wszystkim za przeczytanie. Poniżej dodałem kilka przydatnych linków.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION