JavaRush /Java Blogu /Random-AZ /Gəlin StringUtils sinfini parçalayaq
Roman Beekeeper
Səviyyə

Gəlin StringUtils sinfini parçalayaq

Qrupda dərc edilmişdir
Hər kəsə salam əziz oxucularım. Məni həqiqətən maraqlandıran və hazırda məni narahat edən şeylərdən yazmağa çalışıram. Buna görə də, bu gün gələcəkdə istinad olaraq sizə faydalı olacaq bəzi yüngül oxunuşlar olacaq: gəlin StringUtils haqqında danışaq . StringUtils sinfini parçalayaq - 1Elə oldu ki, bir vaxtlar mən Apache Commons Lang 3 kitabxanasından yan keçdim . Bu, müxtəlif obyektlərlə işləmək üçün köməkçi sinifləri olan bir kitabxanadır. Bu, sətirlər, kolleksiyalar və s. ilə işləmək üçün faydalı üsullar toplusudur. 25 illik biznes məntiqini (COBOL-dan Java-ya) tərcümə etmək üçün sətirlərlə daha ətraflı işləməli olduğum cari layihədə StringUtils sinfi haqqında kifayət qədər dərin biliyə malik olmadığım ortaya çıxdı . Ona görə də hər şeyi özüm yaratmalı oldum. Nə demək istəyirəm? Fakt budur ki, simli manipulyasiya ilə bağlı müəyyən tapşırıqları özünüz yazmağınız lazım deyil, hazır bir həlldən istifadə edin. Bunu özünüz yazmağın nə günahı var? Ən azı bu, çoxdan yazılmış daha çox koddur. Əlavə olaraq yazılmış kodun sınaqdan keçirilməsi məsələsi də az aktual deyil. Özünün yaxşı olduğunu sübut edən bir kitabxanadan istifadə edərkən, onun artıq sınaqdan keçirildiyini və onu yoxlamaq üçün çoxlu test nümunələri yazmağımıza ehtiyac olmadığını gözləyirik. Elə olur ki, Java-da sətirlə işləmək üçün metodlar dəsti o qədər də böyük deyil. İş üçün faydalı olacaq üsullar həqiqətən çox deyil. Bu sinif həmçinin NullPointerException üçün çekləri təmin etmək üçün yaradılmışdır. Məqaləmizin xülasəsi belə olacaq:
  1. Necə qoşulmaq olar?
  2. İşimdən nümunələr: belə bir faydalı sinif haqqında bilmədən velosiped qoltuğumu necə yaratdım.
  3. Mənə maraqlı olan digər üsullara baxaq.
  4. Gəlin ümumiləşdirək.
Bütün hallar GitHub-da Javarush İcma təşkilatında ayrıca depoya əlavə olunacaq . Onlar üçün ayrıca nümunələr və testlər olacaq.

0. Necə qoşulmaq olar

Mənimlə əl-ələ gəzənlər artıq həm Git, həm də Mavenlə az-çox tanışdırlar, ona görə də bundan sonra bu biliyə arxalanacağam və özümü təkrarlamayacağam. Əvvəlki məqalələrimi qaçıranlar və ya yeni oxumağa başlayanlar üçün burada MavenGit haqqında materiallar var . Əlbəttə ki, bir quruluş sistemi olmadan (Maven, Gredl) hər şeyi əl ilə də birləşdirə bilərsiniz, lakin bu, indiki vaxtda dəlilikdir və bunu mütləq etmək lazım deyil: hər şeyi necə düzgün edəcəyinizi dərhal öyrənmək daha yaxşıdır. Buna görə də, Maven ilə işləmək üçün əvvəlcə müvafiq asılılığı əlavə edirik:
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
Harada ${apache.common.version} bu kitabxananın versiyasıdır. Sonra, bəzi sinifdə idxal etmək üçün idxalı əlavə edin:
import org.apache.commons.lang3.StringUtils;
Və budur, hamısı çantadadır))

1. Real layihədən nümunələr

  • sol Pad üsulu

Birinci nümunə, ümumiyyətlə, indi o qədər axmaq görünür ki, həmkarlarımın StringUtils.leftPad haqqında bilməsi və mənə deməsi çox yaxşıdır . Tapşırıq nədən ibarət idi: kod elə qurulmuşdu ki, məlumat düzgün gəlmədiyi təqdirdə onu çevirmək lazım idi. Sətir sahəsinin yalnız rəqəmlərdən ibarət olması gözlənilirdi, yəni. uzunluğu 3 və dəyəri 1-dirsə, giriş “001” olmalıdır. Yəni əvvəlcə bütün boşluqları çıxarmaq lazımdır, sonra isə onu sıfırlarla örtmək lazımdır. Tapşırığın mahiyyətini aydınlaşdırmaq üçün daha çox misal: “12“-dən -> “012” dən “1“ -> “001” və s. Mən nə etdim? Bunu LeftPadExample sinfində təsvir etmişdir . Bütün bunları edəcək bir üsul yazdım:
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();
}
Mən əsas götürdüm ki, biz sadəcə olaraq orijinal və kəsilmiş dəyər arasındakı fərqi əldə edib, qarşısında sıfırlarla doldura bilərik. Bunu etmək üçün eyni əməliyyatı n dəfə etmək üçün IntStream-dən istifadə etdim . Və bu mütləq sınaqdan keçirilməlidir. StringUtils.leftPad metodu haqqında əvvəlcədən bilsəydim nə edə bilərdim :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
Gördüyünüz kimi, kod çox azdır və hər kəs tərəfindən təsdiqlənmiş kitabxana da istifadə olunur. Bu məqsədlə mən LeftPadExampleTest sinfində iki test yaratdım (adətən onlar sinfi sınaqdan keçirməyi planlaşdırdıqda eyni adlı sinif yaradırlar + Eyni paketdə Test, yalnız src/test/java-da). Bu testlər bir üsulu yoxlayır ki, o, dəyəri düzgün çevirir, sonra isə başqa. Əlbəttə ki, daha çox testlər yazılmalıdır, lakin test bizim vəziyyətimizdə əsas mövzu deyil:
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);
   }

}
Hələlik testlərlə bağlı bir neçə şərh verə bilərəm. Onlar JUnit 5 istifadə edərək yazılmışdır:
  1. Test müvafiq annotasiyaya malik olduqda test kimi qəbul ediləcək - @Test.
  2. Testin işini adda təsvir etmək çətindirsə və ya təsvir uzun və oxunması əlverişsizdirsə, @DisplayName annotasiyasını əlavə edə və onu testlər həyata keçirərkən görünəcək normal təsvir edə bilərsiniz.
  3. Testləri yazarkən testləri məntiqi hissələrə böldüyüm BDD yanaşmasından istifadə edirəm:
    1. //verilmiş - testdən əvvəl verilənlərin qurulması bloku;
    2. //kodun sınaqdan keçirdiyimiz hissəsinin işə salındığı blok nə vaxtdır;
    3. //sonra zaman blokunun nəticələrinin yoxlanıldığı blokdur.
Onları işlətsəniz, hər şeyin gözlənildiyi kimi işlədiyini təsdiq edəcəklər.

  • stripStart üsulu

Burada mən əvvəlində boşluq və vergül ola bilən sətirlə problemi həll etməli oldum. Transformasiyadan sonra onlar yeni məna kəsb etməməli idilər. Problem ifadəsi həmişəkindən daha aydındır. Bir neçə nümunə anlayışımızı möhkəmləndirəcək: “, , kitablar” -> “kitablar” “,,, kitablar” -> “kitablar” b , kitablar” -> “b , kitablar” SolPad ilə olduğu kimi, mən də əlavə etdim. İki metodu olan StrimStartExample sinfi. Biri - öz həlli ilə:
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);
}
Burada fikir boşluq və vergül olmayan indeksi tapmaq idi. Başlanğıcda onlar ümumiyyətlə yox idisə, onda indeks sıfır olacaq. İkincisi - StringUtils vasitəsilə həll yolu ilə :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
Burada hansı sətirlə işlədiyimiz haqqında birinci arqument məlumatını, ikincidə isə atlanması lazım olan simvollardan ibarət sətri keçirik. StripStartExampleTest sinfini eyni şəkildə yaradırıq :
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);
   }
}

  • isEmpty metodu

Bu üsul, əlbəttə ki, daha sadədir, lakin bu, onu daha az faydalı etmir. O , String.isEmpty() metodunun imkanlarını genişləndirir , bu da null üçün yoxlama əlavə edir. Nə üçün? NullPointerException-dan qaçmaq üçün, yəni null olan dəyişəndə ​​metodları çağırmaqdan qaçınmaq üçün . Buna görə də yazmamaq üçün:
if(value != null && value.isEmpty()) {
   //doing something
}
Bunu sadəcə olaraq edə bilərsiniz:
if(StringUtils.isEmpty(value)) {
   //doing something
}
Bu metodun üstünlüyü ondan ibarətdir ki, hansı üsuldan istifadə edildiyi dərhal aydın olur.

2. StringUtils sinfinin digər metodlarının təhlili

İndi, mənim fikrimcə, diqqətə layiq olan üsullar haqqında danışaq. Ümumilikdə StringUtils haqqında danışarkən qeyd etmək lazımdır ki, o, String sinfində tapılanlara bənzər null təhlükəsiz metodlar təqdim edir ( isEmpty metodunda olduğu kimi ). Gəlin onlardan keçək:

  • müqayisə üsulu

Belə bir üsul String -də mövcuddur və iki sətir müqayisə edərkən onlardan biri null olarsa, NullPointerException atacaq. Kodumuzda çirkin yoxlamaların qarşısını almaq üçün StringUtils.compare(String str1, String str2) metodundan istifadə edə bilərik : o, müqayisə nəticəsində int qaytarır. Bu dəyərlər nə deməkdir? int = 0 eynidirsə (yaxud hər ikisi sıfırdır). int < 0, əgər str1 str2-dən kiçikdirsə. int > 0, əgər str1 str2-dən böyükdürsə. Həmçinin, onların sənədlərinə baxsanız, bu metodun Javadoc proqramı aşağıdakı ssenariləri təqdim edir:
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

  • ... üsulları ehtiva edir

Burada kommunal proqram tərtibatçıları bir partlayış yaşadılar. İstədiyiniz üsul var. Onları bir yerə yığmaq qərarına gəldim:
  1. ehtiva gözlənilən sətirin başqa bir sətirdə olub olmadığını yoxlayan bir üsuldur. Bu necə faydalıdır? Mətndə müəyyən bir sözün olduğuna əmin olmaq lazımdırsa, bu üsuldan istifadə edə bilərsiniz.

    Nümunələr:

    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

    Yenə də NPE (Null Pointer İstisna) təhlükəsizliyi mövcuddur.

  2. containAny sətirdə mövcud olan simvollardan hər hansı birinin olub-olmadığını yoxlayan bir üsuldur. Həm də faydalı bir şey: bunu tez-tez etməlisiniz.

    Sənədlərdən nümunələr:

    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 , contain metodunun faydalı uzantısıdır . Həqiqətən, bu üsul olmadan belə bir işi yoxlamaq üçün bir neçə variantdan keçməli olacaqsınız. Beləliklə, yalnız bir üsul ahəngdar şəkildə istifadə ediləcəkdir.

  4. Sənədlərdən bir neçə nümunə:

    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 - adına əsasən, nəyin yoxlanıldığını artıq başa düşə bilərsiniz. İçəridə heç bir xətt olmamalıdır. Faydalı bir şey, mütləq. Bəzi arzuolunmaz simvollar üçün sürətli axtarış;). Telegram botumuzda ədəbsizləri süzgəcdən keçirəcəyik və bu gülməli üsulları nəzərdən qaçırmayacağıq.

    Və misallar, onlarsız harda olardıq:

    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

  • defaultString metodu

Əgər sətir boşdursa və siz bəzi standart dəyər təyin etməlisinizsə, əlavə məlumat əlavə etməkdən qaçmağa kömək edən bir sıra üsullar. Hər zövqə uyğun bir çox variant var. Onların arasında ən başlıcası StringUtils.defaultString(son String str, final String defaultStr) - str null olarsa, sadəcə olaraq dəyəri defaultStr- ə keçirəcəyik . Sənədlərdən nümunələr:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
Məlumatlarla POJO sinfi yaratdığınız zaman istifadə etmək çox rahatdır.

  • deleteWhitespace metodu

Bu maraqlı bir üsuldur, baxmayaraq ki, onun tətbiqi üçün bir çox variant yoxdur. Eyni zamanda, belə bir vəziyyət yaranarsa, üsul mütləq çox faydalı olacaqdır. O, sətirdəki bütün boşluqları silir. Bu boşluq haradadırsa, onun izi də qalmayacaq))) Sənədlərdən nümunələr:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • metodu ilə bitir

Özü üçün danışır. Bu, çox faydalı üsuldur: o, sətirin təklif olunan sətirlə bitib-bitmədiyini yoxlayır. Bu tez-tez lazımdır. Əlbəttə ki, çeki özünüz yaza bilərsiniz, lakin hazır bir üsuldan istifadə etmək daha rahat və daha yaxşıdır. Nümunələr:
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
Gördüyünüz kimi hər şey boş sətirlə bitir))) Məncə bu misal (StringUtils.endsWith("ABCDEF", "") = true) sadəcə bonusdur, çünki bu absurddur) Bir üsul da var ki, halına məhəl qoymur.

  • metoduna bərabərdir

İki sətiri müqayisə edən sıfır təhlükəsiz metodun əla nümunəsi. Oraya nə qoysaq, cavab orada olacaq və səhvsiz olacaq. Nümunələr:
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
Əlbəttə ki, equalsIgnoreCase də var - hər şey eyni şəkildə edilir, yalnız biz işi görməməzlikdən gəlirik. Görək?
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

  • istənilən üsula bərabərdir

Gəlin davam edək və bərabərlik metodunu genişləndirək . Deyək ki, bir neçə bərabərlik yoxlaması yerinə, birini yerinə yetirmək istəyirik. Bunun üçün bir sıra sətirlərin müqayisə ediləcəyi bir sətir ötürə bilərik, əgər onlardan hər hansı biri təklif edilənə bərabərdirsə, bu, DOĞRU olacaq. Biz onları bir-biri ilə müqayisə etmək üçün simi və sətirlər toplusunu keçirik (kolleksiyadan simlərlə birinci sim). Çətin? Nə demək olduğunu başa düşməyə kömək etmək üçün sənədlərdən nümunələr:
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
EqualsAnyIgnoreCase də var . Və bunun üçün nümunələr:
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

Alt xətt

Nəticədə, StringUtils-in nə olduğunu və onun hansı faydalı metodlara sahib olduğunu bilməklə ayrılırıq. Yaxşı, belə faydalı şeylərin olduğunu başa düşməklə və hər dəfə hazır bir həllin köməyi ilə məsələni bağlamaq mümkün olan yerlərdə qoltuq dəyənəyi ilə hasarlamağa ehtiyac yoxdur. Ümumiyyətlə, biz metodların yalnız bir hissəsini təhlil etdik. İstəyirsinizsə, davam edə bilərəm: daha çoxları var və həqiqətən diqqətə layiqdirlər. Bunun başqa necə təqdim olunacağına dair hər hansı bir fikriniz varsa, yazın - mən həmişə yeni fikirlərə açığam. Metodlar üçün sənədlər çox yaxşı yazılmışdır, nəticələri olan test nümunələri əlavə edilmişdir ki, bu da metodun işini daha yaxşı başa düşməyə kömək edir. Buna görə də, sənədləri oxumaqdan çəkinmirik: bu, yardım proqramının funksionallığı ilə bağlı şübhələrinizi aradan qaldıracaq. Yeni kodlaşdırma təcrübəsi əldə etmək üçün sizə faydalı siniflərin necə hazırlandığını və yazıldığını nəzərdən keçirməyi məsləhət görürəm. Bu, gələcəkdə faydalı olacaq, çünki adətən hər bir layihənin öz hurda sinifləri var və onları yazmaq təcrübəsi faydalı olacaq. Ənənəvi olaraq, Github-dakı hesabıma abunə olmağı təklif edirəm ) Telegram botu ilə layihəmdən xəbəri olmayanlar üçün burada birinci məqaləyə keçid var . Oxuduğunuz üçün hər kəsə təşəkkür edirəm. Aşağıda bəzi faydalı bağlantılar əlavə etdim.
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION