JavaRush /وبلاگ جاوا /Random-FA /بیایید کلاس StringUtils را تجزیه کنیم
Roman Beekeeper
مرحله

بیایید کلاس StringUtils را تجزیه کنیم

در گروه منتشر شد
سلام به همه، خوانندگان عزیزم. من سعی می کنم در مورد آنچه واقعاً برایم جالب است و در حال حاضر نگرانم است بنویسم. بنابراین، امروز چند مطالعه سبک وجود خواهد داشت که به عنوان مرجع در آینده برای شما مفید خواهد بود: بیایید در مورد StringUtils صحبت کنیم . این اتفاق افتاد که در یک زمان کتابخانه Apache Commons Lang 3بیایید کلاس StringUtils - 1 را تجزیه کنیم را دور زدم . این یک کتابخانه با کلاس های کمکی برای کار با اشیاء مختلف است. این مجموعه ای از روش های مفید برای کار با رشته ها، مجموعه ها و غیره است. در یک پروژه فعلی، که در آن باید با رشته‌ها در ترجمه منطق تجاری ۲۵ ساله (از COBOL به جاوا) با جزئیات بیشتری کار می‌کردم، معلوم شد که دانش کافی از کلاس StringUtils ندارم . بنابراین مجبور شدم همه چیز را خودم بسازم. منظورم چیست؟ این واقعیت که شما مجبور نیستید کارهای خاصی را که شامل دستکاری رشته است بنویسید، بلکه از یک راه حل آماده استفاده کنید. چه اشکالی دارد که خودتان آن را بنویسید؟ حداقل از این نظر این کد بیشتری است که مدتها پیش نوشته شده است. موضوع تست کدی که به صورت اضافی نوشته شده است، کم فشار نیست. وقتی از کتابخانه ای استفاده می کنیم که خوب بودن خود را ثابت کرده است، انتظار داریم که قبلاً آزمایش شده باشد و برای آزمایش آن نیازی به نوشتن یک سری موارد آزمایشی نداشته باشیم. اتفاقاً مجموعه روش های کار با رشته در جاوا آنقدر بزرگ نیست. واقعاً روش های زیادی وجود ندارد که برای کار مفید باشد. این کلاس همچنین برای ارائه چک برای NullPointerException ایجاد شده است. طرح کلی مقاله ما به شرح زیر خواهد بود:
  1. چگونگی اتصال؟
  2. نمونه هایی از کار من: چگونه، بدون اطلاع از چنین کلاس مفیدی، عصا دوچرخه خود را ایجاد کردم .
  3. بیایید روش های دیگری را که برای من جالب بود بررسی کنیم.
  4. بیایید خلاصه کنیم.
همه موارد به یک مخزن جداگانه در سازمان انجمن جاوروش در GitHub اضافه خواهند شد . نمونه ها و تست های جداگانه ای برای آنها وجود خواهد داشت.

0. نحوه اتصال

کسانی که دست در دست من راه می‌روند از قبل هم با گیت و هم با ماون آشنا هستند، بنابراین بیشتر بر این دانش تکیه می‌کنم و خودم را تکرار نمی‌کنم. برای کسانی که مقالات قبلی من را از دست داده اند یا تازه شروع به خواندن کرده اند، در اینجا مطالبی درباره Maven و Git وجود دارد . البته، بدون سیستم ساخت (Maven، Gredl)، می توانید همه چیز را به صورت دستی وصل کنید، اما امروزه این کار دیوانه کننده است و قطعاً نیازی به انجام آن ندارید: بهتر است بلافاصله یاد بگیرید که چگونه همه چیز را به درستی انجام دهید. بنابراین، برای کار با Maven، ابتدا وابستگی مناسب را اضافه می کنیم:
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
جایی که ${apache.common.version} نسخه این کتابخانه است. در مرحله بعد، برای وارد کردن در یک کلاس، import را اضافه کنید:
import org.apache.commons.lang3.StringUtils;
و همین، همه چیز در کیف است))

1. نمونه هایی از یک پروژه واقعی

  • روش چپ پد

مثال اول به طور کلی اکنون آنقدر احمقانه به نظر می رسد که خیلی خوب است که همکارانم از StringUtils.leftPad اطلاع داشتند و به من گفتند. وظیفه چه بود: کد به گونه ای ساخته شده بود که اگر به درستی وارد نمی شد، لازم بود داده ها تغییر شکل دهند. انتظار می رفت که فیلد رشته فقط از اعداد تشکیل شده باشد. اگر طول آن 3 و مقدار آن 1 باشد، ورودی باید "001" باشد. یعنی ابتدا باید تمام فضاها را حذف کنید و سپس آن را با صفر بپوشانید. مثال های بیشتری برای روشن کردن ماهیت کار: از "12" -> "012" از "1" -> "001" و غیره. چه کار کردم؟ این را در کلاس LeftPadExample توضیح داد . من روشی نوشتم که همه اینها را انجام می دهد:
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();
}
به عنوان مبنا، این ایده را در نظر گرفتم که به سادگی می‌توانیم تفاوت بین مقدار اصلی و بریده‌شده را بدست آوریم و آن را با صفرهای جلو پر کنیم. برای انجام این کار از IntStream برای انجام همان عملیات n بار استفاده کردم . و این قطعا نیاز به آزمایش دارد. اگر از قبل از روش StringUtils.leftPad اطلاع داشتم، کاری که می توانستم انجام دهم این است :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
همانطور که می بینید، کد بسیار کمتری وجود دارد و یک کتابخانه تایید شده توسط همه نیز استفاده می شود. برای این منظور در کلاس LeftPadExampleTest دو تست ایجاد کردم (معمولاً وقتی قصد دارند یک کلاس را تست کنند، یک کلاس با همان نام + Test در همان بسته فقط در src/test/java ایجاد می کنند). این تست‌ها یک روش را بررسی می‌کنند تا مطمئن شوند که مقدار را به درستی تغییر می‌دهد، سپس روش دیگری را. البته، تست های بسیار بیشتری باید نوشته شود، اما تست موضوع اصلی در مورد ما نیست:
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);
   }

}
فعلاً می توانم در مورد تست ها نظر بدهم. آنها با استفاده از JUnit 5 نوشته شده اند:
  1. اگر یک تست دارای حاشیه نویسی مناسب باشد، به عنوان یک تست تلقی می شود - @Test.
  2. اگر توصیف عملکرد آزمایش دشوار است یا توصیف طولانی و خواندن آن ناخوشایند است، می‌توانید حاشیه‌نویسی @DisplayName را اضافه کنید و آن را به یک توصیف عادی تبدیل کنید که هنگام اجرای آزمایش‌ها قابل مشاهده باشد.
  3. هنگام نوشتن تست، از روش BDD استفاده می کنم که در آن تست ها را به بخش های منطقی تقسیم می کنم:
    1. //given - بلوک تنظیم داده قبل از آزمایش.
    2. //when بلوکی است که بخشی از کدی که در حال آزمایش آن هستیم راه اندازی می شود.
    3. //then بلوکی است که در آن نتایج بلوک When بررسی می شود.
اگر آنها را اجرا کنید، آنها تأیید می کنند که همه چیز همانطور که انتظار می رود کار می کند.

  • روش stripStart

در اینجا باید مشکلی را با خطی حل کنم که در ابتدا دارای فاصله و کاما باشد. بعد از دگرگونی نباید معنای جدیدی پیدا می کردند. بیان مشکل واضح تر از همیشه است. چند مثال درک ما را تقویت می کند: ", , books" -> "books" ",,, books" -> "books" b , books" -> "b , books" همانطور که در مورد leftPad، من اضافه کردم کلاس StrimStartExample ، که در آن دو متد دارد. یک - با راه حل خاص خود:
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);
}
در اینجا ایده پیدا کردن شاخصی بود که از آن هیچ فاصله یا کاما دیگری وجود ندارد. اگر در ابتدا اصلاً وجود نداشتند، شاخص صفر خواهد شد. و مورد دوم - با یک راه حل از طریق StringUtils :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
در اینجا اطلاعات آرگومان اول را در مورد رشته ای که با آن کار می کنیم ارسال می کنیم و در دومی رشته ای متشکل از کاراکترهایی را که باید رد شوند ارسال می کنیم. ما کلاس StripStartExampleTest را به همین ترتیب ایجاد می کنیم :
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);
   }
}

  • روش خالی است

این روش، البته، بسیار ساده‌تر است، اما این باعث نمی‌شود که کمتر مفید باشد. قابلیت‌های متد String.isEmpty() را گسترش می‌دهد ، که همچنین یک بررسی برای null اضافه می‌کند. برای چی؟ برای جلوگیری از NullPointerException، یعنی اجتناب از فراخوانی متدها بر روی متغیری که null است . بنابراین، برای اینکه ننویسیم:
if(value != null && value.isEmpty()) {
   //doing something
}
شما به سادگی می توانید این کار را انجام دهید:
if(StringUtils.isEmpty(value)) {
   //doing something
}
مزیت این روش این است که بلافاصله مشخص می شود که از کدام روش استفاده می شود.

2. تجزیه و تحلیل روش های دیگر کلاس StringUtils

حالا بیایید در مورد روش هایی صحبت کنیم که به نظر من نیز شایسته توجه هستند. به طور کلی در مورد StringUtils صحبت می کنیم ، شایان ذکر است که این روش روش های ایمن تهی را مشابه آن هایی که در کلاس String یافت می شود (همانطور که در روش isEmpty وجود دارد ) ارائه می دهد. بیایید از آنها عبور کنیم:

  • روش مقایسه

چنین روشی در String وجود دارد و اگر هنگام مقایسه دو رشته، یکی از آنها تهی باشد، یک NullPointerException ایجاد می کند. برای جلوگیری از بررسی های زشت در کد خود، می توانیم از روش StringUtils.compare (String str1, String str2) استفاده کنیم : این یک int را به عنوان نتیجه مقایسه برمی گرداند. این مقادیر به چه معناست؟ int = 0 اگر یکسان باشند (یا هر دو تهی باشند). int < 0، اگر str1 کمتر از str2 باشد. int > 0، اگر str1 بزرگتر از str2 باشد. همچنین، اگر به مستندات آنها نگاه کنید، Javadoc این روش سناریوهای زیر را ارائه می دهد:
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

  • شامل ... روش ها

در اینجا توسعه دهندگان ابزار یک انفجار داشتند. هر روشی که بخواهید وجود دارد. تصمیم گرفتم آنها را کنار هم بگذارم:
  1. contain روشی است که بررسی می کند آیا رشته مورد انتظار داخل رشته دیگری است یا خیر. این چگونه مفید است؟ اگر لازم است از وجود کلمه خاصی در متن مطمئن شوید، می توانید از این روش استفاده کنید.

    مثال ها:

    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

    باز هم، امنیت NPE (Null Pointer Exception) وجود دارد.

  2. containAny متدی است که بررسی می کند آیا هر یک از کاراکترهای موجود در رشته وجود دارد یا خیر. همچنین یک چیز مفید: اغلب باید این کار را انجام دهید.

    نمونه هایی از مستندات:

    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 است . در واقع، برای بررسی چنین موردی بدون این روش، باید چندین گزینه را طی کنید. و بنابراین تنها یک روش به طور هماهنگ استفاده خواهد شد.

  4. چند نمونه از اسناد:

    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. حاوی هیچکدام - با قضاوت بر اساس نام، می توانید آنچه را که بررسی می شود درک کنید. در داخل نباید هیچ خطی وجود داشته باشد. یک چیز مفید، قطعا. جستجوی سریع برای برخی از شخصیت های ناخواسته ؛). در ربات تلگرام خود ، فحاشی ها را فیلتر می کنیم و این روش های خنده دار را نادیده نخواهیم گرفت.

    و مثالها، ما بدون آنها کجا خواهیم بود:

    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

مجموعه‌ای از روش‌ها که در صورت خالی بودن رشته و نیاز به تنظیم مقداری پیش‌فرض، از افزودن اطلاعات اضافی جلوگیری می‌کند. گزینه های زیادی برای هر سلیقه ای وجود دارد. اصلی‌ترین آنها StringUtils.defaultString (رشته رشته نهایی، رشته نهایی defaultStr) است - در صورتی که str null باشد، ما به سادگی مقدار را به defaultStr می‌دهیم . نمونه هایی از مستندات:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
هنگام ایجاد یک کلاس POJO با داده، استفاده از آن بسیار راحت است.

  • روش حذف Whitespace

این یک روش جالب است، اگرچه گزینه های زیادی برای کاربرد آن وجود ندارد. در عین حال اگر چنین موردی پیش بیاید قطعا روش بسیار مفید خواهد بود. تمام فاصله ها را از رشته حذف می کند. هر جا این شکاف باشد، اثری از آن باقی نخواهد ماند))) نمونه هایی از اسناد:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • endsWith روش

برای خودش صحبت می کند. این یک روش بسیار مفید است: بررسی می کند که آیا رشته با رشته پیشنهادی به پایان می رسد یا خیر. این اغلب ضروری است. البته، شما می توانید چک را خودتان بنویسید، اما استفاده از یک روش آماده به وضوح راحت تر و بهتر است. مثال ها:
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
همانطور که می بینید، همه چیز با یک خط خالی تمام می شود))) من فکر می کنم که این مثال (StringUtils.endsWith("ABCDEF", "") = true) فقط یک امتیاز است، زیرا این پوچ است) همچنین روشی وجود دارد که مورد را نادیده می گیرد .

  • روش برابر است

یک مثال عالی از یک روش امن تهی که دو رشته را با هم مقایسه می کند. هر چه در آنجا قرار دهیم، پاسخ همانجا خواهد بود و بدون خطا خواهد بود. مثال ها:
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
البته، برابری IgnoreCase نیز وجود دارد - همه چیز دقیقاً به همان روش انجام می شود، فقط ما مورد را نادیده می گیریم. اجازه بدید ببینم؟
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

  • برابر با هر روش

بیایید جلو برویم و روش برابر را گسترش دهیم . فرض کنید به جای چندین بررسی برابری، می خواهیم یکی را انجام دهیم. برای این کار، می‌توانیم رشته‌ای را ارسال کنیم که مجموعه‌ای از رشته‌ها با آن مقایسه می‌شوند؛ اگر هر یک از آنها با رشته پیشنهادی برابر باشد، درست است. یک رشته و مجموعه ای از رشته ها را رد می کنیم تا آنها را با یکدیگر مقایسه کنیم (نخستین رشته با رشته های مجموعه). دشوار؟ در اینجا نمونه هایی از اسناد آورده شده است تا به شما کمک کند منظور را درک کنید:
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
همچنین برابر باAnyIgnoreCase وجود دارد . و مثال هایی برای آن:
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

خط پایین

در نتیجه، ما با آگاهی از چیستی StringUtils و روش‌های مفید آن را ترک می‌کنیم. خوب، با درک اینکه چنین چیزهای مفیدی وجود دارد و نیازی به حصار کشی هر بار با عصا در مکان هایی نیست که می توان با کمک یک راه حل آماده، موضوع را بسته کرد. به طور کلی، ما تنها بخشی از روش ها را تحلیل کرده ایم. اگر بخواهید، می توانم ادامه دهم: تعداد بیشتری از آنها وجود دارد و واقعاً شایسته توجه هستند. اگر نظری در مورد اینکه چگونه می توان این را ارائه کرد، دارید، لطفا بنویسید - من همیشه آماده ایده های جدید هستم. مستندات روش ها بسیار خوب نوشته شده است، نمونه های آزمایشی با نتایج اضافه شده است، که به درک بهتر عملکرد روش کمک می کند. بنابراین، ما از خواندن مستندات ابایی نداریم: این شک شما را در مورد عملکرد ابزار برطرف می کند. برای به دست آوردن تجربه کدنویسی جدید، به شما توصیه می کنم به نحوه ساخت و نوشتن کلاس های کاربردی نگاه کنید. این در آینده مفید خواهد بود، زیرا معمولاً هر پروژه دارای کلاس های ضایعات خاص خود است و تجربه نوشتن آنها مفید خواهد بود. به‌طور سنتی، پیشنهاد می‌کنم در حساب کاربری من در Github مشترک شوید ) برای کسانی که از پروژه من با ربات تلگرام اطلاعی ندارند، در اینجا یک پیوند به مقاله اول وجود دارد . با تشکر از همه برای خواندن. من چند لینک مفید در زیر اضافه کرده ام.
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION